While I have no explanation to give you (and hopefully someone more knowledgeable will chime in to give you one), here are some thoughts about this topic.
1. I think it is correct to say that a::T
(in the context of typeassert) is successful if and only if a isa T
. I also think it is true in the context of multiple dispatch.
Like you, I used to think that:
\quad a isa T
\Leftrightarrow typeof(a) <: T
and I still think this holds for most values a
, but there are exceptions and you just found one:
julia> Int isa Type{Int}
true
julia> typeof(Int)
DataType
julia> DataType <: Type{Int}
false
julia> Type{Int} <: DataType
true
I.e. for most values a
, typeof(a)
is the “smallest” type T
for which a isa T
is true. But since all types share the same type (DataType
) and it is useful to dispatch on the value of types, the “pseudo-type” (or singleton type) Type{T}
has been introduced for the purposes of dispatch, and it does not follow this rule.
2. An other thing that might be relevant here is the variance of tuples: while it is true that parametric types are invariant
julia> Vector{Int} <: Vector{Number}
false # although Int <: Number
tuples are a bit exceptional in that they are covariant:
julia> Tuple{Int, Int} <: Tuple{Int, Number}
true
Again, I think I recall someone saying here that this is because tuples are used internally to represent arguments in method calls, and this behavior simplified the implementation of multiple dispatch.
So I guess it all boils down to the question of knowing exactly how these two exceptions combine. If we think first about the covariance of tuple types, then we get your interpretation:
\quad (4, Int) isa Tuple{Int, Type{Int}}
= 4 isa Int && Int isa Type{Int}
= true
However, we could also think first about pseudo-types and say, for example: x isa T
if typeof(x) <: T
or (x
is a type and T == Type{x}
). Such an interpretation is widely different in this case:
\quad (4, Int) isa Tuple{Int, Type{Int}}
= typeof((4, Int)) <: Tuple{Int, Type{Int}}
(the exception does not apply)
= Tuple{Int, DataType} <: Tuple{Int, Type{Int}}
= Int <: Int && DataType <: Type{Int}
(covariance of tuple types)
= false
I’m fully aware that I did not give any real answer here; I just wanted to share my thoughts. In conclusion, I would rather phrase the question like this: if both Type{T}
and tuple type covariance are features that were introduced to help with multiple dispatch, how come the dispatch on two arguments does not work exactly in the same way as the dispatch on a tuple of arguments?