Methods of a function that has both optional positional argument and keyword argument

I know keyword arguments don’t invoke multiple dispatch. So methods with the same positional arguments except keyword arguments can overload each other:

julia> f(a) = a
f (generic function with 1 method)

julia> methods(f)
# 1 method for generic function "f":
[1] f(a) in Main at REPL[1]:1

julia> f(a; b) = a+b
f (generic function with 1 method)

julia> methods(f)
# 1 method for generic function "f":
[1] f(a; b) in Main at REPL[2]:1

However, I found that when the defined function has optional positional arguments, only the method with no positional arguments omitted keeps the keyword argument:

julia> h(a, b=1, c=2; d) = a+b+c+d
h (generic function with 3 methods)

julia> methods(h)
# 3 methods for generic function "h":
[1] h(a) in Main at REPL[6]:1 # Why not h(a; d)?
[2] h(a, b) in Main at REPL[6]:1 # Why not h(a, b; d)?
[3] h(a, b, c; d) in Main at REPL[6]:1

Is there a particular reason for this design? I thought keeping the keyword argument in the method definition is a more intuitive decision but maybe there’s some good explanation I don’t know. Thanks a lot!

1 Like

I think you might find some explanation in the devdocs, specifically the section on keyword arguments: Julia Functions · The Julia Language

Tangentially, you might be interested in my recent PR #44434 which hides this internal design in the display of methods(h).

1 Like

Thanks! I’ll look into them.

I think there’s a bug either with the print info or the method dispatch:

julia> f1(;k=1) = k
f1 (generic function with 1 method)

julia> methods(f1)
# 1 method for generic function "f1":
[1] f1(; k) in Main at REPL[24]:1

julia> f2() = 0
f2 (generic function with 1 method)

julia> methods(f2)
# 1 method for generic function "f2":
[1] f2() in Main at REPL[26]:1

julia> f3(a=0;k=1) = a+k
f3 (generic function with 2 methods)

julia> methods(f3)
# 2 methods for generic function "f3":
[1] f3() in Main at REPL[28]:1
[2] f3(a; k) in Main at REPL[28]:1

julia> f3()
1

julia> f3(k=3)
3

In the print info, it shows that the method of f3 which has no positional arguments should also not accept the keyword argument k, but in reality, it can accept it.

Then if I overload the method of f(), I can’t really eliminate the keyword argument:

julia> f3() = NaN
f3 (generic function with 2 methods)

julia> f3()
NaN

julia> f3(k=3)
3

Does this mean there’s no way to remove the keyword arguments from a predefined function through overloading?

There is no bug really, it’s just the same issue as in your first message, which boils down to the fact that the printing of methods(f) is misleading when f has keyword and optional arguments. You can have a look at methods(methods(f3).mt.kwsorter) if you’re interested in checking which methods exist and will actually be called when doing a function call of f3 with a keyword argument (but having a read of the devdocs I mentioned is probably necessary to understand what this is all about).

To answer the second question, there is indeed no way (that I can think of) to remove a keyword argument. Note that there is no way to remove a positional argument either actually, and it comes from the fact that there is no way to remove a method, period. But you can replace a method by defining another with the same argument types and keyword argument names: that’s not really overloading but more shadowing I’d say. And you can of course add other methods to tweak the behavior depending on the number and types of the arguments and the names of the keyword arguments, for instance by doing f3(; k) = k-12 or f3(a; k) = 2a-k. That’s regular multiple dispatch.

Issue #9498 might interest you regarding all this: as you can see there are some known trick cases relating to method definitions with keyword arguments.

Thanks for the reply again. The issue threads you provided (including the previous one) are all very helpful! The print info for methods can definitely be improved. As for the dispatch when involving optional keyword arguments, It seems that people have been discussing this bizarre consequence, if not considered a bug, for quite a while. In my opinion, the methods that have the exact same positional arguments except the optional keyword argument should be able to shadow each other, not just one way. So I hope the inconsistency shown in #9498 can be resolved in the near future. Personally, I feel like missing the multiple dispatch support for keyword arguments alone is what really makes the whole situation a bit messy.