How to return a macro from a macro?

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.