Macro in function scope

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.

Thank you!

Welcome to Julia-discourse!

When the macro is evaluated it only sees :a and not what eventually ends up in a. Thus this cannot work.

Just do a double loop?

1 Like

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).

2 Likes

Thanks for your answers! Ok, so I take it from your answers that a macro is not suitable for this task, or even able to perform it.

My thought was that if I had the more realistic case with a vector a:

a = ["p1 > 1", "p1 > p2", "p1 < 9", "p2 > 3", etc..]

it would be really clean if i could get the expression

p1 > 1 && p1 > p2 && p1 < 9 && p2 > 3 # etc..

straght into my code. As it seems that I am misstaken in what is suitable for metaprogramming, how would you do this?
Thanks!

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
3 Likes

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.

See Passing a string into function as a expression if you really want to explore evaluating strings. Be aware that it’s in many ways an inferior solution and definitely slower, but sometimes it may be what you want.

1 Like

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.)

3 Likes

This one might be informative to watch: https://youtu.be/RYZkHudRTvI

3 Likes