function do_work(first::Number, second::Number)
println("Calling function where the parameters are numbers.")
println("Calling method where the first parameter is a number,
and the second is an AbstractFloat")
In the documentation, we can see the hierarchy for Int32 and Float32:
Number > Real > Integer > Signed > Int32
Number > Real > AbstractFloat > Float32
Do type annotations help Julia to determine which method to call?
Not, I suspect, in the way you’re thinking. Values in Julia have types, variables are just labels attached to those values.
With that said, type declarations do have an effect in that they implicitly try to convert the right-hand side to the specified type:
julia> function f()
x::Float64 = 1
f (generic function with 1 method)
typeof(x) = Float64
What happened here is that the ::Float64 declaration caused Julia to insert a call to convert(Float64,...) which does actually produce a value of type Float64. You can see this in the @code_lowered output:
The most specific method definition matching the number and types of the arguments will be executed when the function is applied.
When checking for the type of arguments, I always assumed Julia will start with what it knows:
We can’t use length to determine what method to call, because both methods require two arguments.
The first method call to do_work() was given an Int32 as the first argument.
None of the methods have Int32 as the first argument, so we move up to Signed, Integer, etc… until it hits the final type, Number. Both methods take Number, so it moves to the second argument, Float32, no method takes a Float32, moving up, we check if any method takes AbstractFloat, which the second method does, so that method is called.
In other words, how does Julia match Float32 with AbstractFloat? Does it move up the hierarchy until it finds the closest match?
Internally, there’s a do_work method table that is sorted by specificity, so any do_work dispatch only needs to provide the call signature and performs an O(n) search through the method table. The first method to match (and therefore the most specific) is do_work(::Number, ::AbstractFloat), so Julia simply specializes on that one.
But you’re not entirely wrong! Sorting methods by specificity and matching the call signatures both depend on an efficient subtyping algorithm, which you can peruse yourself if you’re well-versed with C. To juggle with Unions and UnionAlls, invariant parametric types and covariant tuple types, it is of course a lot more complicated than only traversing the type hierarchy, but a part of the process indeed does just that to incorporate subtyping relations declared by the user.