Macrofunology 1: module structure

This series of posts is about macros that expand into Elixir def expressions. That is, they expand into definitions of functions that get "attached" to a containing module. Such macros are not wildly more complicated than normal Elixir macros, but there are some subtleties and some gotchas. This series of posts aims to explain them to you.

The series: this post, bodies and arglists, guard expressions, atoms and function names


The first gotcha is that you can't use defmacrop to make a module-specific macro.

Here's the type of private macro you might be used to:

  defmodule Works do

    defmacrop plus(a, b) do
      quote do
        unquote(a) + unquote(b)
      end
    end

    def plus_client do
      plus(1, 2)
    end
  end
link to code

plus is a macro that expands into an expression used within the body of a function definition (def). As such, it can be both defined and used within the same module, Works.

But I can't use the same pattern if the macro is one that creates a def:

  defmodule NotForFunctions do 
    defmacrop defadder(addend) do
      quote do
        def adder(n), do: n + unquote(addend)
      end
    end
    
    defadder 4
  end
link to code

The call to defadder will produce this error:

== Compilation error in file lib/defmacrop.ex ==
** (CompileError) lib/defmacrop.ex:28: undefined function defadder/1

What's happening there is not specific to macros. For example, the same thing happens with ordinary functions:

  defmodule TopLevel do
    def add1(n), do: n+1
    IO.inspect add1(1)
  end

== Compilation error in file lib/defmacrop.ex ==
** (CompileError) lib/defmacrop.ex:34: undefined function add1/1
    (stdlib 3.13.2) lists.erl:1358: :lists.mapfoldl/3
link to code

The details of what's happening are complicated enough that I think they'd be a big digression from the point of this series. So, for purposes of defining functions with macros, suffice it to say that the macro definition needs to be in one module and all the uses in other modules. For example, here's a definition:

  defmodule Definition do
    defmacro defadder(addend) do
      quote do
        def adder(n), do: n + unquote(addend)
      end
    end
  end
link to code

... and here's a use, which has to require or import the first module:

  defmodule Use do
    import Definition

    defadder 4
  end
link to code

Next: wrapping bodies, dissecting arglists