# Strange MethodError ambiguity

I found a strange `MethodError` in my code. It is possible to reproduce it with the following simple code

`struct A{T} x::T end`
`fun(a::A{T}, b::T) where {T} = "same parametric type"`
`fun(a::T, b::S) where {T<:A,S} = "any parametric type"`
`fun(b::S, a::T) where {T<:A,S} = "any parametric type"`

Then, `fun(A(A(1)), A(1))` returns an ambiguity where I expected it to return the string `"same parametric type"`.

Does someone know if this is the expected behaviour? From my (certainly limited) understanding of the multiple dispatch, I do not see why this is ambiguous… (btw I use Julia 1.4.1)

EDIT: it seems that

`f1(a::A{T}, b::T) where {T} = "same parametric type"`
`f1(a::T, b::S) where {T<:A,S} = "any parametric type"`

does not yield methods ambiguity. However,

`f2(a::A{T}, b::T) where {T} = "same parametric type"`
`f2(b::S, a::T) where {T<:A,S} = "any parametric type"`

is enough to throw the `MethodError` ambiguity. Moreover, the `Possible fix`suggested by Julia is

`Possible fix, define f2(::S, ::T) where {T<:A, S<:(A{T} where T)}`

which to me seems superfluous.

Julia’s multiple dispatch mechanism works by selecting the most specific method given your arguments. The problem when calling `f2(A(A(1)), A(1))` is that neither methods are more specific than the other.
`A{T}` is more specific than `S` for the first argument `A(A(1))`, but `T where T<:A` is more specific for the second argument `A(1)`, hence the `MethodError` ambiguity.

Although, your second paragraph suggests that Julia only looks at the arguments “one at a time”; this was not my understanding… When looking at both the arguments, the choice of the method is obvious.

My understanding is that a method with argument type `A` is more specific than a function with argument type `B` if `A <: B`. For the two methods

``````f(a::A{T}, b::T) where {T} = "same parametric type"
f(b, a::A) = "any parametric type"
``````

the argument types are `Tuple{A{T},T} where T` and `Tuple{Any,A}`. It is clear that

``````julia> (Tuple{A{T},T} where T) >: Tuple{Any,A}
false
``````

because of types like `Tuple{Int,A{Float64}}` which is a subset of the latter but not of the former. Furthermore, we also have

``````julia> (Tuple{A{T},T} where T) <: Tuple{Any,A}
false
``````

because of types like `Tuple{A{Int},Int}` which is a subset of the former but not of the latter. Hence neither method is more specific.

2 Likes

Interesting. In fact I would expect your ordering to work as well…

It just seems that I misunderstood how the multiple dispatch works.

If

``````h(::Type{A{T}},::Type{T}) where {T} = true
h(::Type{S},::Type{T}) where {S,T<:A} = false
julia> h(A{A{Int}},A{Int})
ERROR: MethodError: h(::Type{A{A{Int64}}}, ::Type{A{Int64}}) is ambiguous. Candidates:
h(::Type{A{T}}, ::Type{T}) where T in Main at none:1
h(::Type{S}, ::Type{T}) where {S, T<:A} in Main at none:1
Possible fix, define
h(::Type{A{T}}, ::Type{T}) where T<:A
``````

I agree that `(Tuple{A{T},T} where T<:A) <: Tuple{Any,A}` and `(Tuple{A{T},T} where T<:A) <: (Tuple{A{T},T} where T)` should both be true.

However, observe that for `h(A{A{Int}},A{Int})` satisfies the definition `h(::Type{A{T}},::Type{T}) where {T}` two times : the first argument matches the expected type `A{T}` and the parameter of the first argument `T` which is the type of the second argument also matches.

Meanwhile, our second definition of `h` is only satisfied by a match with the second argument.

As a matter of fact, the fact that the possible fix given by Julia is to define

``````h(::Type{A{T}}, ::Type{T}) where T<:A
``````

tells me that Julia kind of already know what to do!

EDIT: clearly Julia has a straightforward “set understanding” of the situation and returns the error as it is only concerned with the fact that `Tuple{A{A{Int}},A{Int}}` lies in the intersection of `Tuple{Any,A}` and `Tuple{A{T},T} where T`.

It might seem that the first definition is tighter, but the method will accept any `T` you want to throw at it. The second method, however, specifically limits the type of the second argument to only subtypes of `A`. From this perspective, it’s unclear which method is more specific: do we want to prioritize the requirement that the first argument is parametrized with the same type, or that the second argument must be subtyping `A`?
Hence Julia suggests the fix: define a method that enforces both requirements, which will be more specific than both previously defined methods.

1 Like

Thanks for the discussion! I agree.

I guess it is now a point of view to add this “priority feature” and perhaps not having this flexibility is actually much better and prevent weird edge cases / forces clarity / etc…