# Macros Patterns Patterns extracted from the Elixir standard library source code. --- ## 1. Context-Aware Macros (__CALLER__.context) **Source:** `lib/elixir/lib/kernel.ex` lines 2032–2067 (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` lines 5889–5966 **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` lines 5624–5640 (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` lines 4884–4901 **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` lines 2246–2273 (raise), 2319–2340 (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` lines 5384–5385 (def), 5415–5416 (defp), 5444–5445 (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` lines 5734–5745, `lib/elixir/lib/protocol.ex` lines 290–318 **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` line 162, `lib/elixir/lib/protocol.ex` lines 115–131 **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` lines 6130–6145 **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` lines 6500–6850+ (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` line 4509 **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` lines 507–520 **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.