Files
elixir-patterns/patterns/macros.md
T
Rodin 5f62dd0bf1 feat: add source hyperlinks + remove thin from-source.md
Every source reference now links to elixir-lang/elixir at commit f4e1b34.
122 hyperlinks across 11 topic files. Added PATTERN_COMPLETE sentinels.
Removed from-source.md (326 lines, shallow) — covered by existing files.
2026-04-30 14:43:56 -07:00

1109 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Macros Patterns
Patterns extracted from the Elixir standard library source code.
---
## 1. Context-Aware Macros (__CALLER__.context)
**Source:** [lib/elixir/lib/kernel.ex#L2032](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L2032) (or/and operators)
**What it does:** Macros check `__CALLER__.context` to determine if they're being invoked in a guard, match, or normal context, and generate different code accordingly.
**Why:** Elixir guards have restricted syntax (no function calls, only BIFs). A macro like `or` must emit `:erlang.orelse/2` in guards but can use a `case` expression in normal code. Context-awareness lets one macro serve multiple contexts correctly.
**Anti-pattern:** Writing macros that only work in one context and crash confusingly in others, or ignoring guard context entirely.
**Code example:**
```elixir
defmacro left or right do
case __CALLER__.context do
nil -> build_boolean_check(:or, left, true, right)
:match -> invalid_match!(:or)
:guard -> quote(do: :erlang.orelse(unquote(left), unquote(right)))
end
end
defmacro left and right do
case __CALLER__.context do
nil -> build_boolean_check(:and, left, right, false)
:match -> invalid_match!(:and)
:guard -> quote(do: :erlang.andalso(unquote(left), unquote(right)))
end
end
```
### When to Use
**Triggers:**
- A macro must behave differently in guards vs normal code
- You're writing an operator-like macro that users will put in guard clauses
- The same syntax needs different compilation strategies per context
**Example — before:**
```elixir
# Macro that only works in normal code, crashes in guards
defmacro my_or(left, right) do
quote do
case unquote(left) do
x when x in [false, nil] -> unquote(right)
x -> x
end
end
end
```
**Example — after:**
```elixir
defmacro my_or(left, right) do
case __CALLER__.context do
nil -> quote do
case unquote(left) do
x when x in [false, nil] -> unquote(right)
x -> x
end
end
:guard -> quote(do: :erlang.orelse(unquote(left), unquote(right)))
:match -> raise ArgumentError, "cannot use or/2 in match"
end
end
```
### When NOT to Use
**Don't use this when:**
- Your macro will never be used in guards (most macros)
- A simple `defguard` would suffice for guard-only usage
**Over-application example:**
```elixir
# Checking __CALLER__.context in a macro that just generates module-level code
defmacro define_schema(fields) do
case __CALLER__.context do
nil -> generate_schema(fields)
:guard -> raise "can't use in guard" # Unnecessary
:match -> raise "can't use in match" # Unnecessary
end
end
```
**Better alternative:**
```elixir
defmacro define_schema(fields) do
generate_schema(fields)
end
```
**Why:** Context-checking adds complexity. Only use it when there's a legitimate code path for guards or matches. Module-level macros like schema definitions will never appear in guard context.
---
## 2. defguard — Macro for Guard-Safe Expressions
**Source:** [lib/elixir/lib/kernel.ex#L5889](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L5889)
**What it does:** `defguard` creates a public macro that the compiler verifies is valid in guard clauses. It raises at compile time if the guard body uses non-guard-safe expressions.
**Why:** Regular macros have no guard validation. `defguard` provides compile-time safety: you can't accidentally create a "guard" that uses `IO.puts` or `Enum.map`. The result can be used both in guards and normal code.
**Anti-pattern:** Defining guard-like functions with `defmacro` and no validation. Users will discover at runtime (or never) that the macro isn't guard-safe.
**Code example:**
```elixir
defmodule Integer.Guards do
defguard is_even(value) when is_integer(value) and rem(value, 2) == 0
end
defmodule Collatz do
import Integer.Guards
def converge(n) when n > 0, do: step(n, 0)
defp step(1, step_count), do: step_count
defp step(n, step_count) when is_even(n) do
step(div(n, 2), step_count + 1)
end
defp step(n, step_count) do
step(3 * n + 1, step_count + 1)
end
end
```
### When to Use
**Triggers:**
- You need a guard-safe check that combines multiple guard expressions
- Multiple modules share the same guard condition
- The guard logic is complex enough to warrant a name
**Example — before:**
```elixir
# Repeating guard logic everywhere
def process(n) when is_integer(n) and rem(n, 2) == 0 do
# handle even...
end
def validate(n) when is_integer(n) and rem(n, 2) == 0 do
# validate even...
end
```
**Example — after:**
```elixir
defmodule Guards do
defguard is_even(n) when is_integer(n) and rem(n, 2) == 0
end
import Guards
def process(n) when is_even(n), do: # ...
def validate(n) when is_even(n), do: # ...
```
### When NOT to Use
**Don't use this when:**
- The check isn't guard-safe (calls functions, uses try/rescue, etc.)
- The guard is used in one place and is already readable
- You need runtime logic like database lookups in the check
**Over-application example:**
```elixir
# Trying to use defguard for something that needs runtime state
defguard is_admin(user_id) when user_id in @admin_ids
# @admin_ids is a module attribute — frozen at compile time!
```
**Better alternative:**
```elixir
def admin?(user_id), do: user_id in load_admin_ids()
def process(request) do
if admin?(request.user_id), do: # ...
end
```
**Why:** `defguard` expressions are evaluated at compile time with only BIF access. If your "guard" needs runtime data, config, or database state, it's not a guard — it's a function.
---
## 3. quote + unquote for Code Generation
**Source:** [lib/elixir/lib/kernel.ex#L5624](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L5624) (defstruct)
**What it does:** `quote bind_quoted: [fields: fields]` captures the macro argument into a variable available inside the quoted block. `unquote` injects computed values back into the AST.
**Why:** `bind_quoted` is preferred over raw `unquote` for macro arguments because it evaluates the expression exactly once and binds it to a variable. This prevents double-evaluation bugs and makes the generated code clearer.
**Anti-pattern:** Using `unquote(expr)` multiple times in a quote block when `expr` has side effects or is expensive — it will be evaluated multiple times in the expansion.
**Code example:**
```elixir
defmacro defstruct(fields) do
quote bind_quoted: [fields: fields, bootstrapped?: bootstrapped?(Enum)] do
{struct, derive, escaped_struct, kv, body} =
Kernel.Utils.defstruct(__MODULE__, fields, bootstrapped?, __ENV__)
case derive do
[] -> :ok
_ -> Protocol.__derive__(derive, __MODULE__, __ENV__)
end
def __struct__(), do: unquote(escaped_struct)
def __struct__(unquote(kv)), do: unquote(body)
Kernel.Utils.announce_struct(__MODULE__)
struct
end
end
```
### When to Use
**Triggers:**
- A macro receives arguments that should be evaluated once in the expansion
- You're generating code that interpolates compile-time values into runtime code
- The macro argument has side effects or is expensive to compute
**Example — before:**
```elixir
# unquote(fields) evaluated twice
defmacro setup(fields) do
quote do
validated = validate(unquote(fields))
@fields unquote(fields) # fields expression runs again!
end
end
```
**Example — after:**
```elixir
defmacro setup(fields) do
quote bind_quoted: [fields: fields] do
validated = validate(fields)
@fields fields # Same bound value, no double-evaluation
end
end
```
### When NOT to Use
**Don't use this when:**
- You need to unquote into a pattern match position (bind_quoted can't do this)
- The expression is a simple literal or variable with no side effects
- You're building complex AST where you need fine-grained unquote placement
**Over-application example:**
```elixir
# bind_quoted fails in pattern positions
defmacro match_field(field, value) do
quote bind_quoted: [field: field, value: value] do
def handle(%{field => result}) when result == value do
result
end
end
end
```
**Better alternative:**
```elixir
defmacro match_field(field, value) do
quote do
def handle(%{unquote(field) => result}) when result == unquote(value) do
result
end
end
end
```
**Why:** `bind_quoted` wraps values in regular variable bindings, which can't appear in pattern match positions. When you need to inject into patterns or guards, use direct `unquote`.
---
## 4. var! for Breaking Hygiene
**Source:** [lib/elixir/lib/kernel.ex#L4884](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L4884)
**What it does:** `var!` marks a variable inside `quote` as unhygienic — it will refer to the variable in the *caller's* scope rather than creating a new hygienic binding.
**Why:** Macro hygiene prevents accidental variable capture. But sometimes you *want* to reference the caller's variables (e.g., `Kernel.var!(example) = 1` in tests, or injecting into module scope). `var!` is the explicit escape hatch.
**Anti-pattern:** Using `var!` casually. Every use breaks hygiene and creates implicit coupling between macro and caller. Prefer passing values through macro arguments instead.
**Code example:**
```elixir
defmacro var!(var, context \\ nil)
defmacro var!({name, meta, atom}, context) when is_atom(name) and is_atom(atom) do
# Remove counter and force them to be vars
meta = :lists.keydelete(:counter, 1, meta)
meta = :lists.keystore(:if_undefined, 1, meta, {:if_undefined, :raise})
case Macro.expand(context, __CALLER__) do
context when is_atom(context) ->
{name, meta, context}
other ->
raise ArgumentError,
"expected var! context to expand to an atom, got: #{Macro.to_string(other)}"
end
end
```
### When to Use
**Triggers:**
- A macro must inject code that references a variable from the caller's scope
- Building test/assertion macros where you need access to the test binding
- Module-level macros that accumulate into a module attribute the caller defines
**Example — before:**
```elixir
# Hygienic variable — creates a NEW binding, doesn't access caller's
defmacro assert_stored(key) do
quote do
assert store[unquote(key)] # 'store' is hygienic — undefined in caller!
end
end
```
**Example — after:**
```elixir
defmacro assert_stored(key) do
quote do
assert var!(store)[unquote(key)] # Accesses caller's 'store' variable
end
end
```
### When NOT to Use
**Don't use this when:**
- You can pass the value as a macro argument instead
- The macro doesn't need to reference caller-scope variables
- You're creating new bindings (just let hygiene work naturally)
**Over-application example:**
```elixir
# Using var! when the value could just be passed as an argument
defmacro double_it do
quote do
var!(x) * 2 # Assumes caller has 'x' — fragile!
end
end
# Caller must have 'x' defined
x = 5
double_it() # => 10
```
**Better alternative:**
```elixir
defmacro double_it(value) do
quote do
unquote(value) * 2
end
end
double_it(5) # => 10, explicit, no hidden dependencies
```
**Why:** Every `var!` creates an invisible contract between macro and caller. Prefer explicit arguments. Reserve `var!` for cases where the contract is part of the documented API (like ExUnit's test context).
---
## 5. Macro Expanding with Macro.expand
**Source:** [lib/elixir/lib/kernel.ex#L2246](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L2246) (raise), 23192340 (reraise)
**What it does:** Before generating code, the macro calls `Macro.expand(message, __CALLER__)` to resolve aliases at compile time. This determines the code path: if the expanded value is an atom (module name), it generates exception-specific code.
**Why:** Macros receive AST, not values. An alias like `MyError` is `{:__aliases__, _, [:MyError]}` in AST form. Expanding it resolves it to the actual module atom, enabling compile-time decisions about what code to generate.
**Anti-pattern:** Pattern-matching on raw AST shapes without expanding first. This breaks when users pass aliases, module attributes, or other compile-time expressions.
**Code example:**
```elixir
defmacro raise(message) do
erlang_error = fn expr ->
quote do: :erlang.error(unquote(expr), :none, error_info: %{module: Exception})
end
case Macro.expand(message, __CALLER__) do
atom when is_atom(atom) ->
# It's a module — generate Module.exception([])
erlang_error.(quote do: unquote(atom).exception([]))
_ ->
# It's a string or expression — wrap in RuntimeError
erlang_error.(quote do: RuntimeError.exception(unquote(message)))
end
end
```
### When to Use
**Triggers:**
- Your macro receives user input that could be an alias, module attribute, or compile-time expression
- You need to make compile-time decisions based on what type of thing was passed
- Distinguishing between module names and runtime expressions in macro arguments
**Example — before:**
```elixir
# Trying to pattern-match on the raw AST — breaks with aliases
defmacro wrap_error(module) when is_atom(module) do
# Never matches! Aliases are {:__aliases__, _, [...]} in AST
quote do: %{__exception__: true, __struct__: unquote(module)}
end
```
**Example — after:**
```elixir
defmacro wrap_error(module_or_msg) do
case Macro.expand(module_or_msg, __CALLER__) do
atom when is_atom(atom) ->
quote do: unquote(atom).exception([])
_ ->
quote do: RuntimeError.exception(unquote(module_or_msg))
end
end
```
### When NOT to Use
**Don't use this when:**
- The macro argument is always a literal (string, number)
- You don't need compile-time branching on the argument's type
- The argument should remain unevaluated (lazy evaluation semantics)
**Over-application example:**
```elixir
# Expanding an argument that's meant to be a runtime expression
defmacro log(message) do
expanded = Macro.expand(message, __CALLER__)
quote do
Logger.info(unquote(expanded))
end
end
```
**Better alternative:**
```elixir
defmacro log(message) do
quote do
Logger.info(unquote(message)) # Let it evaluate at runtime
end
end
```
**Why:** `Macro.expand` resolves compile-time constructs (aliases, module attributes). Expanding function calls or complex expressions can produce confusing results. Only expand when you need to determine the *type* of the argument at compile time.
---
## 6. assert_no_match_or_guard_scope Pattern
**Source:** [lib/elixir/lib/kernel.ex#L5384](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L5384) (def), 54155416 (defp), 54445445 (defmacro)
**What it does:** Macros that define module-level constructs (def, defp, defmacro, defmacrop) immediately assert they're not being called inside a guard or match context.
**Why:** Calling `def` inside a guard clause makes no sense but would produce a confusing error much later. Failing early with a clear message ("cannot invoke def/2 inside a guard") is better than a cryptic expansion error.
**Anti-pattern:** Not validating context at the top of macros that are context-sensitive. Let errors surface at the point of misuse, not deep in expansion.
**Code example:**
```elixir
defmacro def(call, expr \\ nil) do
assert_no_match_or_guard_scope(__CALLER__.context, "def/2")
define(:def, call, expr, __CALLER__)
end
defmacro defmacro(call, expr \\ nil) do
assert_no_match_or_guard_scope(__CALLER__.context, "defmacro/2")
define(:defmacro, call, expr, __CALLER__)
end
```
### When to Use
**Triggers:**
- Your macro defines module-level constructs (functions, types, modules)
- The macro makes no sense inside a guard or pattern match
- Early failure with a clear message prevents confusing downstream errors
**Example — before:**
```elixir
# No context validation — weird error deep in expansion
defmacro defroute(path, handler) do
quote do
@routes [{unquote(path), unquote(handler)} | @routes]
end
end
# User accidentally writes:
def check(x) when defroute("/foo", Foo), do: x # Cryptic error
```
**Example — after:**
```elixir
defmacro defroute(path, handler) do
assert_no_match_or_guard_scope(__CALLER__.context, "defroute/2")
quote do
@routes [{unquote(path), unquote(handler)} | @routes]
end
end
# Now gives: "cannot invoke defroute/2 inside a guard"
```
### When NOT to Use
**Don't use this when:**
- Your macro IS designed to work in guards (use `defguard` or handle context)
- Your macro is designed to work in match context (like custom patterns)
- The macro is simple enough that misuse produces a clear error naturally
**Over-application example:**
```elixir
# Adding guard assertion to a macro that could legitimately work anywhere
defmacro debug(expr) do
assert_no_match_or_guard_scope(__CALLER__.context, "debug/1")
quote do
IO.inspect(unquote(expr), label: unquote(Macro.to_string(expr)))
end
end
```
**Better alternative:**
```elixir
# Let it work anywhere it naturally can
defmacro debug(expr) do
quote do
IO.inspect(unquote(expr), label: unquote(Macro.to_string(expr)))
end
end
```
**Why:** Only add context assertions when misuse would produce confusing errors. If your macro naturally fails with a clear error in the wrong context, the assertion adds noise without value.
---
## 7. Protocol Definition as a Macro (defprotocol)
**Source:** [lib/elixir/lib/kernel.ex#L5734](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L5734), [lib/elixir/lib/protocol.ex#L290](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/protocol.ex#L290)
**What it does:** `defprotocol` is a macro that creates a module with auto-generated dispatch functions. Inside the protocol, `def` is redefined as a macro that generates both a callback spec and the dispatch implementation.
**Why:** Protocols need complex machinery: type dispatch, consolidation, fallback handling. Wrapping this in macros means users write simple `defprotocol` + `def` syntax while the system generates efficient dispatch code.
**Anti-pattern:** Trying to implement protocol-like dispatch with regular modules and manual case statements. Use `defprotocol` — it handles consolidation, error messages, and type dispatch.
**Code example:**
```elixir
# What the user writes:
defprotocol Size do
@doc "Calculates the size of a data structure"
def size(data)
end
# What the protocol's def macro generates internally:
quote generated: true do
@__functions__ [{name, arity} | @__functions__]
# Generate a fake definition with the user signature (for docs)
Kernel.def(unquote(name)(unquote_splicing(args)))
# Generate the actual dispatch implementation
Kernel.def unquote(name)(unquote_splicing(call_args)) do
impl_for!(term).unquote(name)(unquote_splicing(call_args))
end
# Copy spec as callback
Module.spec_to_callback(__MODULE__, {name, arity}) ||
@callback unquote(name)(unquote_splicing(type_args)) :: term
end
```
### When to Use
**Triggers:**
- You're building a type system or dispatch mechanism that operates on open-ended types
- Users need to add behavior for their own types without modifying your library
- You need dynamic dispatch based on the data type with compile-time consolidation
**Example — before:**
```elixir
# Manual dispatch via case — closed, must modify to extend
def serialize(data) do
case data do
%User{} -> serialize_user(data)
%Post{} -> serialize_post(data)
_ -> raise "don't know how to serialize"
end
end
```
**Example — after:**
```elixir
defprotocol Serializable do
@doc "Converts a data structure to a wire format"
def serialize(data)
end
defimpl Serializable, for: User do
def serialize(%User{name: name, email: email}), do: %{name: name, email: email}
end
# Anyone can add implementations for their own types
```
### When NOT to Use
**Don't use this when:**
- You have a closed set of types that won't be extended by users
- Simple pattern matching or behaviours solve the problem
- The dispatch doesn't depend on the first argument's type
**Over-application example:**
```elixir
# Protocol for internal types that will never be extended
defprotocol InternalFormat do
def format(thing)
end
defimpl InternalFormat, for: [Map, List, BitString] do
# Only ever these three types, controlled by us
end
```
**Better alternative:**
```elixir
# Simple function with pattern matching — less machinery
def format(%{} = map), do: # ...
def format(list) when is_list(list), do: # ...
def format(binary) when is_binary(binary), do: # ...
```
**Why:** Protocols add indirection and compilation complexity (consolidation). If the type set is closed and you control all implementations, pattern matching is simpler, faster, and easier to understand.
---
## 8. @fallback_to_any in Protocols
**Source:** [lib/elixir/lib/inspect.ex#L162](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/inspect.ex#L162), [lib/elixir/lib/protocol.ex#L115](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/protocol.ex#L115)
**What it does:** Sets `@fallback_to_any true` inside a protocol definition to enable a default implementation via `defimpl Protocol, for: Any`.
**Why:** Some protocols (like `Inspect`) should work on *any* value rather than raising. The fallback provides a reasonable default (e.g., inspecting structs generically) while still allowing specific implementations to override.
**Anti-pattern:** Using `@fallback_to_any true` when failing explicitly is better. As the docs say: "it makes no sense to say a PID has a size of 0." Only use fallbacks when a generic implementation is genuinely useful.
**Code example:**
```elixir
defprotocol Inspect do
@moduledoc """
The `Inspect` protocol converts an Elixir data structure into an
algebra document.
"""
# Handle structs in Any
@fallback_to_any true
@spec inspect(t, Inspect.Opts.t()) ::
Inspect.Algebra.t() | {Inspect.Algebra.t(), Inspect.Opts.t()}
def inspect(term, opts)
end
# The fallback implementation:
defimpl Inspect, for: Any do
# Generic struct inspection using #ModuleName<...> notation
def inspect(%module{} = struct, opts) do
# ...
end
end
```
### When to Use
**Triggers:**
- A protocol should handle ANY value rather than crashing on unknown types
- The generic behavior is genuinely useful (inspect, encode, display)
- You want a safe default that users can override for specific types
**Example — before:**
```elixir
defprotocol Displayable do
def display(term)
end
# Every new struct crashes until someone adds an implementation:
# ** (Protocol.UndefinedError) protocol Displayable not implemented for %MyStruct{}
```
**Example — after:**
```elixir
defprotocol Displayable do
@fallback_to_any true
def display(term)
end
defimpl Displayable, for: Any do
def display(term), do: inspect(term) # Reasonable fallback
end
```
### When NOT to Use
**Don't use this when:**
- The protocol operation doesn't make sense for arbitrary types
- Silently returning a default would hide bugs
- You want to force implementors to think about their implementation
**Over-application example:**
```elixir
defprotocol Saveable do
@fallback_to_any true
def save(data, repo)
end
defimpl Saveable, for: Any do
def save(_data, _repo), do: :ok # Silently does nothing!
end
# Dangerous: %UnknownStruct{} |> Saveable.save(repo) succeeds but saves nothing
```
**Better alternative:**
```elixir
defprotocol Saveable do
# No fallback — forces explicit implementation
def save(data, repo)
end
# Clear error on missing implementation:
# ** (Protocol.UndefinedError) protocol Saveable not implemented for %Foo{}
```
**Why:** Fallbacks that silently succeed are a bug factory. Use `@fallback_to_any` only when the default behavior is *genuinely useful* (like `Inspect`), not when "do nothing" masks errors.
---
## 9. use/2 as Macro Injection Point
**Source:** [lib/elixir/lib/kernel.ex#L6130](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L6130)
**What it does:** `use Module, opts` is a macro that `require`s the module then calls `Module.__using__(opts)`. The `__using__/1` macro returns quoted code injected into the caller.
**Why:** This is Elixir's extension/plugin mechanism. It's explicit (you can see what `use` does by reading `__using__/1`), composable (multiple `use` calls stack), and documented (the admonition convention).
**Anti-pattern:** Using `use` when `import` or `alias` would suffice. `use` should be reserved for cases that need module attributes, callbacks, or compile hooks.
**Code example:**
```elixir
# The implementation of use/2:
defmacro use(module, opts \\ []) do
calls =
Enum.map(expand_aliases(module, __CALLER__), fn
expanded when is_atom(expanded) ->
quote do
require unquote(expanded)
unquote(expanded).__using__(unquote(opts))
end
end)
quote(do: (unquote_splicing(calls)))
end
# A typical __using__ implementation:
defmodule GenServer do
defmacro __using__(_opts) do
quote do
@behaviour GenServer
def child_spec(init_arg) do
# ...default child spec...
end
defoverridable child_spec: 1
end
end
end
```
### When to Use
**Triggers:**
- Your module needs to inject behaviours, default function implementations, or compile hooks
- Users need a one-line "opt in" that sets up complex module infrastructure
- The setup requires `@behaviour`, `@before_compile`, `defoverridable`, or module attributes
**Example — before:**
```elixir
# User must remember all the boilerplate
defmodule MyWorker do
@behaviour GenServer
def child_spec(init_arg) do
%{id: __MODULE__, start: {__MODULE__, :start_link, [init_arg]}}
end
def init(state), do: {:ok, state}
# ... more defaults ...
end
```
**Example — after:**
```elixir
defmodule MyWorker do
use GenServer
# All boilerplate injected, just implement what you need
def init(state), do: {:ok, state}
end
```
### When NOT to Use
**Don't use this when:**
- `import` or `alias` is all you need (no module attributes, no callbacks)
- The module doesn't need to inject code — just provides functions
- You're using `use` to inject large amounts of invisible code that surprises users
**Over-application example:**
```elixir
# use for something that should just be import
defmodule MyHelpers do
defmacro __using__(_opts) do
quote do
import MyHelpers # That's literally all it does
end
end
end
# User writes: use MyHelpers
# When they could just write: import MyHelpers
```
**Better alternative:**
```elixir
# Just tell users to import directly
defmodule MyHelpers do
def format_date(date), do: # ...
def format_money(amount), do: # ...
end
# In user's module:
import MyHelpers
```
**Why:** `use` implies "this module needs setup that goes beyond importing functions." If all you're doing is importing, `use` adds a layer of indirection that obscures what's happening. Reserve `use` for genuine module setup.
---
## 10. Sigil Macros (Pattern for DSL Literals)
**Source:** [lib/elixir/lib/kernel.ex#L6500](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L6500)+ (sigil_S, sigil_s, sigil_r, sigil_D, etc.)
**What it does:** Each sigil (`~r`, `~D`, `~s`, etc.) is implemented as a `defmacro sigil_X(term, modifiers)` that receives the raw string content and modifier characters, then transforms them at compile time.
**Why:** Sigils provide compile-time validated literals. `~D[2024-01-15]` is parsed and validated during compilation — invalid dates won't even compile. The macro pattern means new sigils can be added by any module.
**Anti-pattern:** Parsing literal values at runtime when they're known at compile time. Sigils shift validation left to compilation.
**Code example:**
```elixir
defmacro sigil_D(date_string, modifiers)
defmacro sigil_D({:<<>>, _, [string]}, []) do
# Parses and validates at compile time
{{:., _, [Date, :sigil_D]}, _, [{:<<>>, _, [string]}, []]}
end
# Usage:
date = ~D[2024-01-15] # Compile-time validated Date struct
```
### When to Use
**Triggers:**
- You have compile-time-known literal values that benefit from validation at compile time
- A domain has a specific syntax for literals (dates, regex, URIs, colors)
- You want zero runtime parsing cost for constant values
**Example — before:**
```elixir
# Runtime parsing — fails at runtime, no compile-time validation
def deadline do
Date.from_iso8601!("2024-13-45") # Explodes at runtime
end
```
**Example — after:**
```elixir
def deadline do
~D[2024-13-45] # Compile error! Invalid date caught immediately
end
```
### When NOT to Use
**Don't use this when:**
- Values come from runtime input (user data, config files, databases)
- The syntax doesn't provide meaningful compile-time validation
- A regular function or struct literal is equally clear
**Over-application example:**
```elixir
# Sigil for something with no compile-time validation benefit
defmacro sigil_u({:<<>>, _, [string]}, []) do
quote do: String.upcase(unquote(string))
end
name = ~u"hello" # Just uppercases a string... why not String.upcase("hello")?
```
**Better alternative:**
```elixir
name = String.upcase("hello")
```
**Why:** Sigils shine when they validate or transform at compile time in ways that prevent runtime errors. A sigil that just wraps a function call without validation adds syntax without value.
---
## 11. Pipe Operator as a Macro
**Source:** [lib/elixir/lib/kernel.ex#L4509](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L4509)
**What it does:** The `|>` pipe operator is a macro that rewrites `left |> right` into `right(left)`, inserting the left expression as the first argument of the right expression.
**Why:** It's purely syntactic transformation — there's no runtime dispatch. Being a macro means it's zero-cost at runtime while providing the ergonomic left-to-right reading order.
**Anti-pattern:** Implementing operator-like syntax as runtime function calls when they could be compile-time transformations.
**Code example:**
```elixir
defmacro left |> right do
[{h, _} | t] = Macro.unpipe({:|>, [], [left, right]})
fun = fn {x, pos}, acc ->
Macro.pipe(acc, x, pos)
end
:lists.foldl(fun, h, t)
end
# Transforms at compile time:
# "hello" |> String.upcase() |> String.reverse()
# becomes:
# String.reverse(String.upcase("hello"))
```
### When to Use
**Triggers:**
- You want a zero-cost syntactic transformation (no runtime dispatch)
- The transformation is purely structural (rewriting argument positions)
- An operator or DSL benefits from left-to-right readability
**Example — before:**
```elixir
# Deeply nested function calls — read inside-out
String.trim(String.downcase(String.replace(input, "_", " ")))
```
**Example — after:**
```elixir
input
|> String.replace("_", " ")
|> String.downcase()
|> String.trim()
```
### When NOT to Use
**Don't use this when:**
- The pipe has only one step (just call the function directly)
- The piped value isn't the first argument (requires anonymous function wrappers)
- You're piping into a macro that needs special AST handling
**Over-application example:**
```elixir
# Single-step pipe — adds noise
user
|> Map.get(:name)
# Piping where the value isn't first argument
data
|> Jason.encode!()
|> send_to(socket) # Is this send_to(encoded, socket)? Unclear.
```
**Better alternative:**
```elixir
# Single step — just call it
name = Map.get(user, :name)
# When argument position is unclear, break the pipe
encoded = data |> build_map() |> Jason.encode!()
send_to(socket, encoded) # Clear which arg is which
```
**Why:** Pipes optimize for readability of *sequential transformations*. When the data doesn't flow naturally as the first argument, or there's only one step, the pipe adds syntactic overhead without improving clarity.
---
## 12. Macro.generate_unique_arguments for Hygiene
**Source:** [lib/elixir/lib/macro.ex#L507](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/macro.ex#L507)
**What it does:** `Macro.generate_unique_arguments(n, context)` creates `n` unique variable AST nodes that won't conflict with any user variables.
**Why:** When a macro needs to generate variable bindings in quoted code, using `generate_unique_arguments` guarantees hygiene. The variables get unique counters that can't clash with user-defined names.
**Anti-pattern:** Using hardcoded variable names in macros (like `x`, `acc`, `state`) which can shadow or be shadowed by user variables.
**Code example:**
```elixir
@doc """
Generates a list of `n` unique arguments.
## Examples
iex> [var1, var2] = Macro.generate_unique_arguments(2, __CALLER__.module)
"""
@doc since: "1.11.3"
@spec generate_unique_arguments(0, context :: atom) :: []
@spec generate_unique_arguments(pos_integer, context) ::
[{atom, [counter: integer], context}, ...]
when context: atom
```
### When to Use
**Triggers:**
- Your macro generates variable bindings in quoted code
- You need `n` variables for a generated function clause or pattern
- The macro expands in user code where variable names could clash
**Example — before:**
```elixir
# Hardcoded variable names — can clash with caller's variables
defmacro curry(fun, arity) do
args = Enum.map(1..arity, fn i -> Macro.var(:"arg\#{i}", __MODULE__) end)
quote do
fn unquote_splicing(args) -> unquote(fun).(unquote_splicing(args)) end
end
end
```
**Example — after:**
```elixir
defmacro curry(fun, arity) do
args = Macro.generate_unique_arguments(arity, __CALLER__.module)
quote do
fn unquote_splicing(args) -> unquote(fun).(unquote_splicing(args)) end
end
end
```
### When NOT to Use
**Don't use this when:**
- You're using `bind_quoted` (it handles hygiene for you)
- The variable is accessed via `var!` (intentionally unhygienic)
- You only need one variable (a simple `quote do: var = ... end` is hygienic by default)
**Over-application example:**
```elixir
# Using generate_unique_arguments for a single binding
defmacro time_it(expr) do
[start] = Macro.generate_unique_arguments(1, __CALLER__.module)
quote do
unquote(start) = System.monotonic_time()
result = unquote(expr)
IO.puts("Took \#{System.monotonic_time() - unquote(start)}")
result
end
end
```
**Better alternative:**
```elixir
# Regular quote hygiene handles single variables fine
defmacro time_it(expr) do
quote do
start = System.monotonic_time()
result = unquote(expr)
IO.puts("Took \#{System.monotonic_time() - start}")
result
end
end
```
**Why:** Variables created in `quote` are already hygienic by default — they can't clash with caller variables. `generate_unique_arguments` is needed when you're generating *multiple* variables dynamically (e.g., function parameters for a generated clause) where you need distinct names that also interoperate correctly.
<!-- PATTERN_COMPLETE -->