How to return a macro from a macro?

I want to create a macro that receives the header foo(args) and body begin ... end and returns a macro version of it.

macro macwo(header::Expr, body::Expr)
    #=
    macro header
        body
    end
    =#
end

# Implementation example
@macwo foo(a::Integer, b, c) begin
    println(a + b + c)
end

@foo(1,2,3)

But I’m new to Julia and I can’t do it by reading Julia’s documentation.

Hi, and welcome to the Julia community!


I’m not sure I completely understand what you want to achieve (i.e. why this is easier than just writing

macro foo(a::Integer, b, c)
    println(a + b + c)
end

directly, or just creating a function), so perhaps a more complicated example would be useful. But for this case I guess

macro macwo(header::Expr, body::Expr)
    esc(quote
        macro $(header.args[1])($(header.args[2:end]...))
            $body
        end
    end)
end

works:

julia> @macwo foo(a::Integer, b, c) begin
    println(a + b + c)
end
@foo (macro with 1 method)

julia> @foo(1, 2, 3)
6

The idea here is that we want to output essentially the same macro (expression) as I typed at the start of my post

julia> using MacroTools

julia> @macroexpand @macwo foo(a::Integer, b, c) begin
           println(a + b + c)
       end
quote
    #= REPL[1]:3 =#
    macro foo(a::Integer, b, c)
        #= REPL[1]:3 =#
        #= REPL[1]:4 =#
        begin
            #= REPL[5]:2 =#
            println(a + b + c)
        end
    end
end

(Without the escaping esc we would have Main.foo, Main.Integer and Main.println.) Now, to write our macro macwo it is useful to take a look at the input we are getting:

julia> macro show_args(header, body)
           dump(header)
           dump(body)
       end
@show_args (macro with 1 method)

julia> @show_args foo(a::Integer, b, c) begin
           println(a + b + c)
       end
Expr
  head: Symbol call
  args: Array{Any}((4,))
    1: Symbol foo
    2: Expr
      head: Symbol ::
      args: Array{Any}((2,))
        1: Symbol a
        2: Symbol Integer
    3: Symbol b
    4: Symbol c
Expr
  head: Symbol block
  args: Array{Any}((2,))
    1: LineNumberNode
      line: Int64 2
      file: Symbol REPL[11]
    2: Expr
      head: Symbol call
      args: Array{Any}((2,))
        1: Symbol println
        2: Expr
          head: Symbol call
          args: Array{Any}((4,))
            1: Symbol +
            2: Symbol a
            3: Symbol b
            4: Symbol c

In this way we can see that our method name foo can be found as header.args[1], while the argument list is present in header.args[2:end], which we splat (and interpolate) to get three arguments instead of a single length-3 array.

You could also add additional checks, depending on how robust you want your macro to be. For example, we expect header.head === :call. Currently,

@macwo foo[a::Integer, b, c] begin
     println(a + b + c)
end

would behave identically, even though here header.head === :ref.

julia> macro macwo(header::Expr, body::Expr)
           @assert header.head === :call
           esc(quote
               macro $(header.args[1])($(header.args[2:end]...))
                   $body
               end
           end)
       end
@macwo (macro with 1 method)

julia> @macwo foo(a::Integer, b, c) begin
           println(a + b + c)
       end
@foo (macro with 1 method)

julia> @macwo foo[a::Integer, b, c] begin
           println(a + b + c)
       end
ERROR: LoadError: AssertionError: header.head === :call
(...)

Edit: An alternative to inspecting the expression tree, is to use MacroTools.jl’s @capture, which can be easier to read:

macro macwo(header::Expr, body::Expr)
    @capture(header, f_(a__))
    esc(quote
        macro $f($(a...))
            $body
        end
    end)
end

By the way, I see that the automatic translation thinks you wrote the original message in Japanese, and that it ‘translated’ “receives” to “takes”. This might be a relevant issue for Testing automatic translations to English.

I’m not seeing any indication that this post was automatically translated, maybe it’s a UI problem on my end. How are you seeing it?

In the top-right corner there used to be the translation icon and message on hover. But after refreshing the page now, it has also disappeared for me. Additionally the message has gone back to using “receives”. So I guess it already fixed itself… :slight_smile:

I think it’s more involved than the macro proposed by @eldee. At least one needs to remove the type declarations:

julia> x = 1; @foo(x, 2, 3)
ERROR: LoadError: MethodError: no method matching var"@foo"(::LineNumberNode, ::Module, ::Symbol, ::Int64, ::Int64)
The function `@foo` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  var"@foo"(::LineNumberNode, ::Module, ::Integer, ::Any, ::Any)
   @ Main REPL[1]:3

Apart from the type, the main issue is simply that the macro foo performs its print at parse time, when we do not know the value of x. Note that we do not have

macro foo2(a, b, c)
    esc(:(println($a + $b + $c)))
end

for which

julia> x = 1;

julia> @foo2(x, 2, 3)
6

does work. Also note the difference between

julia> for _ = 1:5
           @foo(1, 2, 3)
       end
6

julia> for _ = 1:5
           @foo2(1, 2, 3)
       end
6
6
6
6
6

But from @ysaak’s description it’s not clear to me which of these behaviours (if any) is desired.

Generically I wouldn’t recommend worrying about macros when you are first starting out. They can be a very useful tool, but only in a small minority of cases, and are easy to misunderstand. See also the discussion in How to warn new users away from metaprogramming

Thank you guys for the support!
My goal with macros in Julia is to make easier to work with some common macros.
Because of this, I had a big interest on how to handle with the AST from Julia.

Could you provide a small specific example of those common macros and what you intend to do? We appreciate the effort to make a minimal general example, but that opened up broader questions you probably didn’t intend to be discussed and couldn’t really be answered to a practical extent.