Why is Tuple Covariant / NamedTuple is not?

Hello everyone,

Here is my situation when you work …

WITH TUPLE

A = (:a, (2,3))
B = (:b, (2,3,4))

using Test
@test  typeof(A) !== typeof(B)

# but, you can catch them with vararg
T = Tuple{Symbol, NTuple{N,Int} where N}
@test A isa T
@test B isa T

WHILE WITH NAMEDTUPLE

NA = (foo=:a, bar=(2,3))
NB = (foo=:b, bar=(2,3,4))

@test typeof(NA) !== typeof(NB)

# and you can not catch them with vararg
NT = NamedTuple{(:foo, :bar),T}
@test_broken NA isa NT
@test_broken NB isa NT

# even broaden them up to tuple fail
T2 = Tuple{Symbol, Tuple}
NT2 = NamedTuple{(:a,:b),T2}
@test_broken NA isa NT2
@test_broken NB isa NT2

# meanwhile covariance still ok @ tuple
@test A isa T2
@test B isa T2

so, aren’t there any fix for now ?

i found one, turning to array.

NA = (foo=:a, bar=[2,3])
NB = (foo=:b, bar=[2,3,4])

T3 = Tuple{Symbol, Vector{Int}}
NT3 = NamedTuple{(:foo, :bar),T3}
@test NA isa NT3
@test NB isa NT3

But i find ugly and a bit counterfeiting.

Is there something better in this case, eg.

  • having a supertype with collection fields in namedtuple
  • without needing to flip front and back from tuple to vector ?

PS I subodorate that’s may be just a lack of time and review to do it

Just make NT covariant on T:

julia> T = Tuple{Symbol, NTuple{N,Int} where N}
Tuple{Symbol,Tuple{Vararg{Int64,N}} where N}

julia> NT = NamedTuple{(:foo, :bar), <:T}
NamedTuple{(:foo, :bar),var"#s46"} where var"#s46"<:Tuple{Symbol,Tuple{Vararg{Int64,N}} where N}

julia> (foo=:a, bar=(2,3)) isa NT
true

Tuple is the only covariant type in julia, everything else must be made covariant with <: if that’s what you want.

5 Likes

Good point. Thanks.

I was used to deal with <: on fun sig.
But is is autonomous and will generate everywhere the good typevar that make it work

FOR MEMO

NT4 = NamedTuple{(:foo, :bar), <:T}  # the NT :)

@test last(Base.unwrap_unionall(NT4).parameters) isa TypeVar
@test (last(Base.unwrap_unionall(NT4).parameters)).ub == T