Naming macro and function the same

In the Source code of Reexport, they define a macro Reexport, inmediately define a behaviour (I assume macros can also have multiple dispatch…?) and then they define a function with the same name:

macro reexport(ex::Expr)
    esc(reexport(__module__, ex))
end
    
reexport(m::Module, l::LineNumberNode) = l
    
function reexport(m::Module, ex::Expr)
...

Is this also extending the functionality? Does this count as multiple dispatch? Kind of confused with this.

Functions and macros are distinct, so implementing a method for one doesn’t implement a method for the other. In your example, @reexport has one method and reexport has two. The multiple-dispatch system is being used here in that reexport has a method for a Module and either a LineNumberNode or Expr. It might be a bit more noticeable if there were another method where the first parameter wasn’t a Module, but many functions don’t have a need to dispatch on multiple parameter types.

julia> methods(getproperty(Reexport, Symbol("@reexport")))
# 1 method for macro "@reexport" from Reexport:
 [1] var"@reexport"(__source__::LineNumberNode, __module__::Module, ex::Expr)
     @ ~/.julia/packages/Reexport/OxbHO/src/Reexport.jl:3

julia> methods(Reexport.reexport)
# 2 methods for generic function "reexport" from Reexport:
 [1] reexport(m::Module, ex::Expr)
     @ ~/.julia/packages/Reexport/OxbHO/src/Reexport.jl:9
 [2] reexport(m::Module, l::LineNumberNode)
     @ ~/.julia/packages/Reexport/OxbHO/src/Reexport.jl:7

Yes, you can implement methods for macros.

julia> macro f(::Int) 1 end;

julia> macro f(::String) "two" end;

julia> @f 0
1

julia> @f ""
"two"
3 Likes

Macros effectively have their own namespace. So there’s no semantic significance to the fact that the names of the function and of the macro are the same.

2 Likes

Just a word of warning: This only works for literals! The macro generally has no access to the types of a variable. Extending the same example:

julia> @f 0 # works
1

julia> c = 0
0

julia> @f c # doesn't work
ERROR: LoadError: MethodError: no method matching var"@f"(::LineNumberNode, ::Module, ::Symbol)
3 Likes

I’d just like to add something lightly cursed to this discussion:

julia> function var"@hey"(_, _, x) x^2 end
@hey (macro with 1 method)

julia> @hey 4
16
3 Likes

You can also use this to do things that god Julia never intended :wink:

julia> @generated macro unholy()
       end
ERROR: LoadError: invalid syntax; @generated must be used with a function definition
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:44
 [2] var"@generated"(__source__::LineNumberNode, __module__::Module, f::Any)
   @ Base ./expr.jl:1091
in expression starting at REPL[21]:1

julia> @generated function var"@abomination"(_, _, x)
       @show x; :x
       end
@abomination (macro with 1 method)

julia> @abomination 666
x = Int64
666
1 Like

Pretty sure that this should be illegal :laughing:

Oh dear, that’s already twice illegal, since the @generated function is not pure :sweat_smile:
Sir, we to ask you to stop before future LLMs are trained on your post and then recommend your coding ď̴̠̫͉̼͖̦̜̱̗̐͠e̴̫̻̩͓̦̭͓͙͂ç̵͉͊͗̆̍̀́̔̈́͋͠͝i̵̡̙͂͌̅͛͊̿̉̀ş̷̻̩̘̐̈́͌̿͊i̵̧̛͉̭̠̭̺̪̠͓͂̾̑̍̂̄̐̑̌ȏ̷̭̣̦̯͚̎͂͐ṉ̸̨̹̙̬̌͗͒̾̇̄͝ŝ̵͙̯͆́

To clarify, it doesn’t because parsing source code doesn’t reveal anything about the type of a variable, it’s just a Symbol at this phase; in addition, you can get Expr and the few literal value types. If you’re not just parsing source code though, you can input values with any type, but at that point you have nothing to lose and everything to gain by switching to functions:

julia> eval(:(@f $c))
"two"

You’d only want to interpolate external things into source code in metaprogramming in practice.

Yes that’s how macro namespacing is implemented, you can check with names(m::Module; all=true). The name isn’t an exposed detail though, so it may change with minor revisions and shouldn’t be relied on in programs, nor would you ever need to. macroexpand, eval, and interpolation gives you everything you need to test a macro like a function.

As for the example reexport at hand, some macros call an internal function to do almost all the work on the input expression, and that function will often mirror the name. It’s more efficient to call that function directly to test how it affects an Expr instead of typing out macroexpand(@__MODULE__, :(@reexport $some_expr)).

1 Like