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

Additionally, if 2 methods are defined with identical signatures, per type-equal, then they will instead *by compared by order of addition, such that the later method is more specific than the earlier

I think that this by after * should be replaced by be.

That’s indeed a likely typo, but you’re taking things out of context again. That’s talking about a different specificity concept for internal method tables, not method dispatch. If you evaluate 2 methods with identical signatures, the recent one overwrites the previous one on the language level; how that happens internally are irrelevant to you. Generally speaking, the devdocs don’t specify the language, it describes parts of the implementation. I’ll hazard a guess that you’re searching by keywords like “specificity”, and you need to read and understand the surrounding context to avoid mixing up different topics.

1 Like

In this case the result is 5, because f(1,2) invokes the first method of f above.

This line is a bit unnecessary or confusing. Which first method above?

f(a,b) = a+2b

f(a,b) = a+2b
f(a) = f(a,2)
f() = f(1,2)

or f( )

julia> f(a=1,b=2) = a+2b
f (generic function with 3 methods)

julia> methods(f)
# 3 methods for generic function "f" from Main:
 [1] f()
     @ REPL[1]:1
 [2] f(a, b)
     @ REPL[1]:1
 [3] f(a)
     @ REPL[1]:1

In other words, optional arguments are tied to a function, not to any specific method of that function. It depends on the types of the optional arguments which method is invoked.

You mean optional arguments values will be available for all methods?

That refers to the manual text, so the first one of

f(a,b) = a+2b
f(a) = f(a,2)
f() = f(1,2)

The ordering returned by methods is arbitrary.

1 Like