# Extracting kwargs from anonymous function

EDIT: original title was “Confused about escaping in macros”

I’m trying metaprogramming in Julia for the first time, and I’m having some trouble grasping the escaping and hygiene discussion in the manual.

I’m trying to write a macro `@par(func, params...)` that inserts some `params...` as kwargs in an anonymous function `func`. So, I want to make

``````@par((x,y) - > x * y * a, a)
``````

become

``````(x, y; a) -> x * y * a
``````

After some fumbling I have what seems like a working macro:

``````macro par(f, params...)
f.head == :-> || throw(ArgumentError("Only @par(args -> ..., parameters...) syntax supported"))
farg = f.args
body = f.args
symbolarg = isa(farg, Symbol)
symbolarg || isa(first(farg.args), Symbol) || throw(ArgumentError("Keyword arguments not supported"))
N = symbolarg ? 1 : length(farg.args)
newargs = symbolarg ? (esc(farg),) : esc.(Tuple(farg.args))
newparams = esc.(params)
newbody = esc(body)
newf = :((\$(newargs...), ; \$(newparams...)) -> \$(newbody))
return newf
end
``````

I am however quite unsure that I’m doing things as I should regarding the use of `esc` and hygiene. Could somebody have a look at the above to see if there is any glaring problem?

1 Like

Yes, this looks like you’ve done things correctly as far as I can tell. In terms of Hygiene, it seems that you’ve escaped everything in the output, so you could have just not done all that intermediate escaping and just had the last line been `return esc(newf)`.

Another general comment is that the package ExprTools.jl may be quite helpful for your purposes here.

Thanks @Mason!

I am a bit perplexed: it seems to me that `(x, y; a) -> x * y * a` is quite readable, better than the application of the macro. What is the motivation?

1 Like

Very good question! I want to be able to extract the kwargs from an anonymous function. I believe the only way is to use a macro at parse time. As soon as `(x, y; a) -> x * y * a` is compiled, there is no way to know that the list of kwargs is `(:a,)`. If there were a way, I would be really happy to learn how!

EDIT: In other words, I want to use `@par` as an alternative syntax to write anonymous functions because of its side effect of providing their kwargs as the `params...` arguments

Maybe in that case, the macro should just take in `(x, y; a) -> x * y * a` and then return

``````(x, y, ; a) -> (params = (;a=a), x * y * a)
``````

i.e. it would take in standard syntax.

1 Like

Mmm, but that would not give you access to `params` without evaluating the function, right?

My current full implementation is this

``````struct ParametricFunction{N,M,F}
f::F
params::NTuple{M,Symbol}
end

ParametricFunction{N}(f::F, p::NTuple{M,Symbol}) where {N,M,F} = ParametricFunction{N,M,F}(f, p)

(pf::ParametricFunction)(args...; kw...) = pf.f(args...; kw...)

parameters(p::ParametricFunction) = p.params

macro par(f, params...)
(f isa Expr && f.head == :->) ||
throw(ArgumentError("Only @par(args -> ..., parameters...) syntax supported"))
farg = f.args
body = f.args
symbolarg = isa(farg, Symbol)
symbolarg || isa(first(farg.args), Symbol) ||
throw(ArgumentError("Keyword arguments not supported in @par"))
N = symbolarg ? 1 : length(farg.args)
newargs = symbolarg ? (esc(farg),) : esc.(Tuple(farg.args))
newparams = esc.(params)
newbody = esc(body)
newf = :((\$(newargs...), ; \$(newparams...)) -> \$(newbody))
return :(ParametricFunction{\$N}(\$newf, \$params))
end
``````

So now

``````julia> f = @par((x, y) -> x*y*a, a)
ParametricFunction{2,1,var"#4#6"}(var"#4#6"(), (:a,))

julia> parameters(f)
(:a,)

julia> f(2, 3; a = 3)
18
``````

Here’s how I would do this:

``````using ExprTools: splitdef
struct ParametricFunction{N,M,F}
f::F
params::NTuple{M,Symbol}
end

ParametricFunction{N}(f::F, p::NTuple{M,Symbol}) where {N,M,F} = ParametricFunction{N,M,F}(f, p)

(pf::ParametricFunction)(args...; kw...) = pf.f(args...; kw...)

parameters(p::ParametricFunction) = p.params

macro par(f::Expr)
(f.head == :->) || throw(ArgumentError("Only @par(args -> ..., parameters...) syntax supported"))
d = splitdef(f)
kwargs = get!(d, :kwargs, []) #use get! because the :kwargs might not exist
params = (x -> x isa Symbol ? x : x.args).(kwargs) |> Tuple
N = length(get!(d, :args, []))
esc(:(ParametricFunction{\$N}(\$f, \$params)))
end
``````

and then

``````
julia> @macroexpand @par (x, y; a) -> x * y * a
:(ParametricFunction{2}(((x, y; a)->begin
#= In:38 =#
x * y * a
end), (:a,)))

julia> pf = @par (x, y; a) -> x * y * a
ParametricFunction{2,1,var"#74#76"}(var"#74#76"(), (:a,))

julia> parameters(pf)
(a,)

julia> pf(1, 2, a=3)
6
``````

To be clear, there’s nothing wrong with the syntax you’re using, I just personally find it easier to understand the kwarg syntax.

2 Likes

I love that, much better!!

1 Like

@Mason, do you mind if I copy your code into my package?

Not at all. 1 Like

You don’t even need the `ParametricFunction` type if you don’t want — you could just have your macro effectively do:

``````f = (x, y; a) -> x * y * a
parameters(::typeof(f)) = (:a, )
return f
``````
3 Likes

Clever!
{"ft_lang_label":"__label__en","ft_lang_prob":0.744194,"math_prob":0.9683586,"size":1912,"snap":"2022-05-2022-21","text_gpt3_token_len":559,"char_repetition_ratio":0.12683438,"word_repetition_ratio":0.05940594,"special_character_ratio":0.31537658,"punctuation_ratio":0.24666667,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9839185,"pos_list":[0,1,2],"im_url_duplicate_count":[null,null,null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2022-05-23T02:31:34Z\",\"WARC-Record-ID\":\"<urn:uuid:ee67c010-dc2a-482a-a192-4eb6afb4fa88>\",\"Content-Length\":\"50872\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:4787c8d9-3ab9-4079-9962-4fab444db063>\",\"WARC-Concurrent-To\":\"<urn:uuid:8576c9ef-612b-43d7-9d75-e722b16ad170>\",\"WARC-IP-Address\":\"64.71.144.205\",\"WARC-Target-URI\":\"https://discourse.julialang.org/t/extracting-kwargs-from-anonymous-function/37350\",\"WARC-Payload-Digest\":\"sha1:LBVGXYF5SIZH274HFARCOSJ2GCVGMSD4\",\"WARC-Block-Digest\":\"sha1:UHAHKOW4CEXXITOVCOIVBKZGCX24MDOK\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2022/CC-MAIN-2022-21/CC-MAIN-2022-21_segments_1652662552994.41_warc_CC-MAIN-20220523011006-20220523041006-00156.warc.gz\"}"}