Passing kwargs... can overwrite other keyword arguments

I recently got bitten by some behavior that was not immediately obvious to me (combined with a little sloppy coding from my side).

In my case I had a function passing on some kwargs from higher up, as well as adding some of their own. It turns out I had forgotten to remove a variable from the kwargs higher up in the chain, so when doing f(; a=4, kwargs...) the a also existed in kwargs and it overwrote the one I added leading to results I had a hard time understanding.

It doesn’t sound unreasonable that the second should be prioritized over the first if allowing this is intended, though since it does error for f(; a=4, a=8) it could be nice to be consistent here.

I realise that the error in f(; a=4; a=8) is a syntax error, so I assume we couldn’t actually check for these two cases in the same way. So maybe the current way of prioritizing is the best we can do unless we want to add a runtime check for this, which is maybe not warranted.

Just thought I would bring it up since my first reaction to it was that it seemed weird to allow this, even though I now think I understand why it might be like it is.

julia> f(;a=3) = @show a
f (generic function with 1 method)

julia> kwargs = (;a=4) 
(a = 4,)

julia> f(;kwargs..., a=8)
a = 8
8

julia> f(;a=8, kwargs...)
a = 4
4

julia> f(;a=4, a=8)
ERROR: syntax: keyword argument "a" repeated in call to "f" around REPL[8]:1
Stacktrace:
 [1] top-level scope
   @ REPL[8]:1

This is working as intended and documented:

The nature of keyword arguments makes it possible to specify the same argument more than once. For example, in the call plot(x, y; options..., width=2) it is possible that the options structure also contains a value for width . In such a case the rightmost occurrence takes precedence; in this example, width is certain to have the value 2 . However, explicitly specifying the same keyword argument multiple times, for example plot(x, y, width=2, width=3) , is not allowed and results in a syntax error.

I think it’s the right behavior: f(; a=4, a=8) is most probably an error, but f(; a=4, kwargs...) and f(; kwargs..., a=4) can both be very useful: with the first one you can let the caller override your value of a, and with the second one you can override what the caller passes.

I find this feature very useful…

3 Likes

The thing that made me post is probably most about the inconsistency. That I had strange behaviour was my error, and it just happened to be hidden by this feature which was that initially made me annoyed :sweat_smile:

I think it just feels a little ugly that one is allowed and the other not. I do agree that it can be useful, and maybe allowing both so that that f(;a=4,a=8) worked and resulted in a=8 should then have been my suggestion. But I can see how one would probably never want to purposely do that, so it seems reasonable to error on it to help users avoid simple mistakes.

I don’t think it should change. It is in the docs, and it seems logical and useful even if I can’t let go of the slight annoyance at the inconsistency :man_shrugging: