How can Julia macro take an Expr variable as its arguments?

Here is a simple macro

macro pre_print(expr::Expr)
    println("show....")
    return quote
        $expr
    end
end

I can use it as:

@pre_print a=10

#show....

#10

But how can this macro take an expr as its argument, like:

test_expr = :(a=10)

@pre_print(test_expr)

LoadError: MethodError: no method matching var"@pre_print"(::LineNumberNode, ::Module, ::Symbol)
Closest candidates are:
  var"@pre_print"(::LineNumberNode, ::Module, ::Expr) at In[7]:1
in expression starting at In[11]:1

Stacktrace:
 [1] eval
   @ .\boot.jl:368 [inlined]
 [2] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
   @ Base .\loading.jl:1281

A macro does not see the value of a variable (test_expr in this case), only the code that is immediately written in its parsed block. At the time of macro expansion, that variable does not yet have a value (even in global scope, should the variable have a value - the macro does not see it) - it can only see the symbol you’ve written in the literal expression block inside of those ().

A macro is distinct from a function that takes an Expr object.

5 Likes

Thanks for your help. In some situation, maybe it is not a proper way to use macro.

1 Like

You can think of it this way. The macro definition you write takes in an expression and returns an expression, much like a method can. But only a method could take in a runtime Expr instance and return another. The macro definition is just a way for you to customize one part of a bigger process of parsing and evaluating source code. You can’t control the parser converting the source code to an Expr that the macro will work on, and you can’t control the macro’s output Expr being evaluated. (You can however use @macroexpand to wrap the output Expr in an extra Expr layer so when it is evaluated, you get the output Expr.)

This incidentally is why macros are said to only work on literals, symbols, and Expr of such; it’s because the parser can only produce such things from source code. However, there is actually an internal (not public API, not stable across versions) trick to get the macro definition’s underlying method, which can take in runtime Expr instances. Right after that trick’s comment in the thread, I also commented how to use the wholly public macroexpand function and $-interpolation to make a macro work on a runtime Expr.