Thanks for mentioning Base.morespecific! Unfortunately, it seems to be a non-public function (as of 1.12). If I try to use it in 1.11, I will even get the following error:
julia> Base.morespecific(methods(f1)...)
ERROR: TypeError: in typeassert, expected Type, got a value of type Method
So, I’m not sure if there’s a proper way to determine whether a method is more specific than another.
Ironically, I thought the mental model for determining method ambiguity was well established by the language, but upon checking the official documentation, I found only an example without a formally defined criterion for what is deemed a “unique most specific method.” There are some practical suggestions on method design to avoid ambiguities, but still no formal definition of a “method ambiguity”. I can only imagine how beginners in Julia can feel confused or develop their own “understanding” regarding method ambiguity.
Since there is no formal definition, I shall share my understanding of what method ambiguity is. Please point out any “misconception” you think I might have.
For each method corresponding to the same function name (inspected by nameof, or its type if it is acutally a callable object of Function), a set of all possible combinations of argument types (in the form of a Tuple of Type) that are covariantly subtyping the argument types specified by that method forms a “method specificity set”. For example, for method f1(argL::T, argR::T) where {T}, its “method specificity set” is
M = {(T, T) | T }
where T iterates through all the possible concrete types.
When a function is called, a tuple of argument types argType is formed. For example, for foo(a::T1, b::T2), argType = (T1, T2). When argType belongs to more than one method specificity set:
MS = {M_i | M_i is a valid “method specificity set”} and |MS| > 1
but MS cannot form a strict chain of inclusion (i.e., M_i ⊊ M_j ⊊ …).
This is my mental model of how “method ambiguity” is defined, which is also consistent with the example in the official documentation, where the term “intersection” is used to indicate the “cause” of “method ambiguity”:
You can avoid method ambiguities by specifying an appropriate method for the intersection case
However, if that is the case, the example I showed in OP directly violates this criterion, as the method specificity set of one method is not a proper subset of another. Therefore, I think in principle, f1([1], [1]) should have thrown a MethodError.