Subtyping `NamedTuple`s in `NamedTuple`s

I don’t understand the following behavior:

julia> typeof((a = (b = 1,),)) <: NamedTuple{(:a,), Tuple{<:NamedTuple}}
false

julia> typeof((a = (b = 1,),)) <: NamedTuple{(:a,), Tuple{NT}} where {NT <: NamedTuple}
true

This is due to two confusing things:

  1. Tuple is covaritant w.r.t its type parameters, whereas NamedTuple is invariant.
  2. A{B{C}} where {C <: D} is different from A{(B{C} where {C <: D})}. In the former, B is treated as a TypeVar, so it’s equivalent to A{<:B{C<:D}}.

So, let’s look at (a = (b = 1,),).:

  • Its type is NamedTuple{(:a,), Tuple{NamedTuple{(:b,), Tuple{Int}}}}
  • Therefore, it’s also a subtype of the UnionAll type NamedTuple{(:a,), <:Tuple}.
    • This is because Tuple{NamedTuple{(:b,), Tuple{Int}}}} <: Tuple
  • However, it’s NOT a subtype of the concrete type NamedTuple{(:a,), Tuple}, which is what your first line queries, because NamedTuple is invariant.

Your second line has NamedTuple{(:a,), Tuple{NT}} where {NT <: NamedTuple}.

  • Since the where is outside the outer parenthesis, there where applies to the outermost NamedTuple - in other words, it makes the NamedTuple a UnionAll
  • That means it’s equivalent to NamedTuple{(:a,), <:Tuple{<:NamedTuple}}. And this is clearly a correct supertype
  • Alternatively, NamedTuple{(:a,), (Tuple{NT} where {NT <: NamedTuple})} is NOT a supertype of the given name dtuple, because the where is inside the outer parenthesis
1 Like

Thanks for the quick reply and the explanation regarding UnionAll types :+1:

1 Like