Stream: helpdesk (published)

Topic: Help writing macro with varargs


view this post on Zulip mbaz (May 18 2023 at 22:22):

I can't figure this out...

I'm writing a macro that takes a variable number of arguments, modifies them, and calls a function on the modified arguments. I can do this if the number of arguments is fixed, but not for varargs. A simple example:

julia> f(args...) = [a^2 for a in args];

julia> modarg(x) = x + 1;

julia> x, y, z = 2, 3, 4;

julia> macro f(ex)
           :( f(modarg($ex)) )
       end;

julia> @f 1
1-element Vector{Int64}:
 4

julia> @f x
1-element Vector{Int64}:
 9

So far so good. For two arguments:

julia> macro f2(ex1, ex2)
           :( f(modarg($ex1), modarg($ex2)) )
       end;

julia> @f2 3 4
2-element Vector{Int64}:
 16
 25

julia> @f2 x y
2-element Vector{Int64}:
 16
 25

Everything looks fine, I think. Now trying for varargs, one of many unsuccessful attempts:

julia> macro fvar(ex...)
           args = (modarg(v) for v in ex)
           :( f($(args...)) )
       end;

julia> @fvar 1 2 3
3-element Vector{Int64}:
  4
  9
 16

Apparently all is well, but....

julia> @fvar x y z
ERROR: LoadError: MethodError: no method matching +(::Symbol, ::Int64)

How can I solve this?

view this post on Zulip mbaz (May 18 2023 at 22:53):

I _may_ have solved it... I thought that building the expression itself by hand might work:

julia> macro fvar(ex...)
           Expr(:call, :f, (:(modarg($v)) for v in ex)...)
       end;

julia> @fvar 2 3 4
3-element Vector{Int64}:
  9
 16
 25

julia> @fvar x y z
3-element Vector{Int64}:
  9
 16
 25

view this post on Zulip Mason Protter (May 18 2023 at 23:26):

The problem was that in fvar you wrote args = (modarg(v) for v in ex) instead of args = (:(modarg($v)) for v in ex)

view this post on Zulip Mason Protter (May 18 2023 at 23:30):

Also, your macro works in the repl because the variables you're referencing exist in the same scope as the macro was defined, but it won't work in general without you escing anything you want to be resolved in the macro-caller's scope

julia> let x = -11
           @fvar x y z
       end
3-element Vector{Int64}:
  9
 16
 25

view this post on Zulip Mason Protter (May 18 2023 at 23:31):

julia> macro fvar2(ex...)
           args = (:(modarg($(esc(v)))) for v in ex)
           :( f($(args...)) )
       end;

julia> let x = -11
           @fvar2 x y z
       end
3-element Vector{Int64}:
 100
  16
  25

view this post on Zulip Mason Protter (May 18 2023 at 23:33):

Also, note that your macro here is no different from the function

fvar(args...) = f(modarg.(args)...)

since the modarg is not being run at macroexpansion time but instead being put into the expanded expression

view this post on Zulip mbaz (May 18 2023 at 23:33):

Thanks! That works. :tada:

view this post on Zulip mbaz (May 18 2023 at 23:36):

Yeah, in my actual use case modargs is more complicated -- I need a macro because I'm doing some syntax transformations to the arguments.

view this post on Zulip Mason Protter (May 18 2023 at 23:37):

Ah, so in that case you probably actually wanted something closer to your original fvar, and not like your 1 or 2 arg examples

view this post on Zulip mbaz (May 18 2023 at 23:37):

I think I was close to the right syntax (I used similar code as you did when I used Expr(...)), but I definitely hadn't even started thinking about esc yet.

view this post on Zulip Mason Protter (May 18 2023 at 23:37):

i.e.

julia> modarg(x) = :($x + 1); # Syntax transformation instead!

julia> macro fvar3(ex...)
           args = (esc(modarg(v)) for v in ex)
           :( f($(args...)) )
       end;

julia> @macroexpand @fvar3 x y z
:(Main.f(x + 1, y + 1, z + 1))

view this post on Zulip mbaz (May 18 2023 at 23:38):

Let me try the actual transformation I want to do

view this post on Zulip mbaz (May 18 2023 at 23:41):

macro plot(ex...)
    args = (esc(procopts(v)) for v in ex)
    :( plot($(args...)) )
end

and

julia> @macroexpand @plot {grid} sin
:(Gaston.plot((Gaston.expand)("grid" => true), sin))

It works :big_smile:

view this post on Zulip mbaz (May 18 2023 at 23:43):

procopts is a function that uses MacroTools.jl to convert arguments inside {} to a pair. I got this idea from PgfplotsX.jl.

view this post on Zulip mbaz (May 18 2023 at 23:43):

(writing procopts was also quite an odissey in itself...)

view this post on Zulip Mason Protter (May 19 2023 at 00:12):

Nice!


Last updated: Oct 02 2023 at 04:34 UTC