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 Vars, 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 escin 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?