Union vs. <: AbstractType

This is not really a question, because I know how to use them, but the notation still makes me confuse every time.

In summary, I feel that the notation Vector{T} where T <: AbstractType suggests that a single concrete type T is to be considered, while Union{T1,T2} suggests that both should be considered. The behavior is exactly the opposite:

julia> abstract type A end

julia> struct A1 <: A x end

julia> struct A2 <: A x end

julia> f(x::Vector{T}) where T <: A = 1
f (generic function with 1 method)

julia> g(x::Vector{Union{A1,A2}}) = 2
g (generic function with 1 method)

julia> x = [ A1(1), A2(1) ]
2-element Array{A,1}:
 A1(1)
 A2(1)

julia> f(x)
1

julia> g(x)
ERROR: MethodError: no method matching g(::Array{A,1})
Closest candidates are:
  g(::Array{Union{A1, A2},1}) at REPL[5]:1
Stacktrace:
 [1] top-level scope at REPL[8]:1

julia>

Perhaps understanding more the inner implementation of Union makes that more intuitive? Any didactic comment on this behavior is appreciated.

2 Likes

I think the confusion is not between Union vs abstract type.
but between Vector{Foo} and Vector{T} where T<:Foo

You want to write

g(x::Vector{T)  where T<:Union{A1,A2}} = 2

to get the behavour that relates to <:AbstractType.
though that still won’t actually match because its on homogenious Vector of just one of those types.
(but still that is the matching behavour)

This is because types parameters* like Bar{Foo} are about exactly that type. So if your array was specifically types as a Union, (or as a Abstract{A} then it would match.
(which you can do via Union{A1, A2}[ A1(1), A2(1) ], but that by default [ A1(1), A2(1) ] would be a Vector{A} because it promote to the abstract type; rather than to a Union)
But Bar{T} where T<:Foo is anything that is a particular subtype.

(*except in tuples)


Aside:
a shorthand for Vector{T} where T<:Foo is Vector{<:Foo}

3 Likes

More generally, this comes down to invariance of type parameters. I think the manual does a great job at explaining this, if you want a detailed explanation:

https://docs.julialang.org/en/v1/manual/types/#Parametric-Composite-Types

4 Likes

Yes, that helps a little. Still, from intuition I would guess that this case would be equivalent to T<:A if A1 and A2 are the only subtypes of A. But that at least gives a practical reason to differentiate both things.

I think the major confusion I have is with the word Union. Seems like it should be OneOf , Xor, something like that.

Seems like in the version of Julia @lmiq uses, a [ A1(1), A2(1) ] is already a more tight type (Array{A,1}). Not that this changes anything about your reasoning.

Yeah that was a mistake on my part.
Fixed
That is also the case on julia 1.0.5
TIL

Which does make another another aspect, of confusion.
That the type of [A1(1), A2(1)] is promotes to the common abstract type.
not to the Union of the types.

I have never made use of this,
In the rare occations that I am dealing with a hetrogeniously types array, I tend to be specific.
and here i would write either.
A[A1(1), A2(1)]
or
Union{A1, A2}[A1(1), A2(1)]

which repectively match
f1(::Vector{A}) (or the wider f2(::Vector{<:A})
and
g1(::Vector{Union{A1,A2}) (or the wider
g1(::Vector{<:Union{A1,A2}) or even f2 from above.)

1 Like

It is important to realize that Union{A1,A2} is a subtype of, but importantly not equivalent to A. This is quite important for a lot of aspects of type inference, since treating abstract types as just unions of a finite number of types would mean that a lot of precompiled methods would need to be invalidated and recompiled every time you subtype an abstract type. Since you can even consider Any an abstract type, this could in the worst case force Julia to recompile pretty much all generic functions every time you define a new type.

6 Likes

Ah, now I got it! (I knew it was worthwhile asking things here). My confusion was mixing the definition of the function with the type to which the vector is promoted when it is created!

My confusion got solved when you showed me this example:

x = Union{A1,A2}[A1(1),A2(2)]

and that of course works with the function g above.

Also, it makes sense that A is a supertype of the Union type, and now it makes sense that the vector that was promoted to A[A1(1),A2(2)] does not work on that same g.

Thank you very much.

2 Likes