Specificity
Let’s define something like this:
"""
strict_subtype(l::Type, r::Type)::Bool
Predicate, tell whether `l` is a strict subtype of `r`.
"""
strict_subtype(l::Type, r::Type) = (l <: r) && !(r <: l)
The rule with Julia’s method specificity is that strict_subtype(l, r)
implies that l
is more specific than r
(with additional rules for breaking some ties that can arise). As briefly mentioned here:
There’s also a request for more docs on the specificity on the issue tracker:
Base.morespecific
is a predicate useful for playing around with type/method specificity in the REPL:
Ambiguity
A MethodError
pointing out the ambiguity gets thrown when there’s no most specific method for a call. This is useful to prevent programmer error, as it’s better to fail loudly than silently proceed in an unexpected manner.
It would be possible, in a future Julia release, to eliminate any specific MethodError
that currently arises due to ambiguity by defining-out the ambiguity, giving some of the methods greater specificity. Indeed, @Lilith proposes to eliminate almost all MethodError
s that currently arise due to ambiguity:
That said, I think that introducing arbitrary rules just to make the specificity a total order (where any two elements are comparable) seems like a horrid idea. The ambiguities (from the programmers’ perspectives) wouldn’t actually be resolved, rather they would just be masked by defining them out. While it would make Julia a slightly more powerful language, because some programs that now throw MethodError
s would become valid; it could, I think, lead to frustrating debugging experiences due to unintuitive specificity rules, and to difficult-to-fix interface design issues all across the ecosystem. Defining-out an ambiguity “fixes” the issue from the language’s perspective, but it would still leave the issue where there’s, for example, two packages, and the authors of both packages expect their method to be more specific. That issue would just be hidden.
The specific example
Regarding your specific example, neither method is clearly more specific:
- for the first argument position:
Int
is, as a strict subtype, more specific than Union{Int, Float64}
- for the second or third argument position:
AbstractArray
is, as a strict subtype, more specific than Any
In the REPL:
julia> a1 = Union{Int, Float64}
Union{Float64, Int64}
julia> b1 = Int
Int64
julia> a2 = AbstractArray
AbstractArray
julia> b2 = Any
Any
julia> strict_subtype(b1, a1) && Base.morespecific(b1, a1)
true
julia> strict_subtype(a2, b2) && Base.morespecific(a2, b2)
true
So (potentially after some additional undocumented tie breaking gives up) the two signatures end up being incomparable, with neither of them the more specific one:
julia> a = Tuple{Union{Int, Float64}, AbstractArray, AbstractArray}
Tuple{Union{Float64, Int64}, AbstractArray, AbstractArray}
julia> b = Tuple{Int, Any, Any}
Tuple{Int64, Any, Any}
julia> Base.morespecific(a, b)
false
julia> Base.morespecific(b, a)
false