I’m encountering quite an headscratcher, I’ve given it quite some time but I couldn’t crack. I am struggling to understand why a tuple of two types is not being recognized as such, while all the intermediate steps are successfull.
Here is a MWE
abstract type A end
abstract type B end
abstract type C{TA<:A, TB<:B} end
struct a <: A end
struct b <: B end
struct c1{TA <: A, TB <: B} <: C{TA, TB}
ta::TA
tb::TB
e::Int
end
struct c2{TA<: A, TB <: B} <: C{TA, TB}
ta::TA
tb::TB
e::Float
end
e1 = c1(a(), b(), 1)
e2 = c2(a(), b(), 1.0)
e1 isa TC where {TA <: A, TB <: B, TC <: C{TA, TB}}
# -> true
e2 isa TC where {TA <: A, TB <: B, TC <: C{TA, TB}}
# -> true
# great!, then i can do
(e1, e2) isa Tuple{Vararg{<: C{TA, TB}}} where {TA <: A, TB <: B}
# -> true
# therefore also
(e1, e2) isa Tuple{Vararg{TC}} where {TA <: A, TB <: B, TC <: C{TA, TB}}
# -> false (???) huh?
#but
[e1, e2] isa Vector{TC} where {TA <: A, TB <: B, TC <: C{TA, TB}}
# -> true
I’ve read the manual on types multiple times but for the life of me I couldn’t understand what is going on here. Why is that second to last test false??
It turns out that being able to dispatch on whether two values have the same type is very useful (this is used by the promotion system for example), so we have multiple reasons to want a different interpretation of Tuple{T,T} where T. To make this work we add the following rule to subtyping: if a variable occurs more than once in covariant position, it is restricted to ranging over only concrete types. (“Covariant position” means that only Tuple and Union types occur between an occurrence of a variable and the UnionAll type that introduces it.) Such variables are called “diagonal variables” or “concrete variables”. More about types · The Julia Language
It’s not about Vararg, but about using the the same type variable in more than one place (which I think Vararg implicitly does):
julia> (e1,e2) isa Tuple{TC,TC} where {TC <: C}
false
julia> (e1,e2) isa Tuple{TC, <:TC} where {TC <: C}
true
julia> (e1,e2) isa Tuple{TC,TD} where {TC <: C, TD <: C}
true
Thanks a million!
Didn’t think of checking the “internals” section of the manual.
After reading that section I must admit I didn’t completely understand it all
but I understood enough to come up with
(e1, e2) isa Tuple{Vararg{TC} where TC} where {TA <: A, TB <: B, TC <: C{TA, TB}}
# -> true
hoping that this Vararg{TC} where TC doesn’t introduce any extra edge case!
If I understood this correctly that extra where TC is making all the TCs in the Vararg bound to a single variable in covariant position which makes it possible to range also over non concrete types, such as C{TA <: A, TB <: B}, is this correct?