macrosmetaprogrammingelixir

Elixir: use macro in the body of same module that defined it


This is common elixir:

defmodule Fizz do
  defmacro asdf, do: IO.puts("asdf")
end

defmodule Buzz do
  require Fizz
  Fizz.asdf
end

However, although you can reference macros in the same context like:

defmodule Fizz do
  # ...
  defmacro asdf_qwer, do: asdf && IO.puts("qwer")
end

... you can't reference macros in the body of the same module that defined them:

defmodule Fizz do
  defmacro asdf, do: IO.puts("asdf")
  asdf
end

This raises undefined function asdf/0.

Is there a workaround for this "problem"? Sometimes I may want to use macros to remove some boilerplate from the module I'm working on, and that macro's functionality may be specific enough not to put it in another module.


Solution

  • The reason that we're getting undefined function errors here is because at compile time, the asdf macro does not yet exist.

    So we need to notify the compiler that an extra step is required just before compilation is finished.

    One option is the @after_compile module callback attribute which lets you invoke some code just after compilation in order to perform a final bit of code generation.

    For example:

    defmodule M do
      @after_compile __MODULE__
    
      def __after_compile__(env, _bytecode) do
        IO.inspect env
      end
    end
    

    Another option is to put your macros in a Fizz.Macros module.