# Strange behavior of union of types

**URL:** <https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298>\
**Category:** New to Julia\
**Created:** [February 28, 2023, 1:18am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298 "2023-02-28T01:18:49Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![jinfreedom](https://avatars.discourse-cdn.com/v4/letter/j/c6cbf5/32.png) [@jinfreedom](https://discourse.julialang.org/u/jinfreedom)\
**Post date:** [February 28, 2023, 1:18am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/1 "2023-02-28T01:18:49Z")

</div>

Hi, I defined some type `UNTuple` as union of `NTuple` and `NamedTuple{Name,NTuple{N,...}}` and got the following unexpected behavior. What I hope for the `UNTuple` is an N-tuple of numbers of same type. The behavior seems strange and unexpected:

```julia
julia> const NNTuple{N,T,NM} = NamedTuple{NM, NTuple{N,T}} where {N,T<:Number,NM}
NamedTuple{NM, Tuple{Vararg{T, N}}} where {N, T<:Number, NM}

julia> const UNTuple{N,T<:Number} = Union{NTuple{N,T},NNTuple{N,T}}
Union{Tuple{Vararg{T, N}}, NamedTuple{NM, Tuple{Vararg{T, N}}} where NM} where {N, T<:Number}

julia> (1,2.0) isa UNTuple{2}
true

julia> (1,2.0) isa NTuple{2}
false

julia> (1,2.0) isa NNTuple{2}
false

julia> (1,2.0) isa Union{NTuple{2},NNTuple{2}}
false

julia> (1,2.0) isa Union{NTuple{N},NNTuple{N}} where N
false

```

So what’s going on here and how to define `UNTuple` correctly?

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 28, 2023, 5:01am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/2 "2023-02-28T05:01:07Z")

</div>

Seems like a bug.

```julia
julia> let T=Union{NTuple{N,T}, NamedTuple{K,NTuple{N,T}} where K} where {N,T<:Number}
           Tuple{Int,Int} <: T,
           Tuple{Int,Float64} <: T, # should be false
           NamedTuple{(:a,:b),Tuple{Int,Int}} <: T,
           NamedTuple{(:a,:b),Tuple{Int,Float64}} <: T
       end
(true, true, true, false)

```

This is how it _should_ behave:

```julia
julia> let T=Union{NTuple{N,T} where {N,T<:Number}, NamedTuple{K,NTuple{N,T}} where {K,N,T<:Number}}
           Tuple{Int,Int} <: T,
           Tuple{Int,Float64} <: T,
           NamedTuple{(:a,:b),Tuple{Int,Int}} <: T,
           NamedTuple{(:a,:b),Tuple{Int,Float64}} <: T
       end
(true, false, true, false)

```

---

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [February 28, 2023, 7:38am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/3 "2023-02-28T07:38:16Z")

</div>

This seems to arise from the following behavior (that I don’t fully understand):

```julia
julia> Tuple{Int,Float64} <: NTuple{2,Number}
true

julia> Tuple{Int,Float64} <: NTuple{2}
false

julia> NTuple{2,Number} <: NTuple{2}
false

julia> NTuple{2,Number} <: NTuple{2,Any}
true

```

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 28, 2023, 7:49am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/4 "2023-02-28T07:49:33Z")

</div>

![WoahNeoGIF](https://global.discourse-cdn.com/julialang/original/3X/5/c/5c3b09703545337ab2adfb7027b19f98c3c7848b.gif)

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 28, 2023, 8:26am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/5 "2023-02-28T08:26:25Z")

</div>

Okay, I think that behavior makes sense.

```julia
julia> NTuple{2,Number}, NTuple{2,T} where T<:Number
(Tuple{Number, Number}, Tuple{T, T} where T<:Number)

julia> @show Tuple{Int,Float64} <: NTuple{2, Number}
       @show Tuple{Int,Float64} <: NTuple{2,<:Number};
Tuple{Int, Float64} <: NTuple{2, Number} = true
Tuple{Int, Float64} <: NTuple{2, <:Number} = false

julia> @show NTuple{2, Number} <: NTuple{2, Any}
       @show NTuple{2, Number} <: NTuple{2, <:Any}
       @show NTuple{2, <:Number} <: NTuple{2, Any}
       @show NTuple{2, <:Number} <: NTuple{2, <:Any};
NTuple{2, Number} <: NTuple{2, Any} = true
NTuple{2, Number} <: NTuple{2, <:Any} = false
NTuple{2, <:Number} <: NTuple{2, Any} = true
NTuple{2, <:Number} <: NTuple{2, <:Any} = true

```

