Broader (non-concrete) types in NamedTuple

I wonder if constructing NamedTuple{names, T}(::S) where S <: T is a bug, eg

julia> VERSION
v"1.1.0-DEV.439"

julia> NamedTuple{(:a, :b)}((missing, 10))
(a = missing, b = 10)

julia> NamedTuple{(:a, :b),Tuple{Union{Missing, Float32},Union{Missing, Int16}}}((missing, 10))
NamedTuple{(:a, :b),Tuple{Union{Missing, Float32},Union{Missing, Int16}}}((missing, 10))

Why would it be a bug? Both the NamedTuples you constructed are valid NamedTuples.

Cf

julia> Tuple{Union{Missing, Float32},Union{Missing, Int16}}((missing, 10))
(missing, 10)

julia> typeof(ans)
Tuple{Missing,Int16}

so you cannot do this with Tuple.

If you think of a NamedTuple as a container implemented using a Tuple, then this is OK, but if you think of it as “a Tuple with names” then this is inconsistent.

2 Likes

Related: NamedTuples are apparently not covariant:

julia> NamedTuple{(:a,), Tuple{Int}} <: NamedTuple{(:a,), Tuple{Integer}}
false

julia> Tuple{Int} <: Tuple{Integer}
true
1 Like

Looking at the source, it seems that having broader types for the fields is something that is allowed, at least Base.show takes special care when printing.

@jeff.bezanson, since you wrote most of this, can you please clarify the intention?

also @cstjean’s point about lack of covariance and whether this is intended.

It’s not clear whether NamedTuples (or, indeed, tuples! see https://github.com/JuliaLang/julia/issues/24614) should be covariant, but for now they are intentionally invariant. The main motivation was of course missing data, where allowing Union{T,Missing} as the field type of a concrete type is potentially useful.

2 Likes