Wow, I’ve been spending the last few days trying to understand metaprogramming in Julia and specifically Macros. Not much luck! Take the code below:
a = "p > 0.5"
function test(a)
for p in [0,1,2]
if @my_macro(a)
# evaluate here if p (in this scope) is larger than 0.5
end
end
end
How should my_macro look for this to work? Is this good practice? In reality I expect a to be a vector of several conditions on different variables which should all be fulfilled.
Macros are not magic, neither is metaprogramming. There’s almost nothing that macro can do for you if you can’t write the code without a macro (the few exceptions being syntax that we only expose via macro like @goto).
It’s almost never necessary nor useful to store expressions in Julia as strings. In fact, in your case there is no reason to store any kind of expression or do any metaprogramming: you can store functions instead:
a = p -> (p > 0.5)
function test(a)
for p in [0,1,2]
if a(p)
println("true")
end
end
end
Thanks for all the answers! The reason I wanted to use strings was for the user to be able call the function in the REPL with various conditions specified in a simple manner. Maybe the approach with a = p → (p > 0.5) will do. I guess that is what I will have to examine for now.
Again, thanks for all the answers!
I am now informed that this is bad practice but I still don’t fully understand why it can only be evaluated at runtime. Why can’t a > b be inserted into the code at compile time by a macro and then later evaluated for the a and b that exist during runtime?
It’s because of how macros work - they see the expression passed to them and not the value of the expression. So @my_macro(a) sees the symbol :a rather than the contents of a. For example, the following macro sort-of does what you want but not really - notice the difference between using a string literal as the argument and a string variable.
julia> macro my_macro(a)
println("a = ", repr(a)) # to see what a actually is
return esc(Meta.parse(a))
end
@my_macro (macro with 1 method)
julia> p = 0.1
0.1
julia> @my_macro("p < 0.5")
a = "p < 0.5"
true
julia> a = "p < 0.5"
"p < 0.5"
julia> @my_macro(a)
a = :a
ERROR: LoadError: MethodError: no method matching parse(::Symbol)
Closest candidates are:
parse(::AbstractString; raise, depwarn) at meta.jl:215
parse(::AbstractString, ::Integer; greedy, raise, depwarn) at meta.jl:176
Stacktrace:
[1] @my_macro(::LineNumberNode, ::Module, ::Any) at .\REPL[20]:3
in expression starting at REPL[24]:1
The key thing is that the contents of a are not defined at compile time. (Also, as per the warnings above, doing this with strings is somewhat fragile and open to abuse.)