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?
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
The problem was that in fvar
you wrote args = (modarg(v) for v in ex)
instead of args = (:(modarg($v)) for v in ex)
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 esc
ing 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
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
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
Thanks! That works. :tada:
Yeah, in my actual use case modargs
is more complicated -- I need a macro because I'm doing some syntax transformations to the arguments.
Ah, so in that case you probably actually wanted something closer to your original fvar
, and not like your 1 or 2 arg examples
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.
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))
Let me try the actual transformation I want to do
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:
procopts
is a function that uses MacroTools.jl
to convert arguments inside {}
to a pair. I got this idea from PgfplotsX.jl
.
(writing procopts
was also quite an odissey in itself...)
Nice!
Last updated: Dec 28 2024 at 04:38 UTC