Proper way to define macros that declare functions?

I define a create_foo macro that declares a function foo.

I want to be able to declare foo functions in the module where the
macro is defined.

This part is OK as this minimal example shows:

  module Foo
  
  macro create_foo(X)
      quote 
	  $(esc(:foo))(x::$(esc(X))) = 2*x
      end 
  end

  println(@macroexpand @create_foo(Int))
  # prints :
  #
  # |  begin
  # |    #= REPL[7]:4 =#
  # |    foo(var"#7#x"::Int) = begin
  # |	    #= REPL[7]:4 =#
  # |	    2var"#7#x"
  # |	end
  # |  end
  #
  # which is OK 
  end

However, I also want Foo module’s users to be able to use this macro to define
specializations. My problem is that, when calling the macro from Main module, I get :

julia> @macroexpand Foo.@create_foo(Float64)
quote
    #= REPL[7]:4 =#
    foo(var"#9#x"::Float64) = begin
            #= REPL[7]:4 =#
            2var"#9#x"
        end
end

The result I want is a fully qualified name:

julia> @macroexpand Foo.@create_foo(Float64)
quote
    #= REPL[7]:4 =#
    Foo.foo(var"#9#x"::Float64) = begin
            #= REPL[7]:4 =#
            2var"#9#x"
        end
end

Does my understanding of the problem is right and how to achieve this expected behaviour ?

Macros are expanded in the scope that they’re called from, so the name you’re getting actually is fully qualified, it’s just not the qualification you expected, since it was called in Main instead of Foo.

Here’s one way you can get foo to always be defined in Foo:

module Foo

macro create_foo(X)
    qualified_name = esc(GlobalRef(Foo, :foo))
    quote 
        $qualified_name(x::$(esc(X))) = 2*x
    end 
end

end
julia> @macroexpand Foo.@create_foo(Int)
quote
    #= REPL[16]:6 =#
    Main.Foo.foo(var"#96#x"::Int) = begin
            #= REPL[16]:6 =#
            2var"#96#x"
        end
end
4 Likes

Great! Thank you for your response.

Creating a function with a macro seems like a fairly common problem to me. Do you have any idea if the way you’re suggesting is the canonical way to solve this problem?

May I suggest a function over a macro?

julia> module Foo

        function foo end

        show_foos() = methods(foo, @__MODULE__)

        dosomething() = foo(2.0)

        create_foo(T) = eval(:( foo(x::$T) = 2*x ))
        end
Main.Foo

julia> Foo.create_foo(Int)
foo (generic function with 1 method)

julia> methods(Foo.foo)
# 1 method for generic function "foo" from Main.Foo:
 [1] foo(x::Int64)
     @ REPL[1]:9

julia> Foo.create_foo(Float64)
foo (generic function with 2 methods)

julia> methods(Foo.foo)
# 2 methods for generic function "foo" from Main.Foo:
 [1] foo(x::Int64)
     @ REPL[1]:9
 [2] foo(x::Float64)
     @ REPL[1]:9

julia> Foo.dosomething()
4.0
1 Like

Thank you. This is a run-time solution, that works too for me.