julia> f() = ()
f (generic function with 1 method)
julia> f(; a = 1, b = 2) = (a, b)
f (generic function with 1 method)
julia> methods(f) # NOTE: 1 method
# 1 method for generic function "f":
[1] f(; a, b) in Main at REPL[141]:1
julia> f()
(1, 2)
How could the language distinguish the first one from the second called without any keyword arguments?
I understand how your example with only optional keyword arguments is indistinguishable from no arguments.
But I expect the language to distinguish between a method that accepts zero arguments and a method that accepts two mandatory keyword arguments.
The language can distinguish between these methods, but only if they are defined in a special sequence. It’s confusing why methods still displays the same output, even though the method was either modified to accept different arguments or a new method was added. It would be helpful for methods to show whether the keyword arguments are optional or mandatory.
# ------ working sequence ----------
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[1]:1
julia> f() = 5
f (generic function with 1 method)
# "overwritten" method looks the same as before, but now also supports zero arguments.
julia> methods(f)
# 1 method for generic function "f":
[1] f(; a, b) in Main at REPL[5]:1
# support for no-arguments and mandatory keyword arguments
julia> f()
5
julia> f(a=1, b=2)
3
# ------------ failing sequence ----------------
julia> g() = 5
g (generic function with 1 method)
julia> g(; a, b) = a + b
g (generic function with 1 method)
# adding support for mandatory keyword arguments afterwards breaks support for no arguments
julia> g()
ERROR: UndefKeywordError: keyword argument a not assigned
Stacktrace:
[1] g() at ./REPL[10]:1
[2] top-level scope at REPL[11]:1
I like to use mandatory keyword arguments instead of positional arguments to avoid bugs when calling constructors of large structs. It’s easy to mess-up the sequence of arguments, and there are no obvious errors if the swapped arguments share the same type. This also helps catch any bugs after adding/removing/reorganizing fields of a struct.
Are there other recommendations for avoiding these types of bugs?