Method specificity/priority rules

Please explain methods priority or specificity in different cases. Where in Julia documentation is it explained? The following line and example in documentation are contradictory.

The order in which the methods are defined does not matter and neither is more specific than the other.

while here in Methods · The Julia Language

julia> same_type(x::T, y::T) where {T} = true
same_type (generic function with 1 method)

julia> same_type(x,y) = false
same_type (generic function with 2 methods)

the first method is specific or preferred. Why not following code gives false as output if no method is specific or has priority?

julia> same_type(1, 2)

I found this post but it explain different case. :backhand_index_pointing_down:

A method can be more specific than another, but when that’s not true, they risk a call throwing an ambiguity MethodError. You’re taking that quote out of a context where ambiguous methods were defined. I’ll save everyone else a click for an accurate quote:

Here the call g(2.0, 3.0) could be handled by either the g(::Float64, ::Any) or the g(::Any, ::Float64) method. The order in which the methods are defined does not matter and neither is more specific than the other. In such cases, Julia raises a MethodError rather than arbitrarily picking a method.

2 Likes
julia> same_type(x::T, y::T) where {T} = true
same_type (generic function with 1 method)

julia> same_type(x,y) = false
same_type (generic function with 2 methods)

Why first method is preferred?

Such definitions correspond to methods whose type signatures are UnionAll types.
is this line referring to the second method or both?

Your two methods for function same_type have a different signature:

  • The first one has signature Tuple{typeof(same_type), T, T} where T
  • The second one has signature Tuple{typeof(same_type), Any, Any}
(here is a programmatic way of getting these method signatures)

The list of defined methods can be accessed with methods:

julia> methods(same_type)
# 2 methods for generic function "same_type" from Main:
 [1] same_type(x::T, y::T) where T
     @ REPL[18]:1
 [2] same_type(x, y)
     @ REPL[23]:1

To get the signature for each method, ask for the sig field:

julia> methods(same_type)[1].sig
Tuple{typeof(same_type), T, T} where T

julia> methods(same_type)[2].sig
Tuple{typeof(same_type), Any, Any}

When a function is called, for example when you call same_type(1, 2), the signature of your call will be compared to the signatures of all the methods of that function. The signature of your call is Tuple{typeof(same_type), Int, Int} in this particular case and it is a subtype of both method signatures:

julia> Tuple{typeof(same_type), Int, Int} <: Tuple{typeof(same_type), T, T} where T
true

julia> Tuple{typeof(same_type), Int, Int} <: Tuple{typeof(same_type), Any, Any}
true

so both methods are eligible. But the first one is more specific than the second one, because the signature of the first method is a subtype of the signature of the second method. Indeed:

julia> (Tuple{typeof(same_type), T, T} where T) <: Tuple{typeof(same_type), Any, Any}
true

while the converse is not true (otherwise the two signatures would be the same):

julia> Tuple{typeof(same_type), Any, Any} <: Tuple{typeof(same_type), T, T} where T
false

and thus, the first method is called and not the second.

The intuition is explained in Methods · The Julia Language and the nitty-gritty details of subtyping can be found in More about types · The Julia Language. In particular, the last subsection goes into more details about what is “method specificity”.
Best is probably to watch Jeff Bezanson explaining the rules though. I highly recommend https://www.youtube.com/live/TPuJsgyu87U?si=Nw_1STjfpEPQS27w&t=985 (the entire talk is great, the section starting at 16’25 is probably what you are most interested in for this particular question).

7 Likes

Another call proving the subtyping is strict, so one method is more specific than instead of overwriting the other: same_type(1, 2.0). Tuple{typeof(same_type), Int, Float64} does not subtype Tuple{typeof(same_type), T, T} where T, only Tuple{typeof(same_type), Any, Any}.

EDIT: typo was fixed, this comment here is irrelevant.

I’m not sure I understand: Tuple{typeof(same_type), Int, Int} does subtype Tuple{typeof(same_type), T, T} where T. That’s actually the first subtyping example I put in the previous answer.
That comes from the fact that Tuple types are covariant in their parameters (it won’t work if you replace Tuple by another type). Moreover, Int is a concrete type so it’s valid for the diagonal rule (it won’t work if you replace Int by Union{Int,Float64} for example).~

Sorry, I made a typo, it didn’t fit the call. Thanks for catching it.

1 Like