How does the methods function work in Julia?

The methods function returns the method table of a function as also mentioned here. I am looking for an explanation on how the function works.

Consider the following example in Julia 1.7:

julia> f(a::Int64,b::Int64=1,c::Float64=1.0) = (a+b+c)
f (generic function with 3 methods)

julia> g(a::Int64,b::Int64=1;c::Float64=1.0) = (a+b+c)
g (generic function with 2 methods)

julia> methods(f)
# 3 methods for generic function "f":
[1] f(a::Int64) in Main at REPL[1]:1
[2] f(a::Int64, b::Int64) in Main at REPL[1]:1
[3] f(a::Int64, b::Int64, c::Float64) in Main at REPL[1]:1

julia> methods(g)
# 2 methods for generic function "g":
[1] g(a::Int64) in Main at REPL[1]:1
[2] g(a::Int64, b::Int64; c) in Main at REPL[1]:1

julia> f(1,1.0)
ERROR: MethodError: no method matching f(::Int64, ::Float64)
Closest candidates are:
  f(::Int64) at REPL[1]:1
  f(::Int64, ::Int64) at REPL[1]:1
  f(::Int64, ::Int64, ::Float64) at REPL[1]:1
Stacktrace:
 [1] top-level scope
   @ REPL[4]:1

julia> g(1,c=1.0)
3.0

julia>

It is not quite clear to me why there is no method f(::Int64, ::Float64) (hence the error). I am also wondering why there is no error for g(1,c=1.0) given that g(::Int64, ::Float64) or g(::Int64, c) are not listed as valid methods for g.


Ah, so to be a bit technical this is really more accurately a question about how type annotations, dispatch, optional arguments, and keyword arguments work in Julia; the methods function just gives you some insight into that process, but it's not the methods function that makes those decisions. To answer your individual questions

It is not quite clear to me why there is no method f(::Int64, ::Float64) (hence the error).

There is no method for this because you you can only omit optional normal (non-keyword) arguments contiguously from the last normal (non-keyword) argument. Consider the following case:

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

julia> f(2,4)

If there were not a rule for this, the compiler would have no idea whether the 2 and 4 provided were supposed to be for a and b, or do I mean that actually I wanted the 2 to go to a and the 4 to go to d? or c? Or anything! This would be undecidable. So we have a rule, and the rule is that the first argument goes to a, the second to b, and the omitted ones are c and d. Even though you have specified defaults, you cannot omit middle arguments, you can only omit the last N optional arguments. This is just the rule, and it does not matter whether or not you have applied type annotations or not.

I am also wondering why there is no error for g(1,c=1.0) given that g(::Int64, ::Float64) or g(::Int64, c) are not listed as valid methods for g.

Firstly, there is no method for g(::Int64, ::Float64) or g(::Int64, c) because keyword arguments (in this example, c) do not participate in dispatch. There is no error for g(1,c=1.0) because when you write g(1,c=1.0), you the optional argument for b is falling back to its default, so you are actually calling g(1,1,c=1.0) When you write g(1,c=1.0), you have explicitly specified that the 1.0 is being assigned to c, so it cannot possibly be the value for b. The value for b has to fall back to it's default, 1.