I have a struct to represent symbolic variable:
struct Var
name::Symbol
value::Any
end
Now I want to generate a function expression that takes objects of type Var
and returns an expression made from their .name
fields, e.g. like this:
function +(x::Var, y::Var)
return Expr(:call, :+, x.name, y.name)
end
x = Var(:a, 1) # note: julia variable `x`, but internal name is :a
y = Var(:b, 2)
x + y # ==> :(a + b)
Code generation will be wrapped into a macro, so I’ll refer to this stage - stage where we generate function like above - as a macroexpand time. On the contrary, the returned expression, e.g. Expr(:call, :+, x.name, y.name)
can only be created in runtime since x.name
and y.name
aren’t known beforehand.
I have many function like this with different signatures, e.g.:
+(x::Var, y::Var)
exp(x::Var)
^(x::Var, n::Int)
...
Obviously, I don’t want to copy-paste function body for of these cases but instead to have something like:
@symbolic +(x::Var, y::Var)
@symbolic exp(x::Var)
@symbolic ^(x::Var, n::Int)
...
So the list of argument names is always different (we can ignore types for a moment). Now the basic setup of a problem looks like this:
f_vars = [:x, :y] # the list of `Var` names from function signature, known during macroexpand time
quote
ex_vars = ... # something that returns [:a, :b] in runtime
Expr(:call, :op, ex_vars...)
end
The missing piece is the code to get ex_vars
, i.e. values of x.name
and y.name
during runtime. Note, that during macroexpand time we known the names of variables (i.e. x
and y
), but not their values.
First I tried the following:
:(ex_vars = [v.name for v in $f_vars])
But f_vars
is a list of symbols, not Var
s, so v.name
fails with type Symbol has no field name
.
So I tried to interpolate each v
first, meaning that interpolation should happen in runtime:
:(ex_vars = [$(v).name for v in $f_vars])
Surely, it’s interpolated in macroexpand time instead. I also tried multiple options with Expr(:$, ...)
and QuoteNode(...)
, but couldn’t find anything working. One more attempt with esc
in hope it passes $
as is:
:(ex_vars = [esc($(v).name) for v in $f_vars])
But it also tries to interpolate v
right in macroexpand time.
Any other options?