I think my mental model of Type{Union{A,B}} is incorrect because I truly do not understand the following behavior:
julia> struct Foo end
julia> Foo isa (Type{Union{Foo,T}} where {T<:Base.IEEEFloat})
true
julia> Float32 isa (Type{Union{Foo,T}} where {T<:Base.IEEEFloat})
false
julia> Union{Foo,Float32} isa (Type{Union{Foo,T}} where {T<:Base.IEEEFloat})
true
Does anybody know what Type{Union{A,B}} where {B<:SuperB} stands for?
This is specifically cases where A is NOT a member of SuperB and hence I would not expect any unbound type parameters, because A by itself should not be a subtype of Type{Union{A,B}}.
It might make sense if this was expanded to Type{<:Union{A,B}}. But then I don’t understand why the union type is a member.
If this only matched Union{A,<:SuperB} explicitly, then I don’t understand why A matches.
Here’s a breakdown to explain each of your examples:
julia> struct Foo end
julia> S = Union{Foo, T} where {T<:Base.IEEEFloat}
Union{Foo, T} where T<:Union{Float16, Float32, Float64}
julia> T = Type{S{T}} where {T<:Base.IEEEFloat}
Type{Union{Foo, T}} where T<:Union{Float16, Float32, Float64}
julia> Foo isa T # the first query in the OP, `true` because the following is `true`:
true
julia> Type{Foo} == T{Union{}} # `true` because the following is `true`:
true
julia> Foo == S{Union{}}
true
julia> Float32 isa T # the second query in the OP, `false` because there is no `X` such that `Type{Float32} == T{X}`, which is because there is no `X` such that `Float32 == S{X}`
false
julia> Union{Foo, Float32} isa T # the third query in the OP, `true` because the following is `true`:
true
julia> Type{Union{Foo, Float32}} == T{Float32} # `true` because the following is `true`:
true
julia> Union{Foo, Float32} == S{Float32}
true
what do you want when you say “more efficient”? Perhaps you’d be satisfied by something like this:
let f(@nospecialize x::Type) = Type{Union{Foo, x}}
types = map(f, (Float16, Float32, Float64))
Union{types...}
end
Why do you want to use UnionAll (“a where clause”)? “Compression” (so to speak) of some Union with UnionAll will almost always, except in trivial cases, I think, produce a strict superset. I don’t know if that’s what you want. Could you say more about the subtyping (or other) properties of the type you’re looking for.
All I meant by “efficient” was if there was any way of repositioning the where clause that would generate Union{Type{Union{Foo,Float16}},... without me needing to write it out. But yeah seems there is not. I guess the difficulty is due the special behavior of Union!
function tangent_type(::Type{Union{NoFData,Array{T,N}}}, ::Type{NoRData}) where {T<:Base.IEEEFloat,N}
return Union{NoTangent,tangent_type(Array{T,N})}
end
I guess this might not be possible without also matching NoFData
Edit 1: Actually maybe it’s fine?? I guess isa injects a Union{} into all UnionAll but method dispatch does not?
Which we can do for Base.IEEEFloat (because it’s an explicit union that we can loop through), but we seemingly can’t do for abstract types like AbstractFloat without an ambiguous form
foo(::Type{Union{Foo,B}}) where {B<:Base.IEEEFloat} = B
which also matches Type{A}:
julia> struct Foo end
julia> foo(::Type{Union{Foo,B}}) where {B<:Base.IEEEFloat} = B
foo (generic function with 1 method)
julia> foo(Foo)
Union{}
So the solution seems to be to make sure there is also a method for
foo(::Type{Foo}) = Foo
which disambiguates that case. And then I think we are fine to use the unbounded form without issue