Is there a trick to input an `Expr` into a macro?

AFAIK a macro only works like a “tag” on source code that is parsed to input expressions for the macro. Is there a way to make a macro work on an Expr instance?

I’m aware that this might be a problem because macros are documented to only work on expressions, literals, and symbols, but an Expr instance can contain other types’ instances. I’m also aware that a method can take an Expr, and the typical way to accomplish what I want is such a method and a macro that uses that method. It’s just that sometimes a macro’s internal Expr-method isn’t part of the API or doesn’t exist.

That’s not what a macro is, no. A macro is given a parsed expression (of type Expr) to transform into another Expr, so they already work on Expr objects/instances.

Not for arbitrary types, no - only literals the parser knows about, like literal 64 bit integers, floating point values, strings, symbols, function definitions/calls (which are different kinds of Expr literals - the values these end up as don’t exist at macro expansion time).

What do you want to accomplish? Using functions to build more complicated Exprs in a macro is usually just a design choice by the macro author - it’s never required to do that.

My understanding is that an Expr can contain any types, just not when parsed from source. For example, I can interpolate a custom type’s instance into an Expr:

julia> mutable struct Blah
         x::Int
         y::Float64
       end

julia> :($(Blah(1, 1.1)) - 1.5) |> dump
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol -
    2: Blah
      x: Int64 1
      y: Float64 1.1
    3: Float64 1.5

I’m talking about these “runtime” Expr instances. For example, @eval 1+3 works, but there’s no way to input :(1+3) to the macro, you need the method version eval(:(1+3)). The macro only seems to work on expressions that are parsed from the source code “tagged” by the macro call.

Are you sure you don’t just want a function that takes in an Expr?

1 Like

Given a macro @something you can get the actual macro object by writing var"@something" and from that you can inspect it’s methods and call it. For example:

julia> macro do_nothing(ex)
           ex
       end
@do_nothing (macro with 1 method)

julia> methods(var"@do_nothing")
# 1 method for macro "@do_nothing":
[1] var"@do_nothing"(__source__::LineNumberNode, __module__::Module, ex) in Main at REPL[13]:1

This says that var"@do_nothing" has a method that takes in LineNumberNode and Module telling you where the macro was invoked, and an expression that it operates on. If you provide this stuff, you can run the macro on a runtime Expr and get out an Expr. For example,

julia> var"@do_nothing"(LineNumberNode(@__LINE__(), @__FILE__()), @__MODULE__(), :(x + 1))
:(x + 1)
4 Likes

I absolutely want that, but sometimes a cool macro doesn’t have a function version, and I’m not cool enough to write one.

“Wait it’s all methods?” “Always has been.”

Also I’ve been messing around with macroexpand a bit, found another way to do it:

julia> y = 2
2

julia> x = :(y+1)
:(y + 1)

julia> macro minus1(ex) return :($ex - 1) end
@minus1 (macro with 1 method)

julia> @macroexpand @minus1 y+1 # ideal output Expr
:((Main.y + 1) - 1)

julia> @minus1 y+1 # ideal eval
2

julia> @macroexpand @minus1 $x # cannot interpolate in source, would error
:($(Expr(:$, :(Main.x))) - 1)

julia> macroexpand(Main, :(@minus1 $x)) # interpolate argument, then expand
:((Main.y + 1) - 1)

julia> Core.eval(Main, :(@minus1 $x))
2

If you’re going to use macroexpand, I’d recommend using @macroexpand1 ex / macroexpand(module, ex; recursive=false), because that way you won’t accidentally end up expanding any inner macros buried in ex.

2 Likes

In what sort of situations would it be useful to only expand the outermost macro call? Documentation for macroexpand1 and macroexpand doesn’t give any tips.