I feel like I have seen this proposal before, but I can’t find it now and I don’t remember the resolution, so sorry if this is repetitive.
I think it would be very nice if single word arguments passed to a function after a semi-colon were interpreted as keyword arguments where the variable name is the keyword and the value is the value. For example:
f( ; kw) = kw
kw = 2
f(kw=kw) #returns 2
f(; kw) #throws a syntax error, but should it?
a = 2
f(a=a) #throws a MethodError
f(; a) #throws a syntax error, but maybe it should throw a MethodError?
Given that the current behavior is to throw an error, adding this would not break anything. It would be a big QOL improvement to avoid typing x=x, y=y, z=z over and over.
I’ve been using a macro get this syntax, but its not very elegant because only way I figured to make it work was to put all the arguments in a Dict{Symbol, <:Any} and then splat it after the semi-colon.
I’m curious to hear thoughts on whether or not this is a good idea
This is not a good idea. This would suddenly mean that the behavior of non-macro code would depend on variable symbols, rather than just the value of the variables themselves. One of the virtues of macros is that they make it very explicit when this can happen.
Note that if what you are looking to do is forward keyword arguments from one function to another (a common use case for sure) one can do
f(;kwargs...) = g(;kwargs...)
Admittedly I have sometimes found myself worrying about whether there are performance implications to this, but there usually don’t seem to be.
The reason I don’t do that is because different keywords go to different functions. I suppose I could make all my functions take an arbitrary amount of keywords and just ignore anything not specified, but that seems wrong.
I see your point about macros, but I don’t really agree. All code depends on variable symbols. when you type f(x=x) which variable is named x definitely matters.
The point is that only the value of x matters in this case. You could have called it absolutely anything and it would not matter. Take any piece of code without macros, and transform it by mapping all symbols to new symbols, and it still works.
I guess it depends on how independent you view functions. If you change the symbol in all functions at the same time, the proposal still works. It only breaks if you change the symbols in one function but not another.
Things will also break if you change the name of a function but don’t change the references in other functions. Clearly the symbol, and not just the value, matters when it comes to functions, and given that functions are treated just like every other type I don’t see why they should be so different.
One thing why not to do so is potential WTFs with macros. Say:
f(; kw) = kw
kw = 2
f(; kw) # supposedly works
@some_macro let kw = 42
f(; kw) # error due to name mangling by the macro
end
If you want that behavior in your code and can get it with a macro, that’s perfectly fine and is a julian solution, I guess. You can use a named tuple instead of a Dict and splat pairs(x), that’d be more lightweight.
One could get around that by doing the expansion of f(; kw) to f(; kw=kw) before doing macro expansion. Then the macro would rewrite it to f(; kw=var”##3#kw”) or something.
I really like this idea, and I would probably use it a lot if it got implemented. I’ll even go as far as saying that with this feature I’d use keyword arguments a lot more than I do now.
using MacroTools: @capture, splitarg
macro pun(ex)
ret = if @capture ex (f_(args__; kw__))
kw = map(kw) do kwarg
key, typ, splat, val = splitarg(kwarg)
if splat
kwarg
elseif val == nothing
:($key = $key)
else
kwarg
end
end
:(
$(f)($(args...); $(kw...))
)
else
ex
end
esc(ret)
end
function f(args...;kw...)
println("="^80)
@show args
@show kw
end
a = 1
b = 2
cs = (d=1, e=2)
@pun f(; a=3)
@pun f(a,b; a, b, c=3)
@pun f(a,b; a, b, cs...)
I would call it KeywordTools or KwargTools or something like that. I personally like to call the macro @pkw standing for pass-keyword. Maybe @passkwarg.
Since this feature exists in other languages, I wonder if it has a name. In some languages like Rust it is restricted to constructors and goes under the name field init syntax.
But that name does not really make sense for arbitrary function calls.
I added the NamedTuple syntax the Stefan mentioned above. I’m new to using MacroTools so there’s likely a better way, but this works
edited to mimic NamedTupleTools.@namedtuple and use MacroTools better
macro eponym(ex)
ret = if @capture ex (f_(args__; kw__))
kw = map(kw) do kwarg
key, typ, splat, val = splitarg(kwarg)
if splat
kwarg
elseif val == nothing
:($key = $key)
else
kwarg
end
end
:(
$(f)($(args...); $(kw...))
)
elseif @capture ex ((; kw__))
kw = map(key -> :($key = $key), kw)
Expr(:tuple, Expr(:parameters, kw...))
else
ex
end
esc(ret)
end
The potential issue with this one is that its probably a little (tiny) bit slower.
julia> @macroexpand @eponym testf(;x,y,z)
:(testf(; x = x, y = y, z = z))
julia> @macroexpand testf(; @namedtuple(x,y,z)...)
:(testf(; ($(Expr(:parameters, :(x = x), :(y = y), :(z = z))),)...))
so it looks to me like there would be a little extra work at runtime because it still has do the tuple making and splatting. Hopefully this is optimized out by the compiler, but I don’t know for sure.
The time saved (if any) would be a fraction of a nanosecond, so it would only make a difference for a function in an inner loop that’s potentially called several billion times, but sometimes I need to write code like that so I think about optimizing these things.