Reading — step 1 of 4
Learn
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 macro — defmacro 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…