Entertainingly, the `show` method prints the anonymous variable name:

```julia
julia> NTuple{2,<:Number}
Tuple{var"#s1", var"#s1"} where var"#s1"<:Number

```

Seems unrelated to the OP though.

---

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [February 28, 2023, 8:31am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/6 "2023-02-28T08:31:45Z")

</div>

> [@uniment](#):
>
> `Tuple{Int,Float64} <: NTuple{2,<:Number}`

I’m still a bit confused:

```julia
julia> Number<:Number
true

julia> NTuple{2,Number} <: NTuple{2,<:Number}
false

julia> NTuple{2,<:Number}
Tuple{var"#s52", var"#s52"} where var"#s52"<:Number

```

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 28, 2023, 8:40am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/7 "2023-02-28T08:40:47Z")

</div>

Does this help?

```julia
julia> Tuple{Number,Number} <: Tuple{T,T} where T<:Number
false

julia> Tuple{Number,Number} >: Tuple{T,T} where T<:Number
true

```

---

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [February 28, 2023, 10:01am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/8 "2023-02-28T10:01:29Z")

</div>

Oh, I see, thanks!

---

<div class="post-metadata">

**Author:** ![jinfreedom](https://avatars.discourse-cdn.com/v4/letter/j/c6cbf5/32.png) [@jinfreedom](https://discourse.julialang.org/u/jinfreedom)\
**Post date:** [February 28, 2023, 2:28pm UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/9 "2023-02-28T14:28:26Z")

</div>

OK, here seems to be some hint: the parameter `T<:Number` can not only be a numerical type but also be a union of numerical types：

```julia
julia> const UNTuple{N,T} = Union{NTuple{N,T},NamedTuple{M,NTuple{N,T}} where M} where T<:Number
Union{Tuple{Vararg{T, N}}, NamedTuple{M, Tuple{Vararg{T, N}}} where M} where {N, T<:Number}

julia> Tuple{Int,Float64} <: UNTuple{2}
true

julia> Tuple{Int,Float64} <: UNTuple{2,T} where T<:Number
true

julia> Tuple{Int,Float64} <: UNTuple{2,Union{Int,Float64}}
true

julia> Union{Int,Float64}<:Number
true

```

However, this is not the case for `NTuple`:

```julia
julia> Tuple{Int,Float64} <: NTuple{2,T} where T<:Number
false

julia> Tuple{Int,Float64} <: NTuple{2,Union{Int,Float64}}
true

```

It seems that `NTuple{N,T}` is defined such that `T` cannot be `Union{Int,Float64}` unless explicitly forced. So my question would be: how to force `T<:Number` to be a concrete numerical type?

---

<div class="post-metadata">

**Author:** ![simsurace](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simsurace/32/30216_2.png) [@simsurace](https://discourse.julialang.org/u/simsurace)\
**Post date:** [February 28, 2023, 3:14pm UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/10 "2023-02-28T15:14:21Z")

</div>

See [Types · The Julia Language](https://docs.julialang.org/en/v1/manual/types/#Tuple-Types), in contrast to other types, tuple types are covariant. EDIT: maybe unrelated, but still good to remember.

---

<div class="post-metadata">

**Author:** ![Sevi](https://avatars.discourse-cdn.com/v4/letter/s/c67d28/32.png) [@Sevi](https://discourse.julialang.org/u/Sevi)\
**Post date:** [February 28, 2023, 9:03pm UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/11 "2023-02-28T21:03:35Z")

</div>

I’ll just give my two cents to see if I got the full picture myself. Maybe it can help anyone else too (or clear up my misconceptions!).

> [@jinfreedom](#):
>
> It seems that `NTuple{N,T}` is defined such that `T` cannot be `Union{Int,Float64}` unless explicitly forced. So my question would be: how to force `T<:Number` to be a concrete numerical type?

I think what happens in that last example is that `NTuple{2,T} where T<:Number` represents `Tuple{T,T} where T <: Number`. The type of any object which will be checked against that tuple type has to be a concrete type. Hence any instance of `Tuple{Int, Float64}` will have exactly that type. Since `Int` and `Float64` are not the same `T`, we have that `Tuple{Int,Float64} <: NTuple{2,<:Number}` is false. Note that `T<:Number` _is_ forced to be a concrete, numerical type. It’s just that it has to be the _same_ concrete type in both entries of the tuple.

That `NTuple{2,Union{Int,Float64}}` works is due to the covariance of `Tuple` types, as @simsurace pointet out. Note that `Union{Int,Float64}` is not a concrete type, so `Tuple{Int,Float64} <: NTuple{2,Union{Int,Float64}}` is true (but there can be no instance of that type on the right).

Neither `NTuple{2,Union{Int,Float64}} <: NTuple{2,<:Number}` nor `NTuple{2,<:Number} <: NTuple{2,Union{Int,Float64}}` are true since there are tuples which will fall into one type, but not into the other.

When thinking about types, I found this video pretty helpful which was posted somewhere else on this discourse. Types and subtyping essentially works like set and subset calculations in mathematics. A type union is basically a union set.

[![](https://global.discourse-cdn.com/julialang/original/3X/4/9/492c9aff3d96076d5a907e1e76178ed47a4e7de8.jpeg "JuliaCon 2017 | The State of the Type System | Jeff Bezanson") ](https://www.youtube.com/watch?v=Z2LtJUe1q8c)

---

<div class="post-metadata">

**Author:** ![jinfreedom](https://avatars.discourse-cdn.com/v4/letter/j/c6cbf5/32.png) [@jinfreedom](https://discourse.julialang.org/u/jinfreedom)\
**Post date:** [February 28, 2023, 11:15pm UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/12 "2023-02-28T23:15:57Z")

</div>

Thank you all for your input. I still get confused by the following very simple example

```julia
julia> Tuple{Int,Float64}<:Union{Tuple{T,T}, Vector{T}} where T<:Number
true

julia> Tuple{Int,Float64}<:Union{Tuple{T,T}, Vector} where T<:Number
false

julia> (Union{Tuple{T,T}, Vector{T}} where T<:Number) <: (Union{Tuple{T,T}, Vector} where T<:Number)
false

```

which does not make any sense to me, as the first union should be a subset of the second union, shouldn’t it?

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [March 1, 2023, 12:44am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/13 "2023-03-01T00:44:02Z")

</div>

> [@Sevi](#):
>
> Note that `T<:Number` _is_ forced to be a concrete, numerical type. It’s just that it has to be the _same_ concrete type in both entries of the tuple.

It has to be the same type, but not necessarily concrete:

```julia
julia> (NTuple{N,T} where {N,T<:Real}) <: NTuple{N,T} where {N,T<:Number}
true

```

The comparison of concrete types happens naturally when you call `isa` on an _instance_, which is of course concrete.

> [@simsurace](#):
>
> maybe unrelated, but still good to remember.

It _does_ seem related to this though: [Diagonal Types - More about types · The Julia Language](https://docs.julialang.org/en/v1/devdocs/types/#Diagonal-types).

> [@jinfreedom](#):
>
> the first union should be a subset of the second union, shouldn’t it?

Yes. Hence, why this seems like a bug. It’s some sort of interaction between covariant and invariant types.

To illustrate, this works correctly:

```julia
julia> let T = Union{Tuple{T,T}, Tuple{T,T,T}} where T<:Number
           @show Tuple{Int,Int} <: T
           @show Tuple{Int,Float64} <: T
           @show Tuple{Int,Int,Int} <: T
           @show Tuple{Int,Int,Float64} <: T
       end;
Tuple{Int, Int} <: T = true
Tuple{Int, Float64} <: T = false
Tuple{Int, Int, Int} <: T = true
Tuple{Int, Int, Float64} <: T = false

```

but this doesn’t:

```julia
julia> struct Foo{X,Y} end
       let T = Union{Tuple{T,T}, Tuple{T,T,T}, Foo{T,T}} where T<:Number
           @show Tuple{Int,Int} <: T
           @show Tuple{Int,Float64} <: T
           @show Tuple{Int,Int,Int} <: T
           @show Tuple{Int,Int,Float64} <: T
           @show Foo{Int,Int} <: T
           @show Foo{Int,Float64} <: T
       end;
Tuple{Int, Int} <: T = true
Tuple{Int, Float64} <: T = true
Tuple{Int, Int, Int} <: T = true
Tuple{Int, Int, Float64} <: T = true
Foo{Int, Int} <: T = true
Foo{Int, Float64} <: T = false

```

---

<div class="post-metadata">

**Author:** ![Sevi](https://avatars.discourse-cdn.com/v4/letter/s/c67d28/32.png) [@Sevi](https://discourse.julialang.org/u/Sevi)\
**Post date:** [March 1, 2023, 8:26am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/14 "2023-03-01T08:26:15Z")

</div>

> [@uniment](#):
>
> The comparison of concrete types happens naturally when you call `isa` on an _instance_, which is of course concrete.

Yes, that’s what I meant, I think my wording wasn’t right. I just tried to think of the subsetting as “can I find an instance that would fall into one type, but not the other”.

> [@jinfreedom](#):
>
> which does not make any sense to me, as the first union should be a subset of the second union, shouldn’t it?

> [@uniment](#):
>
> Yes. Hence, why this seems like a bug. It’s some sort of interaction between covariant and invariant types.
> 
> To illustrate, this works correctly:

Ah, now I see. Thanks for spelling it out! It does seem pretty weird …

---

<div class="post-metadata">

**Author:** ![Sevi](https://avatars.discourse-cdn.com/v4/letter/s/c67d28/32.png) [@Sevi](https://discourse.julialang.org/u/Sevi)\
**Post date:** [March 1, 2023, 8:40am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/15 "2023-03-01T08:40:18Z")

</div>

> [@uniment](#):
>
> Yes. Hence, why this seems like a bug. It’s some sort of interaction between covariant and invariant types.

Not sure it’s a bug though. In the section on diagonal types and equality constraints of the manual you posted, it says

```julia
f(a::Array{T}, x::T, y::T) where {T} = ...

```

> In this case, `T` occurs in invariant position inside `Array{T}`. That means whatever type of array is passed unambiguously determines the value of `T` – we say `T` has an _equality constraint_ on it. Therefore in this case the diagonal rule is not really necessary, since the array determines `T` and we can then allow `x` and `y` to be of any subtypes of `T`. So variables that occur in invariant position are never considered diagonal. This choice of behavior is slightly controversial – some feel this definition should be written as

Consider this example which should match our discussion:

```julia
julia> Tuple{Number, Number} <: Tuple{T, T} where T <: Number
false

julia> Tuple{Number, Number} <: Union{Tuple{T, T}, Vector{T}} where T <: Number
true

```

As soon as we pack a parametric type in the set which allows us to identify `T` exactly (even as an abstract type), i.e. not a tuple type, the constraint on the tuple types having diagonal parameters is lifted.

> [@jinfreedom](#):
>
> which does not make any sense to me, as the first union should be a subset of the second union, shouldn’t it?

In this example we have then

```julia
julia> Tuple{Number, Number} <: (Union{Tuple{T,T}, Vector{T}} where T<:Number)
true

julia> Tuple{Number, Number} <: (Union{Tuple{T,T}, Vector} where T<:Number)
false

```

but also

```julia
julia> Vector{String} <: (Union{Tuple{T,T}, Vector{T}} where T<:Number)
false

julia> Vector{String} <: (Union{Tuple{T,T}, Vector} where T<:Number)
true

```

so they are not the same set (EDIT: nor subsets of each other) and _I think_ it works as intended (according to the manual).  
But it’s definitely a pretty subtle point! 😅

---

<div class="post-metadata">

**Author:** ![jinfreedom](https://avatars.discourse-cdn.com/v4/letter/j/c6cbf5/32.png) [@jinfreedom](https://discourse.julialang.org/u/jinfreedom)\
**Post date:** [March 1, 2023, 1:21pm UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/16 "2023-03-01T13:21:11Z")

</div>

> [@Sevi](#):
>
> As soon as we pack a parametric type in the set which allows us to identify `T` exactly (even as an abstract type), i.e. not a tuple type, the constraint on the tuple types having diagonal parameters is lifted.

This is THE point. Thanks @Sevi !

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [March 1, 2023, 3:54pm UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/17 "2023-03-01T15:54:00Z")

</div>

> [@Sevi](#):
>
> As soon as we pack a parametric type in the set which allows us to identify `T` exactly (even as an abstract type), i.e. not a tuple type, the constraint on the tuple types having diagonal parameters is lifted.

The point that this misses, however, is that although this behavior is appropriate when the outside container is a `Tuple`, it’s not appropriate when the outside container is a `Union`.

Namely, as part of a `Tuple`, the invariant type _must_ be packed in alongside the covariant `Tuple` types, and therefore can be expected to specify their type parameter. But as part of a `Union`, the invariant type may be missing (as is the case here), in which case it does no good to expect it to specify the parametric type for the other covariant types—how could it?

Thus, I maintain that this seems like a bug.

---

<div class="post-metadata">

**Author:** ![Sevi](https://avatars.discourse-cdn.com/v4/letter/s/c67d28/32.png) [@Sevi](https://discourse.julialang.org/u/Sevi)\
**Post date:** [March 1, 2023, 8:29pm UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/18 "2023-03-01T20:29:19Z")

</div>

I see your point (that the discussion in the docs is focused on tuple types, not on union types). And as I said, I’m not entirely sure if this is intended or not (but more and more, based on the discussion below).

What exactly is the behavior you would expect?

* * *

> [@uniment](#):
>
> But as part of a `Union`, the invariant type may be missing (as is the case here), in which case it does no good to expect it to specify the parametric type for the other covariant types—how could it?

I don’t get this. When we put the type parameter in the union and use the same parameter in all places, they better all be the same types over which we take the union. If we put no parameter that occurs in a tuple also in a invariant position (like `Union{Tuple{T, T}, Vector}`) within the union, the tuples are still restricted to the diagonal types and nothing unexpected happens.

Reading this type union

```julia
Union{Tuple{T,T}, Vector{T}} where T <: Number

```

I would always expect that `T` goes over all types which are subtypes of `Number`, and that there can be no `T` that appears in some of the `Vector`, but not in the `Tuple` and vice versa.

The only question in my mind is whether abstract types are included in the union or not.

- `Tuple{T,T}` might suggest that only concrete types `T <: Number` are included, because in isolation, `Tuple{T,T} where T<:Number` allows only concrete types
- but `Vector{T} where T<:Number` doesn’t exclude any abstract types for `T` in any other context (that I can think of right now)

I guess the question is, which behavior should take precedence over the other (the covariant parameters in the tuple types or the invariant ones). Reading the docs, it sounds to me like restricting the parameters for the tuples is treated as the exception, since it is required for matching function signatures in a meaningful way. But apart from that, the type parameters are always(?) invariant and unions range over all possible types of the parameter.

And more importantly, a (perhaps the?) basic property of unions should be that they grow as we add things.

With the current behavior

```julia
Vector{Number} <: Union{Tuple{T,T}, Vector{T}} where T <: Number

```

is true, which is what I would expect, since in any other situation `Vector{T} where T <: Number` allows abstract types for `T`. If adding `Vector{T}` to the union should enforce that `T` iterates over all subtypes of `Number` and is replaced in all three places where it occurs _without changing the behavior of the tuple types_ then `Vector{Number} <: Union{Tuple{T,T}, Vector{T}} where T <: Number` should be false.

I think this would also imply that @jinfreedom’s example relation

```julia
(Union{Tuple{T,T}, Vector{T}} where T <: Number) <: (Union{Tuple{T,T}, Vector} where T <: Number)

```

would be true. This looks reasonable to me, but at the same time, the following would then be false (e.g. `Number` would be contained in the left, but not in the right):

```julia
(Union{T, Vector{T}} where T <: Number) <: (Union{T, Vector{T}, Tuple{T, T}} where T <: Number)

```

Should adding a `Tuple` to an arbitrary type union make the whole union smaller than just leaving it away?

* * *

The only other option I see would be that the tuple types are treated as they behave in isolation (only including concrete/diagonal types) and the vector type as well (including also abstract types), but that is just

```julia
Union{Tuple{T,T}, Vector{S}} where {S<:Number,T <: Number}

```

tl;dr If `T` should be the same `T` in all places in the union, we should rather “widen” the tuple types instead of restricting the other types to ensure (semantically) that unions grow when things are added.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [March 2, 2023, 3:09am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/19 "2023-03-02T03:09:20Z")

</div>

> [@Sevi](#):
>
> What exactly is the behavior you would expect?

Simple. I would expect that all instances of `T` are the _same exact type_ (and therefore, `T` cannot be an abstract type if it’s a `Tuple` type parameter). This means I agree with this part of the [devdocs](https://docs.julialang.org/en/v1/devdocs/types/#Diagonal-types):

> slightly controversial – some feel this definition should be written as
> 
> ```julia
> f(a::Array{T}, x::S, y::S) where {T, S<:T} = ...
> 
> ```

I didn’t hold this view before—I had thought the current behavior was ok—but I changed my mind.

Examples:

This should be `(true, false, true, false, false, true, true)`:

```julia
julia> let T=Union{Tuple{T,T}, Vector{T}} where T <: Number
           Tuple{Int,Int} <: T,
           Tuple{Int,Float64} <: T,
           Union{Tuple{Int,Int}, Vector{Int}} <: T,
           Union{Tuple{Int,Float64}, Vector{Int}} <: T,
           Union{Tuple{Int,Float64}, Vector{Number}} <: T,
           Vector{Int} <: T,
           Vector{Number} <: T
       end
(true, true, true, true, true, true, true)

```

I previously believed it was ok for the 5th item should be `true`, but I changed my mind.

Meanwhile, this should be `(true, true)`:

```julia
julia> let T=Union{Tuple{T,T}, Vector} where T <: Number
           (Union{Tuple{T,T}, Vector{T}} where T <: Number) <: T
       end,
       let T=Union{T, Vector{T}, Tuple{T, T}} where T <: Number
           (Union{T, Vector{T}} where T <: Number) <: T
       end
(false, true)

```

The reason I changed my mind, is because I needed to in order for that `false` to be `true`. Once you make that decision, to be consistent and demand that `T` refer to the exact same type everywhere that `T` is valid, then all of these problems get cleaned up simultaneously, and the OP question would not have occurred.

---

<div class="post-metadata">

**Author:** ![Sevi](https://avatars.discourse-cdn.com/v4/letter/s/c67d28/32.png) [@Sevi](https://discourse.julialang.org/u/Sevi)\
**Post date:** [March 2, 2023, 7:18am UTC](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298/20 "2023-03-02T07:18:31Z")

</div>

> [@uniment](#):
>
> Simple. I would expect that all instances of `T` are the _same exact type_ (and therefore, `T` cannot be an abstract type if it’s a `Tuple` type parameter). This means I agree with this part of the [devdocs](https://docs.julialang.org/en/v1/devdocs/types/#Diagonal-types):

> [@uniment](#):
>
> Meanwhile, this should be `(true, true)`:
> 
> ```julia
> julia> let T=Union{Tuple{T,T}, Vector} where T <: Number
> (Union{Tuple{T,T}, Vector{T}} where T <: Number) <: T
> end,
> let T=Union{T, Vector{T}, Tuple{T, T}} where T <: Number
> (Union{T, Vector{T}} where T <: Number) <: T
> end
> (false, true)
> 
> ```

Isn’t that a contradiction?

If adding `Tuple` to the union restricts the parameter to being concrete, then this would turn into `(true, false)`. The way to make both `true` would be to restrict types parameters _always_ to range over concrete types.

In the current behavior and in your suggestion, adding one type will dictate what happens to the others in the union – personally, I think allowing abstract types in the tuple when the parameter appears also in an invariant position is the more sensible choice.

[Next page](https://discourse.julialang.org/t/strange-behavior-of-union-of-types/95298.md?page=2)
