julia icon indicating copy to clipboard operation
julia copied to clipboard

Disallow interleaving positional and keyword arguments

Open danielmatz opened this issue 5 years ago • 8 comments

This is a minor thing, but I was recently surprised by it. If I call a function with a keyword argument and then splat additional keyword arguments after it, the use of a ; is required. I was expecting Julia to understand that anything after the first keyword argument was also a keyword argument.

Here's a simple demonstration:

julia> foo(; x, y) = (x, y)
foo (generic function with 1 method)

julia> bar(; kw...) = foo(x=1, kw...)
bar (generic function with 1 method)

julia> bar(y=2)
ERROR: MethodError: no method matching foo(::Pair{Symbol,Int64}; x=1)
Closest candidates are:
  foo(; x, y) at REPL[1]:1
Stacktrace:
 [1] bar(; kw::Base.Iterators.Pairs{Symbol,Int64,Tuple{Symbol},NamedTuple{(:y,),Tuple{Int64}}}) at ./REPL[2]:1
 [2] top-level scope at REPL[3]:1

Instead, we must do:

julia> foo(; x, y) = (x, y)
foo (generic function with 1 method)

julia> bar(; kw...) = foo(x=1; kw...)
bar (generic function with 1 method)

julia> bar(y=2)
(1, 2)

danielmatz avatar Sep 15 '20 16:09 danielmatz

You can interleave positional and keyword arguments:

julia> f(x, y; z=2, w=3) = @show x y z w;

julia> f(w=3, 1, z=2, 3);
x = 1
y = 3
z = 2
w = 3

so in your example, you can do:

julia> g(args...; x=1) = @show args x
g (generic function with 1 method)

julia> h(; kw...) = g(x=1, kw...)
h (generic function with 1 method)

julia> h(y=2)
args = (:y => 2,)
x = 1

KristofferC avatar Sep 15 '20 16:09 KristofferC

I had no idea you could interleave positional and keyword arguments! That certainly does explain why my example works the way it does. Thanks for the explanation!

I do worry that this could be quite confusing for people. Especially with the new keyword argument feature where a is the same as a=a. For example, consider the following:

julia> foo(args...; kw...) = (args, kw)
foo (generic function with 1 method)

julia> function bar()
       a = 1
       foo(x=1, a)
       end
bar (generic function with 1 method)

julia> bar()
((1,), Base.Iterators.Pairs(:x => 1))

julia> function bar()
       a = 1
       foo(x=1; a)
       end
bar (generic function with 1 method)

julia> bar()
((), Base.Iterators.Pairs(:x => 1,:a => 1))

danielmatz avatar Sep 15 '20 16:09 danielmatz

I just reread the section of the docs that covers keyword arguments: https://docs.julialang.org/en/v1/manual/functions/#Keyword-Arguments. It does a good job of explaining when a ; is required and when it is optional. It doesn't discuss the fact that positional and keyword arguments can be interleaved. I'm not sure if it's worth mentioning that or not.

danielmatz avatar Sep 15 '20 16:09 danielmatz

You can interleave positional and keyword arguments

Yikes, I didn't know about that. I mean ok you can currently do it, but perhaps it should be a lowering error?

Regardless of that, there's a reason the ; is required: the kw is an object in its own right and splatting it into the positional arguments is meaningful (if unusual). As a somewhat realistic example, you can make a function taking keywords and calling the Dict(::Pair...) constructor quite easily with the following:

julia> makedict(; kw...) = Dict(kw...)
makedict (generic function with 1 method)

julia> makedict(x=1,y=2)
Dict{Symbol,Int64} with 2 entries:
  :y => 2
  :x => 1

c42f avatar Sep 16 '20 05:09 c42f

I agree interleaving positional and keyword arguments is perhaps a misfeature. We can consider changing that in 2.0, but before then it will have to stay this way. Maybe rename the issue to suggest the breaking change instead?

JeffBezanson avatar Sep 16 '20 19:09 JeffBezanson

I could be convinced otherwise, but I rather like this features—sometimes it's significantly clearer to put keyword arguments before positional ones.

StefanKarpinski avatar Sep 29 '20 15:09 StefanKarpinski

I like to be able to use sort(by=..., arr) but at first glance, it looks like an unintended feature. It would be helpful to have a mention in the docs about it and also if it's a misfeature, we should have a note in Style Guide about it.

prbzrg avatar May 15 '24 21:05 prbzrg

this issue should probably get some labels (or just closed as not-planned if that is the case)

adienes avatar May 16 '24 11:05 adienes