Trying to figure out parametric dispach


The following code:

abstract type XX{A <: Real} end

function makexx(T::Type{<:XX})

struct X1{A} <: XX{A}
struct X2{A} <: XX{A} end
X1() = makexx(X1)

Crashes with:

MethodError: no method matching makexx(::Type{X1})

However, if I change the signature of XX to:

abstract type XX{A} end

works fine. Why does the first version doesn’t?

Interesting case. Note that this works when using the XX{A<:Real} pattern:

julia> makexx(X1{Float64})

but I don’t know why it fails with X1 only.

My guess is that when you call the function with makexx(X1) you are implicitly doing makexx(X1{Any}), and there is no method in that case to dispatch, like this:

julia> makexx(X1{Any})
ERROR: TypeError: in XX, in A, expected A<:Real, got Type{Any}
 [1] top-level scope
   @ REPL[7]:1

but with a confusing error message, because you only get the type error for an anonymous type.

Note that this also solves the problem, by removing the possibility of interpreting X1 as something which is not a subtype of XX:

julia> abstract type XX{A <: Real} end

julia> struct X1{A<:Real} <: XX{A} x::A end  # note the <: Real here

julia> makexx(T::Type{<:XX}) = 1
makexx (generic function with 1 method)

julia> makexx(X1)

That is, the problem arises when it is possible to have a X1 which is not a subtype of XX{A<:Real}. Then the function signature gets confused. Maybe it is possible to provide a better error message there?

1 Like

To add to @lmiq’s anwser: This is an open issue (#37901). The problem is

julia> X1 <: XX

Basically, XX is equal to XX{<:Real} but X1 is equal to X1{<:Any}. So X1 also includes types like X1{String} (even though such a type cannot be instantiated), which is not a subtype of XX.

julia> X1 <: XX{<:Any}

julia> X1{<:Real} <: XX

Like @lmiq said, the easiest solution would be to define

struct X1{A<:Real} <: XX{A}
struct X2{A<:Real} <: XX{A} end

(with A<:Real instead of just A). In that case you have X1 <: XX so that makexx(X1) will work.

1 Like