# Type management for NamedTuple's inconsistent or intended?

**URL:** <https://discourse.julialang.org/t/type-management-for-namedtuples-inconsistent-or-intended/82326>\
**Category:** Internals & Design\
**Created:** [June 6, 2022, 4:53pm UTC](https://discourse.julialang.org/t/type-management-for-namedtuples-inconsistent-or-intended/82326 "2022-06-06T16:53:36Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![skraemer](https://avatars.discourse-cdn.com/v4/letter/s/4bbf92/32.png) [@skraemer](https://discourse.julialang.org/u/skraemer)\
**Post date:** [June 6, 2022, 4:53pm UTC](https://discourse.julialang.org/t/type-management-for-namedtuples-inconsistent-or-intended/82326/1 "2022-06-06T16:53:36Z")

</div>

Is there an inherent reason that type management of `NamedTuple`’s works differently from normal `Tuple`’s (in 1.7 at least)?

In particular, is the behavior of the following second command _(case 1.2)_ intended for something? If so, can you give an explaining example? I have not really been able to find a simpler logic behind the given functionality.

I have included more examples below (which are less important but still haunt me), but I think it all boils down to the weird(?) syntax of the following fourth command _(case 1.4)_:

```julia
typeof(convert(Tuple{AbstractArray}, (1:2,)))
# Tuple{UnitRange{Int64}} (case 1.1)
typeof(convert(NamedTuple{(:a,),Tuple{AbstractArray}}, (a=1:2,)))
# NamedTuple{(:a,), Tuple{AbstractArray}} (case 1.2)
typeof(convert(NamedTuple{(:a,),Tuple{T} where T<:AbstractArray}, (a=1:2,)))
# NamedTuple{(:a,), Tuple{AbstractArray}} (case 1.3)
typeof(convert(NamedTuple{(:a,),<:Tuple{AbstractArray}}, (a=1:2,)))
# NamedTuple{(:a,), Tuple{UnitRange{Int64}}} (case 1.4)
typeof(convert(NamedTuple{(:a,),Tuple{UnitRange}}, (a=1:2,)))
# NamedTuple{(:a,), Tuple{UnitRange}} (case 1.5)

```

Since for Tuple’s, we have `Tuple{UnitRange{Int64}} <: Tuple{AbstractArray}`, the first command makes sense. However, in the second and even the third command, the type `Tuple{AbstractArray}` appears, and only the fourth command will behave analogously to the first command. The fifth changes in turn the `eltype` of `UnitRange` to `Any`, which is somewhat what the second command does, but now for the `eltype`.

Secondly, there is also some trouble with `Vector`’s of `NamedTuple`’s, while for `Tuple`’s, types are automatically derived. Is this intended or maybe just not (yet) implemented?

```julia
typeof([(1:2,), ([1, 2],)]))
# Vector{Tuple{AbstractVector{Int64}}} (case 2.1)
typeof([(a=1:2,), (a=1:2,)])
# Vector{NamedTuple{(:a,), Tuple{UnitRange{Int64}}}} (case 2.2)
typeof([(a=1:2,), (a=[1, 2],)])
# Vector{NamedTuple{(:a,)}} (case 2.3)

```

Though one can manually convert the latter `Vector`, this becomes increasingly cumbersome due to the first issue:

```julia
typeof(convert(Vector{NamedTuple{(:a,),Tuple{AbstractArray}}}, [(a=1:2,), (a=[1, 2],)]))
# Vector{NamedTuple{(:a,), Tuple{AbstractArray}}} (case 3.1)
typeof(convert(Vector{NamedTuple{(:a,),<:Tuple{AbstractArray}}}, [(a=1:2,), (a=[1, 2],)]))
# Vector{NamedTuple{(:a,), <:Tuple{T} where T<:AbstractArray}} (case 3.2)
typeof(convert(Vector{NamedTuple{(:a,),<:Tuple{AbstractArray{Int64}}}}, [(a=1:2,), (a=[1, 2],)]))
# Vector{NamedTuple{(:a,), <:Tuple{AbstractArray{Int64}}}} (case 3.3)

```

Thirdly, when one in turn includes an abstract eltype `Number`, an error may be thrown. I have included some more commands for better reference, while the error is thrown by the last command.

```julia
typeof(convert(Tuple{AbstractArray{<:Number}}, (1:2,)))
# Tuple{UnitRange{Int64}} (case 4.1)
typeof(convert(Tuple{AbstractArray{Number}}, (1:2,)))
# Tuple{Vector{Number}} (case 4.2)
typeof(convert(NamedTuple{(:a,),Tuple{AbstractArray{<:Number}}}, (a=1:2,)))
# NamedTuple{(:a,), Tuple{AbstractArray{<:Number}}} (case 4.3)
typeof(convert(NamedTuple{(:a,),Tuple{AbstractArray{Number}}}, (a=1:2,)))
# NamedTuple{(:a,), Tuple{AbstractArray{Number}}}((Number[1, 2],)) (case 4.4)
typeof(convert(NamedTuple{(:a,),<:Tuple{AbstractArray{<:Number}}}, (a=1:2,)))
# NamedTuple{(:a,), Tuple{UnitRange{Int64}}} (case 4.5)
convert( NamedTuple{(:a,),<:Tuple{AbstractArray{Number}}}, (a=1:2,))
# throws error (case 4.6)

```

---

<div class="post-metadata">

**Author:** ![JeffreySarnoff](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeffreysarnoff/32/1980_2.png) [@JeffreySarnoff](https://discourse.julialang.org/u/JeffreySarnoff)\
**Post date:** [June 7, 2022, 3:04am UTC](https://discourse.julialang.org/t/type-management-for-namedtuples-inconsistent-or-intended/82326/2 "2022-06-07T03:04:04Z")

</div>

(you probably know some/all of this … its here in case any of this is new info. I understand your questions are not directly addressed below; perhaps it shows why converting to type containing an abstraction is disrecommended, and why Base.convert is extensible should you need special handling.)

An `AbstractArray` is an abstraction over Array-like entities. Neither a `Tuple` nor a `NamedTuple` is a subtype of `AbstractArray`. Conversion from either into an `AbstractArray` or a subtype thereof (e.g. a `Vector`) is an unsupported operation:

```julia
julia> atuple = (1, 2);
julia> anamedtuple = (a=1, b=2);

julia> convert(Vector, atuple)
ERROR: MethodError: Cannot `convert` an object of type
  Tuple{Int64, Int64} to an object of type
  Vector

julia> convert(AbstractArray, anamedtuple)
ERROR: MethodError: Cannot `convert` an object of type
  NamedTuple{(:a, :b), Tuple{Int64, Int64}} to an object of type
  AbstractArray

julia> convert(Vector{Int}, atuple)
ERROR: MethodError: Cannot `convert` an object of type Tuple{Int64, Int64} to an object of type Vector{Int64}

julia> Base.convert(::Type{Vector{T}}, atuple::NTuple{N,T}) where {N,T} = [atuple...]

julia> convert(Vector{Int}, atuple)
2-element Vector{Int64}:
 1
 2

```

---

<div class="post-metadata">

**Author:** ![skraemer](https://avatars.discourse-cdn.com/v4/letter/s/4bbf92/32.png) [@skraemer](https://discourse.julialang.org/u/skraemer)\
**Post date:** [June 13, 2022, 12:38pm UTC](https://discourse.julialang.org/t/type-management-for-namedtuples-inconsistent-or-intended/82326/3 "2022-06-13T12:38:02Z")

</div>

This is not about converting `Tuple`’s to `Array`’s.

I should maybe provide some context that hopefully helps. What I had initially in mind was to use constructions similar to

```julia
x = [(a=1:2, b=1:2), (a=[1,2], b=2:3)]
foo(x)

```

where `foo` has a method tailored to `foo(x::Vector{NamedTuple{(:a,:b), Tuple{AbstractVector{Int64},AbstractVector{Int64}}}})`. I could certainly use a custom struct

```julia
struct S
   a::AbstractArray
   b::AbstractArray
end

```

and overload `foo(x::Vector{S})`, but just defining `const S` as aboves NamedTuple type seemed more convenient, as the given application is not so much about speed. This is also since a `struct S` will not retain subtypes of `AbstractArray`.

Though in danger of this getting even longer, see the following which works quite nicely with `Tuple`’s but not so much with `NamedTuple`’s.

```julia
struct S
    a::AbstractArray{Int64}
    b::AbstractArray{Int64}
end
const NT = NamedTuple{(:a,:b), <:Tuple{<:AbstractArray{Int64},<:AbstractArray{Int64}}}
const URNT = NamedTuple{(:a,:b), <:Tuple{<:UnitRange{Int64},<:UnitRange{Int64}}}
const T = Tuple{AbstractArray{Int64},AbstractArray{Int64}}
const URT = Tuple{UnitRange{Int64},UnitRange{Int64}}
foo(x::Any) = "Any"
foo(x::Vector{<:NT}) = "NamedTuple"
foo(x::Vector{<:URNT}) = "NamedTuple of UnitRanges"
foo(x::Vector{<:T}) = "Tuple"
foo(x::Vector{<:URT}) = "Tuple of UnitRanges"
foo(x::Vector{<:S}) = "Struct"

display(foo([(1:2,1:2),([1,2],2:3)]))
# "Tuple"
display(foo([(1:2,1:2),(1:2,2:3)]))
# "Tuple of UnitRanges" <- quite convenient
display(foo([(a=1:2, b=1:2), (a=[1,2], b=2:3)]))
# "Any"
display(foo([(a=1:2, b=1:2), (a=1:2, b=2:3)]))
# "NamedTuple of UnitRanges"
display(foo([S(1:2,1:2),S([1,2],2:3)]))
# "Struct"
display(foo([S(1:2,1:2),S(1:2,2:3)]))
# "Struct" <- can not retain NamedTuple with custom struct

display(foo(T[(1:2,1:2),([1,2],2:3)]))
# "Tuple"
display(foo(T[(1:2,1:2),(1:2,2:3)]))
# "Tuple" <- this a-priorly sets abstract type
display(foo(NT[(a=1:2, b=1:2), (a=[1,2], b=2:3)]))
# "NamedTuple"
display(foo(NT[(a=1:2, b=1:2), (a=1:2, b=2:3)]))
# "NamedTuple" <- better, but remedies UnitRange type
display(foo(S[S(1:2,1:2),S([1,2],2:3)]))
# "Struct"
display(foo(S[S(1:2,1:2),S(1:2,2:3)]))
# "Struct"

display(foo(convert(Vector{<:T},[(1:2,1:2),([1,2],2:3)])))
# "Tuple"
display(foo(convert(Vector{<:T},[(1:2,1:2),(1:2,2:3)])))
# "Tuple of UnitRanges" <- again convenient as one can enforce this via bar(...)::Vector{<:T}
display(foo(convert(Vector{NT},[(a=1:2, b=1:2), (a=[1,2], b=2:3)]))) # 
# "NamedTuple" <- Vector{<:NT} instead throws error
display(foo(convert(Vector{NT},[(a=1:2, b=1:2), (a=1:2, b=2:3)]))) # Vector{<:NT} throws error
# "NamedTuple" <- Vector{<:NT} instead throws error

```

---

<div class="post-metadata">

**Author:** ![nalimilan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nalimilan/32/147_2.png) [@nalimilan](https://discourse.julialang.org/u/nalimilan)\
**Post date:** [June 13, 2022, 3:27pm UTC](https://discourse.julialang.org/t/type-management-for-namedtuples-inconsistent-or-intended/82326/4 "2022-06-13T15:27:09Z")

</div>

IIUC your complaint is that promotion of tuples gives a more specific element type than promotion of `NamedTuple`s:

```julia
julia> eltype([(1:2, 1:2), ([1,2], 2:3)])
Tuple{AbstractVector{Int64}, UnitRange{Int64}}

julia> eltype([(a=1:2, b=1:2), (a=[1,2], b=2:3)])
NamedTuple{(:a, :b)}

```

This is due to tuples using particular promotion rules (they are [very special](https://docs.julialang.org/en/v1/manual/types/#Tuple-Types-1) in the type system), while `NamedTuple`s just use the fallback promotion rules of any `struct`, which call `typejoin`. I agree it can be annoying, I had made [a pull request](https://github.com/JuliaLang/julia/pull/25924/) to change this. But I don’t really know what would be the drawbacks.

---

<div class="post-metadata">

**Author:** ![skraemer](https://avatars.discourse-cdn.com/v4/letter/s/4bbf92/32.png) [@skraemer](https://discourse.julialang.org/u/skraemer)\
**Post date:** [June 13, 2022, 3:50pm UTC](https://discourse.julialang.org/t/type-management-for-namedtuples-inconsistent-or-intended/82326/5 "2022-06-13T15:50:51Z")

</div>

Well I would not go as far as to wish to complain, but rather be curious about it. I tried to describe potential drawbacks in the example above, that is, the difficulties to work with types of `Array`’s of `NamedTuple`’s.

Apart from that, it took me a while to realize where my errors came from due to the different behaviors and I thought to have misunderstood rules for types. So this might be something others struggle or have struggled with too. The pull request you linked is unfortunately yet above my understanding of Julia. I think it is not just about promotion.

In that sense, I so far only have a naturally arising, superficial perspective on Julia that is not much influenced what may be behind the implementation.

One could maybe argue that the name `NamedTuple` is a bit unfortunate when the behavior rather seems to rely on `struct` rules, if I understood it correctly.

It is just that Julia seems overall very consistent and intuitive, but for example explaining this behavior already takes some deeper insight. Naively, one would think that `NamedTuple`’s just adds names and otherwise behaves equivalently to `Tuple`’s. Yet the contained type `<:Tuple{AbstractArray}` as in aboves case 1.4 does not even exist outside of the `NamedTuple`.
