Metaprogramming
Elixir code is represented as three-element tuples {name, metadata, args}:
38.1 Quoted expressions — the AST
Elixir code is represented as three-element tuples {name, metadata, args}:
Literals that quote to themselves: atoms, numbers, lists, strings, two-element tuples.
quote do: 1 + 2
# {:+, [context: Elixir, imports: [{1, Kernel}, {2, Kernel}]], [1, 2]}
quote do: sum(1, 2, 3)
# {:sum, [], [1, 2, 3]}38.2 unquote
x = 5
quote do: 1 + unquote(x) # {:+, _, [1, 5]}
args = [1, 2]
quote do: sum(unquote_splicing(args)) # sum(1, 2)
Code.eval_quoted(quote do: 1 + 2) # {3, []}
Macro.to_string(quote do: 1 + 2) # "1 + 2"38.3 Writing macros
Macros receive quoted arguments and return quoted code, expanded at compile time.
defmodule MyMacros do
defmacro unless_(condition, do: block) do
quote do
if !unquote(condition), do: unquote(block)
end
end
defmacro log_time(name, do: block) do
quote do
start = System.monotonic_time(:microsecond)
result = unquote(block)
IO.puts("#{unquote(name)} took #{System.monotonic_time(:microsecond) - start}µs")
result
end
end
end
require MyMacros
MyMacros.unless_ 1 > 2, do: IO.puts("ok")
MyMacros.log_time "sleep", do: Process.sleep(10)38.4 Hygiene
Variables defined inside a macro don't leak into the caller (start above won't clash with a caller's start). Escape deliberately with var!/1:
defmacro set_x(v), do: quote(do: var!(x) = unquote(v))38.5 Macro tools
Macro.expand_once(quote(do: unless(true, do: 1)), __ENV__)
Macro.expand(ast, __ENV__)
Macro.escape(%{a: 1}) # convert a runtime value into its quoted form
Macro.prewalk(ast, fn node -> node end), Macro.postwalk
Macro.var(:name, nil)
__CALLER__, __ENV__, __MODULE__, __DIR__, __STACKTRACE__
quote location: :keep, bind_quoted: [x: x] do ... end # bind_quoted evaluates once38.6 Compile-time code generation
String.Unicode, Mime, and many libraries generate thousands of function clauses this way — fast pattern-matched lookups.
defmodule HTTPStatus do
@codes %{200 => "OK", 404 => "Not Found", 500 => "Internal Server Error"}
for {code, msg} <- @codes do
def message(unquote(code)), do: unquote(msg)
end
def message(_), do: "Unknown"
end38.7 Module hooks
defmodule Plugin do
defmacro __before_compile__(_env) do
quote do
def registered, do: @plugins
end
end
end
# @before_compile Plugin, @after_compile, @on_definition38.8 Rules for macros
Don't write a macro if a function will do. Keep macro bodies small; delegate to functions.
Document generated functions.
Macros make code harder to debug — use them for DSLs and compile-time optimisations.
38.9 Runtime code
Code.eval_string/2 (avoid on untrusted input), Code.string_to_quoted/1, Code.compile_string/1, Code.ensure_loaded?/1, Module.create/3.