Skip to content
Macros and Code Generation
step 1/4

Reading — step 1 of 4

Learn

~1 min readTasks, ETS, Macros

Elixir macros are functions that operate on the AST at compile time. Like Lisp/Clojure macros — code as data.

Most of Elixir's syntax is implemented as macros: def, defmodule, if, unless, case, cond, with, for. Open the source and you'll see they're regular Elixir functions tagged defmacro.

A macro:

defmodule MyMacros do
    defmacro unless(test, do: body) do
        quote do
            if !unquote(test), do: unquote(body)
        end
    end
end

quote captures code as data:

quote do: 1 + 2
# => {:+, [], [1, 2]}     — AST tuple

unquote injects a value into a quoted block.

Use the macro:

import MyMacros

unless 1 == 2 do
    IO.puts "not equal"
end

Getting expanded at compile time to:

if !(1 == 2), do: IO.puts "not equal"

Macro.expand_once/2 to debug — see what your macro produces:

Macro.expand_once(quote(do: unless(true, do: "x")), __ENV__)
# => {:if, [...], [{:!, [...], [true]}, [do: "x"]]}

__MODULE__ and friends:

  • __MODULE__ — current module name
  • __ENV__ — compile-time environment
  • __CALLER__ — environment of the macro's caller

Defining functions inside a macrodefmacro that calls def for each entry:

defmacro generate_attr(names) do
    Enum.map(names, fn name ->
        quote do
            def unquote(name)(state), do: state.unquote(name)
        end
    end)
end

This is how Phoenix routers, Ecto schemas, and lots of DSLs work — route, field, embeds_one are all macros.

When NOT to write a macro:

  • A function works
  • You'd only call it once
  • The metaprogramming hides important behavior from the reader

When TO write a macro:

  • Building a DSL
  • Reducing massive boilerplate (10x+ savings)
  • Compile-time optimizations

Elixir's principle: "first rule of macros: don't write macros." Use functions until you really, really need a macro.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…