How use eval to define macros that involve function definitions

I want to define a bunch of macros that define function methods in a private module.

module MyModule

function f() end
function g() end

end

using Main.MyModule

struct T
    i
end

macro f(T, expr)
    esc(:(MyModule.f(::$T) = $expr))
end

@f T "test"

julia> MyModule.f(T(1))
"test"

That works fine if I do it manually. If I try to do this in an eval loop, if fails

for fn in (:f, :g)
    Core.eval(@__MODULE__, quote
        macro $fn(T, expr)
          esc(:($(MyModule.$fn)(::$T) = $expr))
        end
    end)
end

The macro call fails:

julia> @f T "test"
ERROR: syntax: invalid function name "MyModule.f" around REPL[36]:1
Stacktrace:
 [1] top-level scope
   @ REPL[36]:1

I played around with QuoteNodes, but I couldn’t solve this problem.
It feels a bit similar like the discussion in Can't initalize a function with no methods inside a macro · Issue #34164 · JuliaLang/julia · GitHub

Anyone who can help here?

I assume the problem is due to the nested interpolations with $. For example, using Meta.parse you can write

julia> for fn in (:f, :g)
           "macro $fn(T, expr)
               esc(:(MyModule.$fn(::\$T) = \$expr))
           end" |> Meta.parse |> eval
       end

julia> @g T "abc"

julia> MyModule.g(T(1))
"abc"

but note that I had to use both $ and \$.

Unrelated to your metaprogramming issue - you most probably want this instead:

function f end
function g end

function f end defines a function without methods, while function f() end defines a function with a single method.

1 Like

Inspired by the Expression obtained by the Meta.parse above, here’s a way to achieve this without resorting to Strings:

for fn in (:f, :g)
    quote
        macro $fn(T, expr)
            $(Expr(:quote, esc(:(MyModule.$fn(::$(Expr(:$, T))) = $(Expr(:$, :expr))))))
        end
    end |> eval
end

i.e. use Expr(:$, :x) for the nested version of $x, and wrap this nesting in an Expr(:quote, ...).