# Union type confusion

Consider an abstract type with a parameter `T` and its two concrete types:

``````abstract type AbstractMyType{T} end
struct MyType1{T} <: AbstractMyType{T} end
struct MyType2{T} <: AbstractMyType{T} end
``````

Construct a vector of instances of the two concrete types with the same parameter `T = Int`:

``````julia> v = [MyType1{Int}(), MyType2{Int}()]
2-element Vector{AbstractMyType{Int64}}:
MyType1{Int64}()
MyType2{Int64}()
``````

The result is a vector of the abstract type.

Now, here is a thing that I don’t understand:

``````julia> v isa AbstractVector{AbstractMyType{T}} where {T<:Number}
true

julia> v isa AbstractVector{AbstractMyType{<:Number}}
false
``````

I thought `AbstractMyType{<:Number}` was a shorthand of `AbstractMyType{T} where {T<:Number}`. Why do they produce the different results?

It is:

``````julia> v isa AbstractVector{AbstractMyType{T} where T <: Number}
false
``````

The point is that

``````AbstractVector{AbstractMyType{T} where T <: Number}
``````

is not the same as

``````AbstractVector{AbstractMyType{T}} where T <: Number
``````

The second one subsumes all `AbstractVector`s whose element type is some type of the form `AbstractMyType{T}` where `T` is a subtype of `Number`. The first one applies only to `AbstractVector`s whose element type is exactly `AbstractMyType{<:Number}`.

4 Likes

Could you give an example vector of this type? Not sure how to construct a vector whose element type is exactly `AbstractMyType{<:Number}`.

``````julia> v = AbstractMyType{<:Number}[ MyType1{Int}(), MyType2{Number}(), MyType2{Real}() ]
3-element Vector{AbstractMyType{<:Number}}:
MyType1{Int64}()
MyType2{Number}()
MyType2{Real}()
``````

I don’t think Julia’s array literals will divine out such an eltype, but you can explicitly request it:

``````julia> AbstractMyType{<:Number}[MyType2{Int}(), MyType1{Float64}()]
2-element Vector{AbstractMyType{<:Number}}:
MyType2{Int64}()
MyType1{Float64}()
``````

But more to your original point, you can add another where clause to say that it can match any subtype of `AbstractMyType` as long as its parameter is `<: Number`.

``````julia> v = [MyType1{Int}(), MyType2{Int}()];

julia> v isa AbstractVector{<:AbstractMyType{<:Number}}
true
``````
3 Likes

A good point, but I would like to note that this also includes types like `Vector{MyType{Int}}` whose eltype is the concrete `MyType{Int}`, whereas `AbstractVector{AbstractMyType{T}} where {T<:Number}` excludes such types. Therefore, the two type specifications are different.

There are cases where this difference matters. For example, I am actually trying to specify the type of `v` in a function signature as

``````function myfun(v::AbstractVector{AbstractMyType{T}}) where {T<:Number}
``````

in order to make sure that the `eltype` of `v` is always the abstract `AbstractMyType`, , because `myfun` is expected to take aways a vector of abstract-type elements.

Why would you want to do that? Coming from generic programming a function taking a vector of `AbstractMyType`s could only do some reasonable things with its inputs, such as iterating over the vector and calling some methods on some elements – which would need to work for `AbstractMyType`s. In any case, the function should probably also work with vectors holding some concrete subtype such as `MyType1`.
In OOP this idea is known as the Liskov substitution principle and the related quote by Jon Postel:

be conservative in what you do, be liberal in what you accept from others

1 Like