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.
This commit is contained in:
+10
-8
@@ -4,7 +4,7 @@ How behaviours are designed, implemented, and used in Elixir core and Phoenix.
|
|||||||
|
|
||||||
## 1. Behaviour Definition with `@callback`
|
## 1. Behaviour Definition with `@callback`
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:577-812` (all callback definitions)
|
**Source:** [lib/elixir/lib/gen_server.ex#L577](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L577) (all callback definitions)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
@callback init(init_arg :: term) ::
|
@callback init(init_arg :: term) ::
|
||||||
@@ -89,7 +89,7 @@ end
|
|||||||
|
|
||||||
## 2. `@optional_callbacks` for Extensibility
|
## 2. `@optional_callbacks` for Extensibility
|
||||||
|
|
||||||
**Source:** `lib/phoenix/channel.ex:442-448`
|
**Source:** [lib/phoenix/channel.ex#L442](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/phoenix/channel.ex#L442)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
@optional_callbacks handle_in: 3,
|
@optional_callbacks handle_in: 3,
|
||||||
@@ -168,7 +168,7 @@ end
|
|||||||
|
|
||||||
## 3. `@behaviour` Declaration in `__using__`
|
## 3. `@behaviour` Declaration in `__using__`
|
||||||
|
|
||||||
**Source:** `lib/phoenix/channel.ex:450-453`
|
**Source:** [lib/phoenix/channel.ex#L450](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/phoenix/channel.ex#L450)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
defmacro __using__(opts \\ []) do
|
defmacro __using__(opts \\ []) do
|
||||||
@@ -182,7 +182,7 @@ defmacro __using__(opts \\ []) do
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:836`
|
**Source:** [lib/elixir/lib/gen_server.ex#L836](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L836)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
quote location: :keep, bind_quoted: [opts: opts] do
|
quote location: :keep, bind_quoted: [opts: opts] do
|
||||||
@@ -270,7 +270,7 @@ end
|
|||||||
|
|
||||||
## 4. Default Implementations via `defoverridable`
|
## 4. Default Implementations via `defoverridable`
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:849`
|
**Source:** [lib/elixir/lib/gen_server.ex#L849](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L849)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
def child_spec(init_arg) do
|
def child_spec(init_arg) do
|
||||||
@@ -358,7 +358,7 @@ end
|
|||||||
|
|
||||||
## 5. Phoenix Channel: Behaviour + Process + Protocol
|
## 5. Phoenix Channel: Behaviour + Process + Protocol
|
||||||
|
|
||||||
**Source:** `lib/phoenix/channel.ex:364-448` (full callback set)
|
**Source:** [lib/phoenix/channel.ex#L364](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/phoenix/channel.ex#L364) (full callback set)
|
||||||
|
|
||||||
The Channel behaviour combines:
|
The Channel behaviour combines:
|
||||||
1. **Required callback:** `join/3` (authorization gate)
|
1. **Required callback:** `join/3` (authorization gate)
|
||||||
@@ -475,7 +475,7 @@ end
|
|||||||
|
|
||||||
## 6. Callback Documentation Pattern
|
## 6. Callback Documentation Pattern
|
||||||
|
|
||||||
**Source:** `lib/phoenix/channel.ex:350-363` (join callback doc)
|
**Source:** [lib/phoenix/channel.ex#L350](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/phoenix/channel.ex#L350) (join callback doc)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
@doc """
|
@doc """
|
||||||
@@ -591,7 +591,7 @@ This callback is required.
|
|||||||
|
|
||||||
## 7. Phoenix.Endpoint: Behaviour as Interface Contract
|
## 7. Phoenix.Endpoint: Behaviour as Interface Contract
|
||||||
|
|
||||||
**Source:** `lib/phoenix/endpoint.ex:408`
|
**Source:** [lib/phoenix/endpoint.ex#L408](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/phoenix/endpoint.ex#L408)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
defmacro __using__(opts) do
|
defmacro __using__(opts) do
|
||||||
@@ -677,3 +677,5 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Why:** The more code a `use` macro generates, the harder it is to debug. If users regularly need to read the generated code to understand failures, the abstraction is leaking. Reserve heavy `use` macros for well-established patterns (GenServer, Endpoint, Channel) where the community has internalized the mental model.
|
**Why:** The more code a `use` macro generates, the harder it is to debug. If users regularly need to read the generated code to understand failures, the abstraction is leaking. Reserve heavy `use` macros for well-established patterns (GenServer, Endpoint, Channel) where the community has internalized the mental model.
|
||||||
|
|
||||||
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
+14
-12
@@ -6,7 +6,7 @@ Patterns extracted from Elixir's standard library source code.
|
|||||||
|
|
||||||
## 1. List-Specialized Clause Before Protocol Dispatch
|
## 1. List-Specialized Clause Before Protocol Dispatch
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/enum.ex` lines 1723–1733
|
**Source:** [lib/elixir/lib/enum.ex#L1723](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/enum.ex#L1723)
|
||||||
|
|
||||||
**What it does:** Every public Enum function defines a `when is_list(enumerable)` clause first, then a generic fallback that uses the Enumerable protocol.
|
**What it does:** Every public Enum function defines a `when is_list(enumerable)` clause first, then a generic fallback that uses the Enumerable protocol.
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ end
|
|||||||
|
|
||||||
## 2. Build-Then-Reverse (Cons-Cell Accumulation)
|
## 2. Build-Then-Reverse (Cons-Cell Accumulation)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/enum.ex` lines 1124, 1733, 2697
|
**Source:** [lib/elixir/lib/enum.ex#L1124](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/enum.ex#L1124), 1733, 2697
|
||||||
|
|
||||||
**What it does:** Accumulates results by prepending to a list (`[x | acc]`), then reverses at the end.
|
**What it does:** Accumulates results by prepending to a list (`[x | acc]`), then reverses at the end.
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ end
|
|||||||
|
|
||||||
## 3. Pipeline for Linear Transformations, Bare Calls for Control Flow
|
## 3. Pipeline for Linear Transformations, Bare Calls for Control Flow
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/enum.ex` lines 1684–1685, 1551, vs 496–502
|
**Source:** [lib/elixir/lib/enum.ex#L1684](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/enum.ex#L1684), 1551, vs 496–502
|
||||||
|
|
||||||
**What it does:** Elixir core uses `|>` when data flows linearly through 2+ transformations. It does NOT use `|>` for single-step operations or when the first argument is computed by a `case`/`if`/`with`.
|
**What it does:** Elixir core uses `|>` when data flows linearly through 2+ transformations. It does NOT use `|>` for single-step operations or when the first argument is computed by a `case`/`if`/`with`.
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ end
|
|||||||
|
|
||||||
## 4. Pipeline Ending with `|> elem(1)` (Protocol Reduce Unwrap)
|
## 4. Pipeline Ending with `|> elem(1)` (Protocol Reduce Unwrap)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/enum.ex` lines 363, 403, 433, 468, 725, 1022, 2676
|
**Source:** [lib/elixir/lib/enum.ex#L363](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/enum.ex#L363), 403, 433, 468, 725, 1022, 2676
|
||||||
|
|
||||||
**What it does:** When calling `Enumerable.reduce/3` directly, the result is always `{:done | :halted | :suspended, acc}`. Core extracts the accumulator with `|> elem(1)`.
|
**What it does:** When calling `Enumerable.reduce/3` directly, the result is always `{:done | :halted | :suspended, acc}`. Core extracts the accumulator with `|> elem(1)`.
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ Enum.reduce([1, 2, 3], 0, &(&1 + &2)) |> elem(1)
|
|||||||
|
|
||||||
## 5. Private Helper Decomposition: Recursive Workers with Guards
|
## 5. Private Helper Decomposition: Recursive Workers with Guards
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/enum.ex` lines 4975–4995, 5025–5039
|
**Source:** [lib/elixir/lib/enum.ex#L4975](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/enum.ex#L4975), 5025–5039
|
||||||
|
|
||||||
**What it does:** Complex operations are split into a public entry point (with validation guards) and a private recursive worker function. The worker uses pattern matching on structure (empty list, head|tail) and guards on counters.
|
**What it does:** Complex operations are split into a public entry point (with validation guards) and a private recursive worker function. The worker uses pattern matching on structure (empty list, head|tail) and guards on counters.
|
||||||
|
|
||||||
@@ -435,7 +435,7 @@ def my_take(list, n), do: Enum.take(list, n)
|
|||||||
|
|
||||||
## 6. Enum vs Stream Decision Pattern
|
## 6. Enum vs Stream Decision Pattern
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/stream.ex` lines 1–80 (module docs), `lib/elixir/lib/enum.ex`
|
**Source:** [lib/elixir/lib/stream.ex#L1](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/stream.ex#L1) (module docs), `lib/elixir/lib/enum.ex`
|
||||||
|
|
||||||
**What it does:** Enum functions are eager (materialize intermediate lists). Stream functions are lazy (build computation recipes). Core uses Stream for:
|
**What it does:** Enum functions are eager (materialize intermediate lists). Stream functions are lazy (build computation recipes). Core uses Stream for:
|
||||||
- Infinite sequences (`cycle`, `iterate`, `repeatedly`)
|
- Infinite sequences (`cycle`, `iterate`, `repeatedly`)
|
||||||
@@ -528,7 +528,7 @@ config
|
|||||||
|
|
||||||
## 7. Map.update vs Map.put Decision Pattern
|
## 7. Map.update vs Map.put Decision Pattern
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/map.ex` lines 670–700
|
**Source:** [lib/elixir/lib/map.ex#L670](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/map.ex#L670)
|
||||||
|
|
||||||
**What it does:** `Map.update/4` transforms an existing value based on its current state. `Map.put/3` unconditionally sets a value regardless of current state.
|
**What it does:** `Map.update/4` transforms an existing value based on its current state. `Map.put/3` unconditionally sets a value regardless of current state.
|
||||||
|
|
||||||
@@ -603,7 +603,7 @@ Map.put(user, :name, new_name)
|
|||||||
|
|
||||||
## 8. Pattern Matching on Map Structure for Dispatch
|
## 8. Pattern Matching on Map Structure for Dispatch
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/map.ex` lines 398, 509, 586
|
**Source:** [lib/elixir/lib/map.ex#L398](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/map.ex#L398), 509, 586
|
||||||
|
|
||||||
**What it does:** Map functions use `case map do %{^key => value} -> ...` to dispatch on whether a key exists, rather than calling `has_key?` + conditional.
|
**What it does:** Map functions use `case map do %{^key => value} -> ...` to dispatch on whether a key exists, rather than calling `has_key?` + conditional.
|
||||||
|
|
||||||
@@ -700,7 +700,7 @@ end
|
|||||||
|
|
||||||
## 9. Delegating to Erlang BIFs with `defdelegate`
|
## 9. Delegating to Erlang BIFs with `defdelegate`
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/map.ex` lines 127, 143, 159, 173
|
**Source:** [lib/elixir/lib/map.ex#L127](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/map.ex#L127), 143, 159, 173
|
||||||
|
|
||||||
**What it does:** When an Erlang function already does exactly what's needed, Elixir delegates directly rather than wrapping.
|
**What it does:** When an Erlang function already does exactly what's needed, Elixir delegates directly rather than wrapping.
|
||||||
|
|
||||||
@@ -772,7 +772,7 @@ end
|
|||||||
|
|
||||||
## 10. Reduce as the Universal Primitive
|
## 10. Reduce as the Universal Primitive
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/enum.ex` lines 19–21, 2660–2676
|
**Source:** [lib/elixir/lib/enum.ex#L19](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/enum.ex#L19), 2660–2676
|
||||||
|
|
||||||
**What it does:** Nearly every Enum operation is built on top of `reduce`. The Enumerable protocol's core function is `reduce/3`. Everything else (`count`, `member?`, `slice`) is an optimization hint.
|
**What it does:** Nearly every Enum operation is built on top of `reduce`. The Enumerable protocol's core function is `reduce/3`. Everything else (`count`, `member?`, `slice`) is an optimization hint.
|
||||||
|
|
||||||
@@ -857,7 +857,7 @@ end
|
|||||||
|
|
||||||
## 11. Keyword Multi-Clause Guard Dispatch (String.split pattern)
|
## 11. Keyword Multi-Clause Guard Dispatch (String.split pattern)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/string.ex` lines 516–563
|
**Source:** [lib/elixir/lib/string.ex#L516](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/string.ex#L516)
|
||||||
|
|
||||||
**What it does:** Functions with many input shapes use multiple `def` clauses with guards to dispatch, handling each case distinctly rather than using internal `cond`/`case`.
|
**What it does:** Functions with many input shapes use multiple `def` clauses with guards to dispatch, handling each case distinctly rather than using internal `cond`/`case`.
|
||||||
|
|
||||||
@@ -949,7 +949,7 @@ end
|
|||||||
|
|
||||||
## 12. Lazy Private Helpers with `defp parts_to_index`
|
## 12. Lazy Private Helpers with `defp parts_to_index`
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/string.ex` lines 562–563
|
**Source:** [lib/elixir/lib/string.ex#L562](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/string.ex#L562)
|
||||||
|
|
||||||
**What it does:** Tiny private helpers that convert between API-level concepts and implementation-level values use single-line `defp` with guards.
|
**What it does:** Tiny private helpers that convert between API-level concepts and implementation-level values use single-line `defp` with guards.
|
||||||
|
|
||||||
@@ -1009,3 +1009,5 @@ def log(msg) when is_atom(msg), do: IO.puts(Atom.to_string(msg))
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Why:** When a conversion is used exactly once and the calling function already dispatches on clauses, folding the conversion into the caller's clauses reduces indirection. Named helpers shine when reused or when they name a non-obvious transformation.
|
**Why:** When a conversion is used exactly once and the calling function already dispatches on clauses, folding the conversion into the caller's clauses reduces indirection. Named helpers shine when reused or when they name a non-obvious transformation.
|
||||||
|
|
||||||
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Patterns extracted from the Elixir standard library source code.
|
|||||||
|
|
||||||
## 1. @moduledoc with Structured Sections
|
## 1. @moduledoc with Structured Sections
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex` lines 6–100+, `lib/logger/lib/logger.ex` lines 6–200+
|
**Source:** [lib/elixir/lib/gen_server.ex#L6](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L6)+, [lib/logger/lib/logger.ex#L6](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/logger/lib/logger.ex#L6)+
|
||||||
|
|
||||||
**What it does:** `@moduledoc` uses a clear hierarchical structure with `##` sections covering: overview, examples, configuration, and detailed behavioral explanations. GenServer covers "Example", "Client / Server APIs", "How to supervise", "Name registration", "Timeouts", "Debugging". Logger covers "Levels", "Message", "Metadata", "Configuration" (with subsections for Boot/Compile/Runtime).
|
**What it does:** `@moduledoc` uses a clear hierarchical structure with `##` sections covering: overview, examples, configuration, and detailed behavioral explanations. GenServer covers "Example", "Client / Server APIs", "How to supervise", "Name registration", "Timeouts", "Debugging". Logger covers "Levels", "Message", "Metadata", "Configuration" (with subsections for Boot/Compile/Runtime).
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ end
|
|||||||
|
|
||||||
## 2. @doc with Sections and Examples
|
## 2. @doc with Sections and Examples
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 315–335 (abs/1), `lib/logger/lib/logger.ex` lines 536–540
|
**Source:** [lib/elixir/lib/kernel.ex#L315](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L315) (abs/1), [lib/logger/lib/logger.ex#L536](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/logger/lib/logger.ex#L536)
|
||||||
|
|
||||||
**What it does:** `@doc` for individual functions includes: a brief one-liner, allowed-in-guards note (via `@doc guard: true`), explanation of edge cases, and `## Examples` with doctests.
|
**What it does:** `@doc` for individual functions includes: a brief one-liner, allowed-in-guards note (via `@doc guard: true`), explanation of edge cases, and `## Examples` with doctests.
|
||||||
|
|
||||||
@@ -230,7 +230,7 @@ def get_name(%User{name: name}), do: name
|
|||||||
|
|
||||||
## 3. @doc since: Version Annotation
|
## 3. @doc since: Version Annotation
|
||||||
|
|
||||||
**Source:** `lib/logger/lib/logger.ex` lines 539, 576, 813, 824, 831, `lib/elixir/lib/kernel.ex` line 5163+
|
**Source:** [lib/logger/lib/logger.ex#L539](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/logger/lib/logger.ex#L539), 576, 813, 824, 831, [lib/elixir/lib/kernel.ex#L5163](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L5163)+
|
||||||
|
|
||||||
**What it does:** Attaches `since: "X.Y.Z"` metadata to `@doc` indicating the Elixir version that introduced the function.
|
**What it does:** Attaches `since: "X.Y.Z"` metadata to `@doc` indicating the Elixir version that introduced the function.
|
||||||
|
|
||||||
@@ -307,7 +307,7 @@ end
|
|||||||
|
|
||||||
## 4. @doc guard: true Metadata
|
## 4. @doc guard: true Metadata
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 329, 408, 428, 452, etc.
|
**Source:** [lib/elixir/lib/kernel.ex#L329](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L329), 408, 428, 452, etc.
|
||||||
|
|
||||||
**What it does:** Functions/macros usable in guard clauses are annotated with `@doc guard: true` metadata, separate from the doc string itself.
|
**What it does:** Functions/macros usable in guard clauses are annotated with `@doc guard: true` metadata, separate from the doc string itself.
|
||||||
|
|
||||||
@@ -395,7 +395,7 @@ end
|
|||||||
|
|
||||||
## 5. @doc false — Hiding from Documentation
|
## 5. @doc false — Hiding from Documentation
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/inspect.ex` lines 410, 417; implicit via `@impl true`
|
**Source:** [lib/elixir/lib/inspect.ex#L410](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/inspect.ex#L410), 417; implicit via `@impl true`
|
||||||
|
|
||||||
**What it does:** `@doc false` explicitly hides a function from documentation generators. Also, using `@impl true` automatically sets `@doc false` (unless `@doc` is explicitly provided).
|
**What it does:** `@doc false` explicitly hides a function from documentation generators. Also, using `@impl true` automatically sets `@doc false` (unless `@doc` is explicitly provided).
|
||||||
|
|
||||||
@@ -555,7 +555,7 @@ end
|
|||||||
|
|
||||||
## 7. Mermaid Diagrams in Documentation
|
## 7. Mermaid Diagrams in Documentation
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex` lines 14–20
|
**Source:** [lib/elixir/lib/gen_server.ex#L14](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L14)
|
||||||
|
|
||||||
**What it does:** Embeds Mermaid diagram syntax directly in `@moduledoc` to illustrate architectural patterns (client-server message flow).
|
**What it does:** Embeds Mermaid diagram syntax directly in `@moduledoc` to illustrate architectural patterns (client-server message flow).
|
||||||
|
|
||||||
@@ -648,7 +648,7 @@ A simple integer counter. Call `increment/1` to add, `value/1` to read.
|
|||||||
|
|
||||||
## 8. Admonition Blocks in Documentation
|
## 8. Admonition Blocks in Documentation
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex` lines 88–95, `lib/elixir/lib/supervisor.ex` lines 34–38
|
**Source:** [lib/elixir/lib/gen_server.ex#L88](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L88), [lib/elixir/lib/supervisor.ex#L34](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/supervisor.ex#L34)
|
||||||
|
|
||||||
**What it does:** Uses markdown admonition syntax (`> #### Title {: .info}`) to highlight important callouts — especially for `use ModuleName` behavior documentation.
|
**What it does:** Uses markdown admonition syntax (`> #### Title {: .info}`) to highlight important callouts — especially for `use ModuleName` behavior documentation.
|
||||||
|
|
||||||
@@ -745,7 +745,7 @@ Helper functions for string formatting.
|
|||||||
|
|
||||||
## 9. @doc deprecated: Soft Deprecation
|
## 9. @doc deprecated: Soft Deprecation
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/module.ex` lines 163–180
|
**Source:** [lib/elixir/lib/module.ex#L163](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/module.ex#L163)
|
||||||
|
|
||||||
**What it does:** Uses `@doc deprecated: "Use X instead"` for soft deprecation (documentation-only warning) vs. `@deprecated "reason"` for hard deprecation (compiler warning).
|
**What it does:** Uses `@doc deprecated: "Use X instead"` for soft deprecation (documentation-only warning) vs. `@deprecated "reason"` for hard deprecation (compiler warning).
|
||||||
|
|
||||||
@@ -822,7 +822,7 @@ def process(input), do: new_process(input, [])
|
|||||||
|
|
||||||
## 10. Callback Documentation Convention
|
## 10. Callback Documentation Convention
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex` lines 584–646 (handle_call docs)
|
**Source:** [lib/elixir/lib/gen_server.ex#L584](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L584) (handle_call docs)
|
||||||
|
|
||||||
**What it does:** Each `@callback` is preceded by a comprehensive `@doc` that explains: what triggers the callback, what the parameters mean, every possible return value and its effect, when the callback is optional, and cross-references to related callbacks.
|
**What it does:** Each `@callback` is preceded by a comprehensive `@doc` that explains: what triggers the callback, what the parameters mean, every possible return value and its effect, when the callback is optional, and cross-references to related callbacks.
|
||||||
|
|
||||||
@@ -999,3 +999,5 @@ Returns `true` if the calling process is the owner of this resource.
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Why:** Link references should aid navigation, not turn documentation into hypertext soup. Link types and callbacks that users might need to look up; don't link primitive types or universally known functions.
|
**Why:** Link references should aid navigation, not turn documentation into hypertext soup. Link types and callbacks that users might need to look up; don't link primitive types or universally known functions.
|
||||||
|
|
||||||
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
+16
-14
@@ -6,7 +6,7 @@ Patterns extracted from Elixir's standard library source code.
|
|||||||
|
|
||||||
## 1. The `with` Macro — Normalized Error Clauses
|
## 1. The `with` Macro — Normalized Error Clauses
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel/special_forms.ex` lines 1600–1715 (docs + definition)
|
**Source:** [lib/elixir/lib/kernel/special_forms.ex#L1600](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel/special_forms.ex#L1600) (docs + definition)
|
||||||
|
|
||||||
**What it does:** The `with` macro chains pattern-matched steps where each `<-` clause returns a normalized error shape. When a step fails to match, the non-matched value falls through (or hits `else`).
|
**What it does:** The `with` macro chains pattern-matched steps where each `<-` clause returns a normalized error shape. When a step fails to match, the non-matched value falls through (or hits `else`).
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ Repo.get_user(id)
|
|||||||
|
|
||||||
## 2. Real-World `with` — Multi-Step Fallible Operations
|
## 2. Real-World `with` — Multi-Step Fallible Operations
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/exception.ex` lines 251–285 (`blame_mfa/4`)
|
**Source:** [lib/elixir/lib/exception.ex#L251](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/exception.ex#L251) (`blame_mfa/4`)
|
||||||
|
|
||||||
**What it does:** Uses `with` to chain 5+ fallible steps where any failure should produce `:error`. Each step's pattern is an exact match.
|
**What it does:** Uses `with` to chain 5+ fallible steps where any failure should produce `:error`. Each step's pattern is an exact match.
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@ def process_payment(order) do
|
|||||||
|
|
||||||
## 3. Another `with` — Error Info Extraction
|
## 3. Another `with` — Error Info Extraction
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/exception.ex` lines 2695–2720 (`error_info/3` in ErlangError)
|
**Source:** [lib/elixir/lib/exception.ex#L2695](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/exception.ex#L2695) (`error_info/3` in ErlangError)
|
||||||
|
|
||||||
**What it does:** Chains pattern matching on a stacktrace and error_info map to extract formatted error details.
|
**What it does:** Chains pattern matching on a stacktrace and error_info map to extract formatted error details.
|
||||||
|
|
||||||
@@ -322,7 +322,7 @@ def extract_user_preference(_user, _key), do: {:error, :no_preferences}
|
|||||||
|
|
||||||
## 4. `{:ok, value}` / `:error` Convention (Map.fetch)
|
## 4. `{:ok, value}` / `:error` Convention (Map.fetch)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/map.ex` lines 290–309
|
**Source:** [lib/elixir/lib/map.ex#L290](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/map.ex#L290)
|
||||||
|
|
||||||
**What it does:** `Map.fetch/2` returns `{:ok, value}` on success and bare `:error` on failure. No reason atom, because the failure mode is obvious (key not found).
|
**What it does:** `Map.fetch/2` returns `{:ok, value}` on success and bare `:error` on failure. No reason atom, because the failure mode is obvious (key not found).
|
||||||
|
|
||||||
@@ -413,7 +413,7 @@ end
|
|||||||
|
|
||||||
## 5. Bang Functions: Raise on Error (`fetch!` vs `fetch`)
|
## 5. Bang Functions: Raise on Error (`fetch!` vs `fetch`)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/map.ex` lines 311–380
|
**Source:** [lib/elixir/lib/map.ex#L311](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/map.ex#L311)
|
||||||
|
|
||||||
**What it does:** The `!` suffix convention means "raises on failure instead of returning an error tuple." The non-bang version is for when the caller wants to handle the error.
|
**What it does:** The `!` suffix convention means "raises on failure instead of returning an error tuple." The non-bang version is for when the caller wants to handle the error.
|
||||||
|
|
||||||
@@ -507,7 +507,7 @@ end
|
|||||||
|
|
||||||
## 6. Exception Structure: `defexception` Fields
|
## 6. Exception Structure: `defexception` Fields
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/exception.ex` lines 2250–2500 (exception definitions)
|
**Source:** [lib/elixir/lib/exception.ex#L2250](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/exception.ex#L2250) (exception definitions)
|
||||||
|
|
||||||
**What it does:** Exceptions carry meaningful fields beyond just `:message`. The `message/1` callback generates a human-readable string from those fields.
|
**What it does:** Exceptions carry meaningful fields beyond just `:message`. The `message/1` callback generates a human-readable string from those fields.
|
||||||
|
|
||||||
@@ -605,7 +605,7 @@ raise "expected state in #{inspect(expected)}, got #{inspect(actual)}"
|
|||||||
|
|
||||||
## 7. Custom `exception/1` Callback for Ergonomic Raising
|
## 7. Custom `exception/1` Callback for Ergonomic Raising
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/exception.ex` lines 2255–2270 (UnicodeConversionError)
|
**Source:** [lib/elixir/lib/exception.ex#L2255](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/exception.ex#L2255) (UnicodeConversionError)
|
||||||
|
|
||||||
**What it does:** Override `exception/1` to accept raw values (not just keyword lists) and build the struct with a meaningful message.
|
**What it does:** Override `exception/1` to accept raw values (not just keyword lists) and build the struct with a meaningful message.
|
||||||
|
|
||||||
@@ -710,7 +710,7 @@ end
|
|||||||
|
|
||||||
## 8. `raise` Macro Internals: Compile-Time Type Resolution
|
## 8. `raise` Macro Internals: Compile-Time Type Resolution
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 2246–2294
|
**Source:** [lib/elixir/lib/kernel.ex#L2246](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L2246)
|
||||||
|
|
||||||
**What it does:** The `raise` macro inspects the argument at compile time to determine if it's a string, binary expression, atom (module), or existing exception struct, generating optimized code for each case.
|
**What it does:** The `raise` macro inspects the argument at compile time to determine if it's a string, binary expression, atom (module), or existing exception struct, generating optimized code for each case.
|
||||||
|
|
||||||
@@ -815,7 +815,7 @@ end
|
|||||||
|
|
||||||
## 9. Error Normalization: Erlang → Elixir Exception Translation
|
## 9. Error Normalization: Erlang → Elixir Exception Translation
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/exception.ex` lines 2530–2680 (`ErlangError.normalize/2`)
|
**Source:** [lib/elixir/lib/exception.ex#L2530](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/exception.ex#L2530) (`ErlangError.normalize/2`)
|
||||||
|
|
||||||
**What it does:** Translates raw Erlang error reasons (atoms and tuples) into proper Elixir exception structs with helpful messages.
|
**What it does:** Translates raw Erlang error reasons (atoms and tuples) into proper Elixir exception structs with helpful messages.
|
||||||
|
|
||||||
@@ -913,7 +913,7 @@ end
|
|||||||
|
|
||||||
## 10. `blame/2` Callback: Enriching Exceptions After the Fact
|
## 10. `blame/2` Callback: Enriching Exceptions After the Fact
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/exception.ex` lines 2200–2215 (KeyError.blame)
|
**Source:** [lib/elixir/lib/exception.ex#L2200](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/exception.ex#L2200) (KeyError.blame)
|
||||||
|
|
||||||
**What it does:** The optional `blame/2` callback enriches an exception with additional context that's expensive to compute (like "did you mean?" suggestions).
|
**What it does:** The optional `blame/2` callback enriches an exception with additional context that's expensive to compute (like "did you mean?" suggestions).
|
||||||
|
|
||||||
@@ -1008,7 +1008,7 @@ end
|
|||||||
|
|
||||||
## 11. Guards for Type Dispatch in Error Handling
|
## 11. Guards for Type Dispatch in Error Handling
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/exception.ex` lines 2530–2550, `lib/elixir/lib/map.ex` lines 586–594
|
**Source:** [lib/elixir/lib/exception.ex#L2530](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/exception.ex#L2530), [lib/elixir/lib/map.ex#L586](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/map.ex#L586)
|
||||||
|
|
||||||
**What it does:** Guards (`when is_map(term)`, `when is_list(term)`) dispatch to different error handling or normalization logic without using conditionals.
|
**What it does:** Guards (`when is_map(term)`, `when is_list(term)`) dispatch to different error handling or normalization logic without using conditionals.
|
||||||
|
|
||||||
@@ -1101,7 +1101,7 @@ end
|
|||||||
|
|
||||||
## 12. The `:error` / `{:error, reason}` Convention Split
|
## 12. The `:error` / `{:error, reason}` Convention Split
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/map.ex` (Map.fetch → `:error`), `lib/elixir/lib/exception.ex` lines 2695–2720 (`error_info` → `{:ok, ...} | :error`)
|
**Source:** `lib/elixir/lib/map.ex` (Map.fetch → `:error`), [lib/elixir/lib/exception.ex#L2695](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/exception.ex#L2695) (`error_info` → `{:ok, ...} | :error`)
|
||||||
|
|
||||||
**What it does:** Elixir has two error return conventions:
|
**What it does:** Elixir has two error return conventions:
|
||||||
1. **`:error`** alone — when there's only one failure mode (Map.fetch, Access)
|
1. **`:error`** alone — when there's only one failure mode (Map.fetch, Access)
|
||||||
@@ -1197,7 +1197,7 @@ end
|
|||||||
|
|
||||||
## 13. `reduce_while` — Early Exit Without Exceptions
|
## 13. `reduce_while` — Early Exit Without Exceptions
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/enum.ex` lines 2660–2676
|
**Source:** [lib/elixir/lib/enum.ex#L2660](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/enum.ex#L2660)
|
||||||
|
|
||||||
**What it does:** `reduce_while` uses `{:cont, acc}` / `{:halt, acc}` tuples as the reducer's return value to signal continuation or early termination.
|
**What it does:** `reduce_while` uses `{:cont, acc}` / `{:halt, acc}` tuples as the reducer's return value to signal continuation or early termination.
|
||||||
|
|
||||||
@@ -1291,7 +1291,7 @@ Enum.find(users, & &1.admin?)
|
|||||||
|
|
||||||
## 14. Three-Tier Error Strategy in Map Operations
|
## 14. Three-Tier Error Strategy in Map Operations
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/map.ex` lines 290–430
|
**Source:** [lib/elixir/lib/map.ex#L290](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/map.ex#L290)
|
||||||
|
|
||||||
**What it does:** Map provides three variants for key operations, each with different error semantics:
|
**What it does:** Map provides three variants for key operations, each with different error semantics:
|
||||||
|
|
||||||
@@ -1399,3 +1399,5 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Why:** The three-tier pattern only makes sense when failure is a real possibility and different callers genuinely need different responses to that failure. Don't cargo-cult it onto functions that always succeed or have a single calling context.
|
**Why:** The three-tier pattern only makes sense when failure is a real possibility and different callers genuinely need different responses to that failure. Don't cargo-cult it onto functions that always succeed or have a single calling context.
|
||||||
|
|
||||||
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
@@ -1,358 +0,0 @@
|
|||||||
# Elixir Patterns (from Source)
|
|
||||||
|
|
||||||
Prescriptive patterns extracted from elixir-lang/elixir source.
|
|
||||||
"If writing new Elixir, follow these rules."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Module Organization
|
|
||||||
|
|
||||||
### One Module Per Concept
|
|
||||||
|
|
||||||
**Rule:** Each module owns exactly one concept. If you can't name it in
|
|
||||||
2-3 words, it's too broad.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# Source: lib/elixir/lib/string.ex — String is strings. Period.
|
|
||||||
defmodule String do
|
|
||||||
@moduledoc """
|
|
||||||
Strings in Elixir are UTF-8 encoded binaries.
|
|
||||||
"""
|
|
||||||
@type t :: binary
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** The Elixir source has zero "util" or "helper" modules. Every
|
|
||||||
module has a noun name that IS the thing.
|
|
||||||
|
|
||||||
**When NOT to use:** Kernel is the exception — it's the implicit
|
|
||||||
surface area. You don't get to make your own Kernel.
|
|
||||||
|
|
||||||
**Source:** Every file in `lib/elixir/lib/` follows this.
|
|
||||||
|
|
||||||
### @moduledoc false for Internal Modules
|
|
||||||
|
|
||||||
**Rule:** Internal modules that users shouldn't call get `@moduledoc false`.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# Source: lib/elixir/lib/code/formatter.ex
|
|
||||||
defmodule Code.Formatter do
|
|
||||||
@moduledoc false
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** Hides from docs. Signals "this is implementation, not API."
|
|
||||||
The Elixir team uses this for 30+ internal modules.
|
|
||||||
|
|
||||||
**When to use:** Modules that are implementation details — adapters,
|
|
||||||
internal state machines, compiler passes.
|
|
||||||
|
|
||||||
**When NOT to use:** If ANYONE outside your team might call it. Public
|
|
||||||
API must have docs.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Protocol Design
|
|
||||||
|
|
||||||
### Protocols for External Extension
|
|
||||||
|
|
||||||
**Rule:** Define a protocol when users need to extend behavior for
|
|
||||||
their own types.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# Source: lib/elixir/lib/collectable.ex
|
|
||||||
defprotocol Collectable do
|
|
||||||
@doc """
|
|
||||||
Returns an initial accumulation value and a "collector" function.
|
|
||||||
"""
|
|
||||||
@spec into(t) :: {initial_acc :: term, collector(term)}
|
|
||||||
def into(collectable)
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** Protocols dispatch on the first argument's type. They're the
|
|
||||||
extension point for "I made a new data structure and want it to work
|
|
||||||
with Enum."
|
|
||||||
|
|
||||||
**When to use:** When third-party code needs to extend your library
|
|
||||||
for new data types.
|
|
||||||
|
|
||||||
**When NOT to use:** When you control all implementations. Use
|
|
||||||
behaviours instead. Protocols are for open extension; behaviours are
|
|
||||||
for closed contracts.
|
|
||||||
|
|
||||||
### Only 6 Stdlib Protocols
|
|
||||||
|
|
||||||
**Rule:** Be conservative defining protocols. The Elixir stdlib has
|
|
||||||
only 6 in 15 years.
|
|
||||||
|
|
||||||
- `Enumerable` — iterate over things
|
|
||||||
- `Collectable` — put things into containers
|
|
||||||
- `Inspect` — debug representation
|
|
||||||
- `String.Chars` — convert to string
|
|
||||||
- `List.Chars` — convert to charlist
|
|
||||||
- `JSON.Encoder` — JSON serialization (added 2024)
|
|
||||||
|
|
||||||
**Why:** Each protocol is a permanent API commitment. Once defined,
|
|
||||||
every type in the ecosystem may implement it.
|
|
||||||
|
|
||||||
**When to use:** When you need a small set of polymorphic operations
|
|
||||||
that ANY type in the ecosystem should be able to implement.
|
|
||||||
|
|
||||||
**When NOT to use:** Don't define a protocol for something only your
|
|
||||||
app needs. A behaviour or function argument is cheaper.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
### Tagged Tuples for Expected Failures
|
|
||||||
|
|
||||||
**Rule:** Return `{:ok, value}` or `{:error, reason}` for operations
|
|
||||||
that can fail in expected ways.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# Source: lib/elixir/lib/file.ex
|
|
||||||
@spec read(Path.t()) :: {:ok, binary} | {:error, posix}
|
|
||||||
def read(path) do
|
|
||||||
case :file.read_file(path) do
|
|
||||||
{:ok, binary} -> {:ok, binary}
|
|
||||||
{:error, reason} -> {:error, reason}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** Pattern matching makes handling explicit. The caller MUST
|
|
||||||
decide what to do with errors — they can't accidentally ignore them.
|
|
||||||
|
|
||||||
**When to use:** Any operation that interacts with the outside world
|
|
||||||
(files, network, databases) where failure is a normal outcome.
|
|
||||||
|
|
||||||
**When NOT to use:** For programmer errors (bugs). Those should raise.
|
|
||||||
`File.read!` raises; `File.read` returns tuples.
|
|
||||||
|
|
||||||
### Bang Functions for "Should Never Fail"
|
|
||||||
|
|
||||||
**Rule:** Provide `function!` variant that raises on error. Use when
|
|
||||||
failure means a bug in the caller.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# Convention: function returns {:ok, _} | {:error, _}
|
|
||||||
# function! raises on error
|
|
||||||
File.read("path") # => {:ok, "..."} | {:error, :enoent}
|
|
||||||
File.read!("path") # => "..." | raises File.Error
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** The `!` signals to the reader: "I expect this to succeed. If
|
|
||||||
it doesn't, crash." This is intentional — crashing is the correct
|
|
||||||
response to unexpected errors in OTP.
|
|
||||||
|
|
||||||
**When to use:** Pipeline code where you want to crash on unexpected
|
|
||||||
failure rather than propagate error tuples.
|
|
||||||
|
|
||||||
**When NOT to use:** When failure is expected and the caller should
|
|
||||||
handle it (use tagged tuples).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
### CaseTemplate for Shared Setup
|
|
||||||
|
|
||||||
**Rule:** Use `ExUnit.CaseTemplate` when multiple test files share
|
|
||||||
setup logic.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# Source: lib/ex_unit/lib/ex_unit/case_template.ex
|
|
||||||
defmodule MyApp.DataCase do
|
|
||||||
use ExUnit.CaseTemplate
|
|
||||||
|
|
||||||
setup tags do
|
|
||||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(MyApp.Repo)
|
|
||||||
unless tags[:async] do
|
|
||||||
Ecto.Adapters.SQL.Sandbox.mode(MyApp.Repo, {:shared, self()})
|
|
||||||
end
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** Inheritance via `use` is how Phoenix's ConnCase/DataCase work.
|
|
||||||
This pattern comes directly from ExUnit itself.
|
|
||||||
|
|
||||||
**When to use:** When 3+ test modules share the same setup callbacks,
|
|
||||||
imports, or lifecycle management.
|
|
||||||
|
|
||||||
**When NOT to use:** For helpers that don't need lifecycle callbacks.
|
|
||||||
Simple `import` is cleaner for utility functions.
|
|
||||||
|
|
||||||
### async: true by Default
|
|
||||||
|
|
||||||
**Rule:** Mark tests `async: true` unless they touch shared state.
|
|
||||||
|
|
||||||
**Why:** Async tests run in parallel. The Elixir stdlib tests show that
|
|
||||||
most tests CAN be async — only database/file/process tests need
|
|
||||||
serialization.
|
|
||||||
|
|
||||||
**When to use:** Pure unit tests, tests with isolated process state,
|
|
||||||
tests that only read shared resources.
|
|
||||||
|
|
||||||
**When NOT to use:** Tests that modify global state, shared files, or
|
|
||||||
named processes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
### Every Public Function Gets @doc + @spec
|
|
||||||
|
|
||||||
**Rule:** All public functions have both `@doc` and `@spec`.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# Source: lib/elixir/lib/enum.ex
|
|
||||||
@doc """
|
|
||||||
Returns `true` if all elements in `enumerable` are truthy.
|
|
||||||
"""
|
|
||||||
@spec all?(t) :: boolean
|
|
||||||
def all?(enumerable) when is_list(enumerable) do
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** Specs enable Dialyzer checking. Docs generate ExDoc pages. The
|
|
||||||
Elixir stdlib has zero undocumented public functions.
|
|
||||||
|
|
||||||
**When to use:** Every public function in every public module.
|
|
||||||
No exceptions.
|
|
||||||
|
|
||||||
**When NOT to use:** `@moduledoc false` modules skip docs (they're
|
|
||||||
internal).
|
|
||||||
|
|
||||||
### @type t for Structs
|
|
||||||
|
|
||||||
**Rule:** Every struct defines `@type t` with field types.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# Source: lib/elixir/lib/kernel.ex (defstruct docs)
|
|
||||||
defmodule User do
|
|
||||||
defstruct name: "John", age: 25
|
|
||||||
@type t :: %__MODULE__{name: String.t(), age: non_neg_integer}
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** Enables Dialyzer to catch type mismatches at struct boundaries.
|
|
||||||
Without `@type t`, struct fields are effectively untyped.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Naming
|
|
||||||
|
|
||||||
### Modules Are Nouns
|
|
||||||
|
|
||||||
**Rule:** Module names are nouns. Never verbs, never adjectives.
|
|
||||||
|
|
||||||
`String`, `Enum`, `Map`, `File`, `Logger`, `GenServer`
|
|
||||||
|
|
||||||
**Why:** A module IS a thing. Functions are what you DO with that thing.
|
|
||||||
`String.split/2` reads as "take a String, split it."
|
|
||||||
|
|
||||||
**When to use:** Every module you define. Period.
|
|
||||||
|
|
||||||
**When NOT to use:** Mix tasks (they're commands: `Mix.Tasks.Deps.Get`).
|
|
||||||
|
|
||||||
### Functions Are Verbs
|
|
||||||
|
|
||||||
**Rule:** Function names start with a verb (or are a question with `?`).
|
|
||||||
|
|
||||||
`Enum.map/2`, `String.split/2`, `File.read/1`, `Enum.empty?/1`
|
|
||||||
|
|
||||||
**Why:** `module.verb(subject)` reads as a sentence.
|
|
||||||
|
|
||||||
### Underscore Prefix for Unused
|
|
||||||
|
|
||||||
**Rule:** Prefix unused variables with `_`.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
def handle_info(_message, state), do: {:noreply, state}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** Compiler warning suppression AND documentation that the value
|
|
||||||
is intentionally ignored.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Process Design
|
|
||||||
|
|
||||||
### GenServer for Stateful Services
|
|
||||||
|
|
||||||
**Rule:** Use GenServer when you need mutable state that outlives a
|
|
||||||
request.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# Source: lib/iex/lib/iex/broker.ex
|
|
||||||
defmodule IEx.Broker do
|
|
||||||
use GenServer
|
|
||||||
# ...
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** GenServer gives you: message serialization, supervision tree
|
|
||||||
integration, hot code upgrade, `:sys` debugging.
|
|
||||||
|
|
||||||
**When to use:** When you need mutable state that survives across
|
|
||||||
multiple requests or events.
|
|
||||||
|
|
||||||
**When NOT to use:** Stateless transformations (just use functions).
|
|
||||||
One-off concurrent work (use Task). Accumulating state within a request
|
|
||||||
(use recursion or reduce).
|
|
||||||
|
|
||||||
### Agent for Simple State
|
|
||||||
|
|
||||||
**Rule:** Use Agent when you only need get/update on a value — no
|
|
||||||
complex message handling.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# Source: lib/mix/lib/mix/tasks_server.ex
|
|
||||||
defmodule Mix.TasksServer do
|
|
||||||
use Agent
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** Agent is GenServer with the common case optimized. Less
|
|
||||||
boilerplate for "I just need a mutable box."
|
|
||||||
|
|
||||||
**When to use:** When your process only needs get/update operations
|
|
||||||
on a single piece of state — no complex message handling.
|
|
||||||
|
|
||||||
**When NOT to use:** When you need `handle_info`, timeouts, or multiple
|
|
||||||
operations that must be atomic.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Smells
|
|
||||||
|
|
||||||
### GenEvent (Deprecated Pattern)
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# Source: lib/elixir/lib/gen_event.ex
|
|
||||||
@moduledoc deprecated: "Use one of the alternatives described below"
|
|
||||||
```
|
|
||||||
|
|
||||||
The Elixir team deprecated their own GenEvent. Alternatives: Registry +
|
|
||||||
GenServer, or Phoenix.PubSub. Lesson: event buses that try to do
|
|
||||||
everything are worse than composed primitives.
|
|
||||||
|
|
||||||
### Version-Gated TODOs (Deferred Cleanup)
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# TODO: Remove me on v2.0
|
|
||||||
# TODO: Deprecate me on Elixir v1.23
|
|
||||||
```
|
|
||||||
|
|
||||||
127 of these exist. They're not smells in the "bad code" sense —
|
|
||||||
they're discipline. But if YOUR code has TODOs without version targets,
|
|
||||||
that IS a smell.
|
|
||||||
|
|
||||||
### @moduledoc false Proliferation
|
|
||||||
|
|
||||||
30+ internal modules in the stdlib. If your app has this many, you may
|
|
||||||
be over-splitting. Internal modules should be rare in application code
|
|
||||||
— they're appropriate for libraries and frameworks.
|
|
||||||
|
|
||||||
<!-- PATTERN_COMPLETE -->
|
|
||||||
+14
-12
@@ -6,7 +6,7 @@ Analysis of `lib/elixir/lib/gen_server.ex`, `lib/elixir/lib/agent.ex`, and relat
|
|||||||
|
|
||||||
## Pattern 1: Client/Server API Separation
|
## Pattern 1: Client/Server API Separation
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:101-149` (documentation example)
|
**Source:** [lib/elixir/lib/gen_server.ex#L101](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L101) (documentation example)
|
||||||
|
|
||||||
**What it does:** Every GenServer module defines two distinct API layers — a **client API** (thin public functions that wrap `GenServer.call/cast`) and a **server API** (callback implementations). The client functions live in the same module but are clearly separated with comments.
|
**What it does:** Every GenServer module defines two distinct API layers — a **client API** (thin public functions that wrap `GenServer.call/cast`) and a **server API** (callback implementations). The client functions live in the same module but are clearly separated with comments.
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ end
|
|||||||
|
|
||||||
## Pattern 2: `@impl true` Annotations on All Callbacks
|
## Pattern 2: `@impl true` Annotations on All Callbacks
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:41-60` (Stack example)
|
**Source:** [lib/elixir/lib/gen_server.ex#L41](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L41) (Stack example)
|
||||||
|
|
||||||
**What it does:** Every callback function is annotated with `@impl true`. This tells the compiler to verify that the function is a valid callback for the declared behaviour.
|
**What it does:** Every callback function is annotated with `@impl true`. This tells the compiler to verify that the function is a valid callback for the declared behaviour.
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ end
|
|||||||
|
|
||||||
## Pattern 3: Guard-Protected `start_link`
|
## Pattern 3: Guard-Protected `start_link`
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:101` and `lib/elixir/lib/agent.ex:28`
|
**Source:** [lib/elixir/lib/gen_server.ex#L101](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L101) and [lib/elixir/lib/agent.ex#L28](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/agent.ex#L28)
|
||||||
|
|
||||||
**What it does:** The `start_link` function uses guards to validate its arguments at the API boundary, failing fast with a clear error before any process spawning occurs.
|
**What it does:** The `start_link` function uses guards to validate its arguments at the API boundary, failing fast with a clear error before any process spawning occurs.
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ end
|
|||||||
|
|
||||||
## Pattern 4: `handle_continue` for Post-Init Work
|
## Pattern 4: `handle_continue` for Post-Init Work
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:520-528` (callback spec), `lib/elixir/lib/gen_server.ex:714-720` (handle_continue callback definition)
|
**Source:** [lib/elixir/lib/gen_server.ex#L520](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L520) (callback spec), [lib/elixir/lib/gen_server.ex#L714](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L714) (handle_continue callback definition)
|
||||||
|
|
||||||
**What it does:** `init/1` can return `{:ok, state, {:continue, continue_arg}}`, which causes `handle_continue/2` to be invoked immediately after init completes. This allows splitting initialization into a synchronous part (that unblocks the supervisor) and an asynchronous continuation.
|
**What it does:** `init/1` can return `{:ok, state, {:continue, continue_arg}}`, which causes `handle_continue/2` to be invoked immediately after init completes. This allows splitting initialization into a synchronous part (that unblocks the supervisor) and an asynchronous continuation.
|
||||||
|
|
||||||
@@ -352,7 +352,7 @@ end
|
|||||||
|
|
||||||
## Pattern 5: Timeout-Based Idle Shutdown
|
## Pattern 5: Timeout-Based Idle Shutdown
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:335-380`
|
**Source:** [lib/elixir/lib/gen_server.ex#L335](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L335)
|
||||||
|
|
||||||
**What it does:** Callbacks can return a timeout value (milliseconds) as the last element of the return tuple. If no message arrives within that time, `handle_info(:timeout, state)` is called. This enables idle process cleanup.
|
**What it does:** Callbacks can return a timeout value (milliseconds) as the last element of the return tuple. If no message arrives within that time, `handle_info(:timeout, state)` is called. This enables idle process cleanup.
|
||||||
|
|
||||||
@@ -458,7 +458,7 @@ end
|
|||||||
|
|
||||||
## Pattern 6: Periodic Work via `Process.send_after`
|
## Pattern 6: Periodic Work via `Process.send_after`
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:298-332` (Periodically example in docs)
|
**Source:** [lib/elixir/lib/gen_server.ex#L298](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L298) (Periodically example in docs)
|
||||||
|
|
||||||
**What it does:** A GenServer schedules periodic work by sending itself a message via `Process.send_after` in `init/1`, then rescheduling in `handle_info`. This creates a self-sustaining periodic loop.
|
**What it does:** A GenServer schedules periodic work by sending itself a message via `Process.send_after` in `init/1`, then rescheduling in `handle_info`. This creates a self-sustaining periodic loop.
|
||||||
|
|
||||||
@@ -573,7 +573,7 @@ end
|
|||||||
|
|
||||||
## Pattern 7: Call vs Cast Decision (Synchronous vs Asynchronous)
|
## Pattern 7: Call vs Cast Decision (Synchronous vs Asynchronous)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:83-90` (docs), `lib/elixir/lib/agent.ex:368-378` (Agent.update uses call, Agent.cast uses cast)
|
**Source:** [lib/elixir/lib/gen_server.ex#L83](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L83) (docs), [lib/elixir/lib/agent.ex#L368](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/agent.ex#L368) (Agent.update uses call, Agent.cast uses cast)
|
||||||
|
|
||||||
**What it does:** The Elixir team uses `call` (synchronous) for operations where the client needs confirmation or a return value, and `cast` (fire-and-forget) only when the client genuinely doesn't care about the outcome.
|
**What it does:** The Elixir team uses `call` (synchronous) for operations where the client needs confirmation or a return value, and `cast` (fire-and-forget) only when the client genuinely doesn't care about the outcome.
|
||||||
|
|
||||||
@@ -640,7 +640,7 @@ end
|
|||||||
|
|
||||||
## Pattern 8: Default Callback Implementations with Clear Error Messages
|
## Pattern 8: Default Callback Implementations with Clear Error Messages
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:902-993` (`__using__` macro)
|
**Source:** [lib/elixir/lib/gen_server.ex#L902](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L902) (`__using__` macro)
|
||||||
|
|
||||||
**What it does:** `use GenServer` injects default implementations of `handle_call`, `handle_cast`, `handle_info`, `terminate`, and `code_change`. The defaults for `handle_call` and `handle_cast` raise with a descriptive error message including the process name. `handle_info` logs an error. All are `defoverridable`.
|
**What it does:** `use GenServer` injects default implementations of `handle_call`, `handle_cast`, `handle_info`, `terminate`, and `code_change`. The defaults for `handle_call` and `handle_cast` raise with a descriptive error message including the process name. `handle_info` logs an error. All are `defoverridable`.
|
||||||
|
|
||||||
@@ -747,7 +747,7 @@ end
|
|||||||
|
|
||||||
## Pattern 9: `child_spec/1` Generation and Customization via `use` Options
|
## Pattern 9: `child_spec/1` Generation and Customization via `use` Options
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:900-921`, `lib/elixir/lib/agent.ex:206-218`, `lib/elixir/lib/task.ex:282-292`
|
**Source:** [lib/elixir/lib/gen_server.ex#L900](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L900), [lib/elixir/lib/agent.ex#L206](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/agent.ex#L206), [lib/elixir/lib/task.ex#L282](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/task.ex#L282)
|
||||||
|
|
||||||
**What it does:** Each `use GenServer/Agent/Task` generates a `child_spec/1` function with sensible defaults that can be customized via options passed to `use`. The child spec is a map with `:id`, `:start`, `:restart`, `:shutdown`, and `:type`.
|
**What it does:** Each `use GenServer/Agent/Task` generates a `child_spec/1` function with sensible defaults that can be customized via options passed to `use`. The child spec is a map with `:id`, `:start`, `:restart`, `:shutdown`, and `:type`.
|
||||||
|
|
||||||
@@ -852,7 +852,7 @@ end
|
|||||||
|
|
||||||
## Pattern 10: Agent as Minimal State Wrapper (GenServer Under the Hood)
|
## Pattern 10: Agent as Minimal State Wrapper (GenServer Under the Hood)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/agent.ex:1-60` (module docs), `lib/elixir/lib/agent.ex:246-250` (implementation)
|
**Source:** [lib/elixir/lib/agent.ex#L1](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/agent.ex#L1) (module docs), [lib/elixir/lib/agent.ex#L246](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/agent.ex#L246) (implementation)
|
||||||
|
|
||||||
**What it does:** Agent is implemented entirely in terms of `GenServer.start_link(Agent.Server, fun, options)`. It's a thin abstraction that provides `get/update/get_and_update/cast` over GenServer's `call/cast`.
|
**What it does:** Agent is implemented entirely in terms of `GenServer.start_link(Agent.Server, fun, options)`. It's a thin abstraction that provides `get/update/get_and_update/cast` over GenServer's `call/cast`.
|
||||||
|
|
||||||
@@ -947,7 +947,7 @@ end
|
|||||||
|
|
||||||
## Pattern 11: Name Registration via `:via` Tuple
|
## Pattern 11: Name Registration via `:via` Tuple
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:1087-1107` (do_start implementation), `lib/elixir/lib/gen_server.ex:230-250` (documentation)
|
**Source:** [lib/elixir/lib/gen_server.ex#L1087](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L1087) (do_start implementation), [lib/elixir/lib/gen_server.ex#L230](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L230) (documentation)
|
||||||
|
|
||||||
**What it does:** GenServer supports four naming schemes: `nil` (anonymous), atom (local), `{:global, term}` (cluster-wide), and `{:via, module, term}` (pluggable registry). The implementation delegates to `:gen.start` with the appropriate format.
|
**What it does:** GenServer supports four naming schemes: `nil` (anonymous), atom (local), `{:global, term}` (cluster-wide), and `{:via, module, term}` (pluggable registry). The implementation delegates to `:gen.start` with the appropriate format.
|
||||||
|
|
||||||
@@ -1032,7 +1032,7 @@ end
|
|||||||
|
|
||||||
## Pattern 12: GenServer as Anti-Pattern — Don't Use Processes for Code Organization
|
## Pattern 12: GenServer as Anti-Pattern — Don't Use Processes for Code Organization
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:381-415` ("When (not) to use a GenServer" section)
|
**Source:** [lib/elixir/lib/gen_server.ex#L381](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L381) ("When (not) to use a GenServer" section)
|
||||||
|
|
||||||
**What it does:** The Elixir team explicitly documents that GenServer should NOT be used for code organization. A process must model a runtime property: mutable state, concurrency boundary, or failure isolation.
|
**What it does:** The Elixir team explicitly documents that GenServer should NOT be used for code organization. A process must model a runtime property: mutable state, concurrency boundary, or failure isolation.
|
||||||
|
|
||||||
@@ -1108,3 +1108,5 @@ end
|
|||||||
**Better alternative:** If you need mutable state (counters, connections, caches), you need a process. The rule is "don't use processes for code organization" — not "never use processes." A rate limiter with shared counters is a legitimate use of GenServer (or ETS owned by a GenServer).
|
**Better alternative:** If you need mutable state (counters, connections, caches), you need a process. The rule is "don't use processes for code organization" — not "never use processes." A rate limiter with shared counters is a legitimate use of GenServer (or ETS owned by a GenServer).
|
||||||
|
|
||||||
**Why:** The pattern cuts both ways. Over-using GenServer creates bottlenecks. Under-using it means reinventing state management poorly. The litmus test: does the state need to survive between function calls? Does access need serialization? If yes, you need a process.
|
**Why:** The pattern cuts both ways. Over-using GenServer creates bottlenecks. Under-using it means reinventing state management poorly. The litmus test: does the state need to survive between function calls? Does access need serialization? If yes, you need a process.
|
||||||
|
|
||||||
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
+14
-12
@@ -6,7 +6,7 @@ Patterns extracted from the Elixir standard library source code.
|
|||||||
|
|
||||||
## 1. Context-Aware Macros (__CALLER__.context)
|
## 1. Context-Aware Macros (__CALLER__.context)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 2032–2067 (or/and operators)
|
**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.
|
**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.
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ end
|
|||||||
|
|
||||||
## 2. defguard — Macro for Guard-Safe Expressions
|
## 2. defguard — Macro for Guard-Safe Expressions
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 5889–5966
|
**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.
|
**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.
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ end
|
|||||||
|
|
||||||
## 3. quote + unquote for Code Generation
|
## 3. quote + unquote for Code Generation
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 5624–5640 (defstruct)
|
**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.
|
**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.
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ end
|
|||||||
|
|
||||||
## 4. var! for Breaking Hygiene
|
## 4. var! for Breaking Hygiene
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 4884–4901
|
**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.
|
**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.
|
||||||
|
|
||||||
@@ -376,7 +376,7 @@ double_it(5) # => 10, explicit, no hidden dependencies
|
|||||||
|
|
||||||
## 5. Macro Expanding with Macro.expand
|
## 5. Macro Expanding with Macro.expand
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 2246–2273 (raise), 2319–2340 (reraise)
|
**Source:** [lib/elixir/lib/kernel.ex#L2246](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L2246) (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.
|
**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.
|
||||||
|
|
||||||
@@ -464,7 +464,7 @@ end
|
|||||||
|
|
||||||
## 6. assert_no_match_or_guard_scope Pattern
|
## 6. assert_no_match_or_guard_scope Pattern
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 5384–5385 (def), 5415–5416 (defp), 5444–5445 (defmacro)
|
**Source:** [lib/elixir/lib/kernel.ex#L5384](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L5384) (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.
|
**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.
|
||||||
|
|
||||||
@@ -551,7 +551,7 @@ end
|
|||||||
|
|
||||||
## 7. Protocol Definition as a Macro (defprotocol)
|
## 7. Protocol Definition as a Macro (defprotocol)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 5734–5745, `lib/elixir/lib/protocol.ex` lines 290–318
|
**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.
|
**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.
|
||||||
|
|
||||||
@@ -651,7 +651,7 @@ def format(binary) when is_binary(binary), do: # ...
|
|||||||
|
|
||||||
## 8. @fallback_to_any in Protocols
|
## 8. @fallback_to_any in Protocols
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/inspect.ex` line 162, `lib/elixir/lib/protocol.ex` lines 115–131
|
**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`.
|
**What it does:** Sets `@fallback_to_any true` inside a protocol definition to enable a default implementation via `defimpl Protocol, for: Any`.
|
||||||
|
|
||||||
@@ -751,7 +751,7 @@ end
|
|||||||
|
|
||||||
## 9. use/2 as Macro Injection Point
|
## 9. use/2 as Macro Injection Point
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 6130–6145
|
**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.
|
**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.
|
||||||
|
|
||||||
@@ -863,7 +863,7 @@ import MyHelpers
|
|||||||
|
|
||||||
## 10. Sigil Macros (Pattern for DSL Literals)
|
## 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.)
|
**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.
|
**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.
|
||||||
|
|
||||||
@@ -934,7 +934,7 @@ name = String.upcase("hello")
|
|||||||
|
|
||||||
## 11. Pipe Operator as a Macro
|
## 11. Pipe Operator as a Macro
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` line 4509
|
**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.
|
**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.
|
||||||
|
|
||||||
@@ -1016,7 +1016,7 @@ send_to(socket, encoded) # Clear which arg is which
|
|||||||
|
|
||||||
## 12. Macro.generate_unique_arguments for Hygiene
|
## 12. Macro.generate_unique_arguments for Hygiene
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/macro.ex` lines 507–520
|
**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.
|
**What it does:** `Macro.generate_unique_arguments(n, context)` creates `n` unique variable AST nodes that won't conflict with any user variables.
|
||||||
|
|
||||||
@@ -1104,3 +1104,5 @@ 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.
|
**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 -->
|
||||||
|
|||||||
+9
-7
@@ -214,7 +214,7 @@ end
|
|||||||
|
|
||||||
## 3. `@moduledoc false` for Internal Modules
|
## 3. `@moduledoc false` for Internal Modules
|
||||||
|
|
||||||
**Source:** `lib/phoenix/router/route.ex:5-7`
|
**Source:** [lib/phoenix/router/route.ex#L5](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/phoenix/router/route.ex#L5)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
# This module defines the Route struct that is used
|
# This module defines the Route struct that is used
|
||||||
@@ -289,7 +289,7 @@ end
|
|||||||
|
|
||||||
## 4. Struct Definition Conventions
|
## 4. Struct Definition Conventions
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/task.ex:279-296`
|
**Source:** [lib/elixir/lib/task.ex#L279](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/task.ex#L279)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
@enforce_keys [:mfa, :owner, :pid, :ref]
|
@enforce_keys [:mfa, :owner, :pid, :ref]
|
||||||
@@ -303,7 +303,7 @@ defstruct @enforce_keys
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Source:** `lib/phoenix/router/route.ex:30-46`
|
**Source:** [lib/phoenix/router/route.ex#L30](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/phoenix/router/route.ex#L30)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
defstruct [
|
defstruct [
|
||||||
@@ -389,14 +389,14 @@ end
|
|||||||
|
|
||||||
## 5. Selective Imports in `__using__`
|
## 5. Selective Imports in `__using__`
|
||||||
|
|
||||||
**Source:** `lib/phoenix/channel.ex:463-464`
|
**Source:** [lib/phoenix/channel.ex#L463](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/phoenix/channel.ex#L463)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
import unquote(__MODULE__)
|
import unquote(__MODULE__)
|
||||||
import Phoenix.Socket, only: [assign: 3, assign: 2]
|
import Phoenix.Socket, only: [assign: 3, assign: 2]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Source:** `lib/phoenix/router.ex:271-275`
|
**Source:** [lib/phoenix/router.ex#L271](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/phoenix/router.ex#L271)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
import Phoenix.Router
|
import Phoenix.Router
|
||||||
@@ -471,7 +471,7 @@ end
|
|||||||
|
|
||||||
## 6. Alias at Module Scope for Readability
|
## 6. Alias at Module Scope for Readability
|
||||||
|
|
||||||
**Source:** `lib/phoenix/router.ex:268`
|
**Source:** [lib/phoenix/router.ex#L268](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/phoenix/router.ex#L268)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
alias Phoenix.Router.{Resource, Scope, Route, Helpers}
|
alias Phoenix.Router.{Resource, Scope, Route, Helpers}
|
||||||
@@ -538,7 +538,7 @@ end
|
|||||||
|
|
||||||
## 7. Boolean-Suffixed Fields in Structs
|
## 7. Boolean-Suffixed Fields in Structs
|
||||||
|
|
||||||
**Source:** `lib/phoenix/router/route.ex:43-44`
|
**Source:** [lib/phoenix/router/route.ex#L43](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/phoenix/router/route.ex#L43)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
:trailing_slash?,
|
:trailing_slash?,
|
||||||
@@ -589,3 +589,5 @@ defstruct [:user, :admin?, :count]
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Why:** The `?` suffix should only mark genuine booleans. Using it on non-boolean fields creates confusion about the field's type and breaks the convention's usefulness as a type signal.
|
**Why:** The `?` suffix should only mark genuine booleans. Using it on non-boolean fields creates confusion about the field's type and breaks the convention's usefulness as a type signal.
|
||||||
|
|
||||||
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
+20
-18
@@ -6,7 +6,7 @@ Analysis of `lib/elixir/lib/supervisor.ex`, `lib/elixir/lib/dynamic_supervisor.e
|
|||||||
|
|
||||||
## Pattern 1: Static vs Dynamic Supervision — Choose the Right Tool
|
## Pattern 1: Static vs Dynamic Supervision — Choose the Right Tool
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/supervisor.ex:1-20` vs `lib/elixir/lib/dynamic_supervisor.ex:1-20`
|
**Source:** [lib/elixir/lib/supervisor.ex#L1](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/supervisor.ex#L1) vs [lib/elixir/lib/dynamic_supervisor.ex#L1](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/dynamic_supervisor.ex#L1)
|
||||||
|
|
||||||
**What it does:** Elixir provides two distinct supervisor types:
|
**What it does:** Elixir provides two distinct supervisor types:
|
||||||
- `Supervisor` — for **static** children known at compile time, started in a defined order
|
- `Supervisor` — for **static** children known at compile time, started in a defined order
|
||||||
@@ -110,7 +110,7 @@ end
|
|||||||
|
|
||||||
## Pattern 2: PartitionSupervisor for Scalability
|
## Pattern 2: PartitionSupervisor for Scalability
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/dynamic_supervisor.ex:60-95` and `lib/elixir/lib/task/supervisor.ex:35-65`
|
**Source:** [lib/elixir/lib/dynamic_supervisor.ex#L60](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/dynamic_supervisor.ex#L60) and [lib/elixir/lib/task/supervisor.ex#L35](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/task/supervisor.ex#L35)
|
||||||
|
|
||||||
**What it does:** Both `DynamicSupervisor` and `Task.Supervisor` document the same scalability pattern: when a single supervisor becomes a bottleneck, wrap it in a `PartitionSupervisor` which starts N instances (one per core by default) and routes via a key.
|
**What it does:** Both `DynamicSupervisor` and `Task.Supervisor` document the same scalability pattern: when a single supervisor becomes a bottleneck, wrap it in a `PartitionSupervisor` which starts N instances (one per core by default) and routes via a key.
|
||||||
|
|
||||||
@@ -206,7 +206,7 @@ end
|
|||||||
|
|
||||||
## Pattern 3: Supervision Strategies — Choosing the Right Restart Behavior
|
## Pattern 3: Supervision Strategies — Choosing the Right Restart Behavior
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/supervisor.ex:315-345` (Strategies section)
|
**Source:** [lib/elixir/lib/supervisor.ex#L315](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/supervisor.ex#L315) (Strategies section)
|
||||||
|
|
||||||
**What it does:** Three strategies model three dependency patterns:
|
**What it does:** Three strategies model three dependency patterns:
|
||||||
- `:one_for_one` — independent children (crash of A doesn't affect B)
|
- `:one_for_one` — independent children (crash of A doesn't affect B)
|
||||||
@@ -319,7 +319,7 @@ end
|
|||||||
|
|
||||||
## Pattern 4: Restart Intensity (`max_restarts` / `max_seconds`)
|
## Pattern 4: Restart Intensity (`max_restarts` / `max_seconds`)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/supervisor.ex:309-313`, `lib/elixir/lib/dynamic_supervisor.ex:730-758` (implementation)
|
**Source:** [lib/elixir/lib/supervisor.ex#L309](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/supervisor.ex#L309), [lib/elixir/lib/dynamic_supervisor.ex#L730](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/dynamic_supervisor.ex#L730) (implementation)
|
||||||
|
|
||||||
**What it does:** Supervisors track restart frequency. If a child exceeds `max_restarts` within `max_seconds`, the supervisor itself shuts down (escalating the failure to its parent). Defaults: 3 restarts in 5 seconds.
|
**What it does:** Supervisors track restart frequency. If a child exceeds `max_restarts` within `max_seconds`, the supervisor itself shuts down (escalating the failure to its parent). Defaults: 3 restarts in 5 seconds.
|
||||||
|
|
||||||
@@ -422,7 +422,7 @@ Supervisor.init(children,
|
|||||||
|
|
||||||
## Pattern 5: Restart Values — `:permanent` vs `:transient` vs `:temporary`
|
## Pattern 5: Restart Values — `:permanent` vs `:transient` vs `:temporary`
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/supervisor.ex:130-152` (Restart values section)
|
**Source:** [lib/elixir/lib/supervisor.ex#L130](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/supervisor.ex#L130) (Restart values section)
|
||||||
|
|
||||||
**What it does:** Three restart policies control what happens when a child terminates:
|
**What it does:** Three restart policies control what happens when a child terminates:
|
||||||
- `:permanent` — always restart (default for GenServer/Agent/Supervisor)
|
- `:permanent` — always restart (default for GenServer/Agent/Supervisor)
|
||||||
@@ -522,7 +522,7 @@ end
|
|||||||
|
|
||||||
## Pattern 6: Automatic Shutdown for Pipeline Supervisors
|
## Pattern 6: Automatic Shutdown for Pipeline Supervisors
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/supervisor.ex:349-375` (Automatic shutdown section)
|
**Source:** [lib/elixir/lib/supervisor.ex#L349](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/supervisor.ex#L349) (Automatic shutdown section)
|
||||||
|
|
||||||
**What it does:** Supervisors support `:auto_shutdown` which terminates the supervisor when significant children exit. Options: `:any_significant` (first significant child exits → shutdown) or `:all_significant` (all significant children must exit → shutdown).
|
**What it does:** Supervisors support `:auto_shutdown` which terminates the supervisor when significant children exit. Options: `:any_significant` (first significant child exits → shutdown) or `:all_significant` (all significant children must exit → shutdown).
|
||||||
|
|
||||||
@@ -634,7 +634,7 @@ Supervisor.init(children, strategy: :one_for_one)
|
|||||||
|
|
||||||
## Pattern 7: Task.async/await for Concurrent Value Computation
|
## Pattern 7: Task.async/await for Concurrent Value Computation
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/task.ex:1-20` and `lib/elixir/lib/task.ex:300-340`
|
**Source:** [lib/elixir/lib/task.ex#L1](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/task.ex#L1) and [lib/elixir/lib/task.ex#L300](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/task.ex#L300)
|
||||||
|
|
||||||
**What it does:** `Task.async` spawns a linked, monitored process and returns a `%Task{}` struct. `Task.await` blocks until the result arrives or times out. This is the canonical pattern for "compute a value concurrently."
|
**What it does:** `Task.async` spawns a linked, monitored process and returns a `%Task{}` struct. `Task.await` blocks until the result arrives or times out. This is the canonical pattern for "compute a value concurrently."
|
||||||
|
|
||||||
@@ -718,7 +718,7 @@ end
|
|||||||
|
|
||||||
## Pattern 8: Task.Supervisor.async_nolink for Fault-Tolerant Task Execution
|
## Pattern 8: Task.Supervisor.async_nolink for Fault-Tolerant Task Execution
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/task/supervisor.ex:240-320` (async_nolink docs with GenServer example)
|
**Source:** [lib/elixir/lib/task/supervisor.ex#L240](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/task/supervisor.ex#L240) (async_nolink docs with GenServer example)
|
||||||
|
|
||||||
**What it does:** Unlike `Task.async`, `async_nolink` spawns a task that is NOT linked to the caller. The caller monitors it and handles success/failure via `handle_info`. This prevents a task crash from killing the caller.
|
**What it does:** Unlike `Task.async`, `async_nolink` spawns a task that is NOT linked to the caller. The caller monitors it and handles success/failure via `handle_info`. This prevents a task crash from killing the caller.
|
||||||
|
|
||||||
@@ -844,7 +844,7 @@ end
|
|||||||
|
|
||||||
## Pattern 9: Task Supervisor as DynamicSupervisor Specialization
|
## Pattern 9: Task Supervisor as DynamicSupervisor Specialization
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/task/supervisor.ex:151-165` (start_link implementation)
|
**Source:** [lib/elixir/lib/task/supervisor.ex#L151](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/task/supervisor.ex#L151) (start_link implementation)
|
||||||
|
|
||||||
**What it does:** `Task.Supervisor` is implemented directly on top of `DynamicSupervisor`. It stores default restart/shutdown settings in the process dictionary and delegates `init` to `DynamicSupervisor.init`.
|
**What it does:** `Task.Supervisor` is implemented directly on top of `DynamicSupervisor`. It stores default restart/shutdown settings in the process dictionary and delegates `init` to `DynamicSupervisor.init`.
|
||||||
|
|
||||||
@@ -936,7 +936,7 @@ DynamicSupervisor.start_child(MyApp.WorkerSupervisor, {MyApp.Worker, args})
|
|||||||
|
|
||||||
## Pattern 10: Registry for Dynamic Process Naming and PubSub
|
## Pattern 10: Registry for Dynamic Process Naming and PubSub
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/registry.ex:1-70` (module docs), `lib/elixir/lib/registry.ex:250-270` (whereis_name via callbacks)
|
**Source:** [lib/elixir/lib/registry.ex#L1](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/registry.ex#L1) (module docs), [lib/elixir/lib/registry.ex#L250](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/registry.ex#L250) (whereis_name via callbacks)
|
||||||
|
|
||||||
**What it does:** Registry provides two modes:
|
**What it does:** Registry provides two modes:
|
||||||
- `:unique` keys — each key maps to exactly one process (name registry, process lookup)
|
- `:unique` keys — each key maps to exactly one process (name registry, process lookup)
|
||||||
@@ -1053,7 +1053,7 @@ end
|
|||||||
|
|
||||||
## Pattern 11: Shutdown Semantics — Graceful Termination
|
## Pattern 11: Shutdown Semantics — Graceful Termination
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/supervisor.ex:156-192` (Shutdown values section)
|
**Source:** [lib/elixir/lib/supervisor.ex#L156](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/supervisor.ex#L156) (Shutdown values section)
|
||||||
|
|
||||||
**What it does:** Three shutdown modes:
|
**What it does:** Three shutdown modes:
|
||||||
- `:brutal_kill` — immediate `Process.exit(child, :kill)`, no cleanup
|
- `:brutal_kill` — immediate `Process.exit(child, :kill)`, no cleanup
|
||||||
@@ -1155,7 +1155,7 @@ end
|
|||||||
|
|
||||||
## Pattern 12: DynamicSupervisor Internal State — Struct with Restart Tracking
|
## Pattern 12: DynamicSupervisor Internal State — Struct with Restart Tracking
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/dynamic_supervisor.ex:165-178` (defstruct)
|
**Source:** [lib/elixir/lib/dynamic_supervisor.ex#L165](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/dynamic_supervisor.ex#L165) (defstruct)
|
||||||
|
|
||||||
**What it does:** The DynamicSupervisor uses a struct for its GenServer state with explicit fields: `children` (map of pid → child spec), `restarts` (list of timestamps for rate limiting), and configuration fields.
|
**What it does:** The DynamicSupervisor uses a struct for its GenServer state with explicit fields: `children` (map of pid → child spec), `restarts` (list of timestamps for rate limiting), and configuration fields.
|
||||||
|
|
||||||
@@ -1255,7 +1255,7 @@ end
|
|||||||
|
|
||||||
## Pattern 13: Restart Logic with Exponential Backoff via `:try_again`
|
## Pattern 13: Restart Logic with Exponential Backoff via `:try_again`
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/dynamic_supervisor.ex:710-758` (restart_child and related functions)
|
**Source:** [lib/elixir/lib/dynamic_supervisor.ex#L710](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/dynamic_supervisor.ex#L710) (restart_child and related functions)
|
||||||
|
|
||||||
**What it does:** When a child fails to restart (start function returns error), DynamicSupervisor doesn't give up. It stores the child as `{:restarting, child}`, sends itself a `:"$gen_restart"` message, and retries later. This prevents the supervisor from blocking on a transiently failing child.
|
**What it does:** When a child fails to restart (start function returns error), DynamicSupervisor doesn't give up. It stores the child as `{:restarting, child}`, sends itself a `:"$gen_restart"` message, and retries later. This prevents the supervisor from blocking on a transiently failing child.
|
||||||
|
|
||||||
@@ -1382,7 +1382,7 @@ end
|
|||||||
|
|
||||||
## Pattern 14: `$ancestors` and `$callers` — Process Lineage Tracking
|
## Pattern 14: `$ancestors` and `$callers` — Process Lineage Tracking
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/task.ex:227-268` (Ancestor and Caller Tracking section)
|
**Source:** [lib/elixir/lib/task.ex#L227](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/task.ex#L227) (Ancestor and Caller Tracking section)
|
||||||
|
|
||||||
**What it does:** Elixir uses two process dictionary keys for lineage:
|
**What it does:** Elixir uses two process dictionary keys for lineage:
|
||||||
- `$ancestors` — the supervision hierarchy (who spawned/supervises this process)
|
- `$ancestors` — the supervision hierarchy (who spawned/supervises this process)
|
||||||
@@ -1495,7 +1495,7 @@ end
|
|||||||
|
|
||||||
## Pattern 15: GenServer.reply/2 for Deferred Responses
|
## Pattern 15: GenServer.reply/2 for Deferred Responses
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:620-640` (callback docs), `lib/elixir/lib/gen_server.ex:1328-1346` (reply/2 function)
|
**Source:** [lib/elixir/lib/gen_server.ex#L620](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L620) (callback docs), [lib/elixir/lib/gen_server.ex#L1328](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L1328) (reply/2 function)
|
||||||
|
|
||||||
**What it does:** A `handle_call` can return `{:noreply, state}` without replying, then later call `GenServer.reply(from, response)` from any process. This decouples request receipt from response delivery.
|
**What it does:** A `handle_call` can return `{:noreply, state}` without replying, then later call `GenServer.reply(from, response)` from any process. This decouples request receipt from response delivery.
|
||||||
|
|
||||||
@@ -1606,7 +1606,7 @@ end
|
|||||||
|
|
||||||
## Pattern 16: Process.alias for Safe Request/Response
|
## Pattern 16: Process.alias for Safe Request/Response
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/process.ex:32-95` (Aliases section)
|
**Source:** [lib/elixir/lib/process.ex#L32](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/process.ex#L32) (Aliases section)
|
||||||
|
|
||||||
**What it does:** Process aliases (Erlang/OTP 24+) provide a deactivatable reference for receiving replies. After sending a request with an alias as the reply address, you can deactivate the alias if you no longer want the response — any messages sent to a deactivated alias are silently dropped.
|
**What it does:** Process aliases (Erlang/OTP 24+) provide a deactivatable reference for receiving replies. After sending a request with an alias as the reply address, you can deactivate the alias if you no longer want the response — any messages sent to a deactivated alias are silently dropped.
|
||||||
|
|
||||||
@@ -1714,7 +1714,7 @@ end
|
|||||||
|
|
||||||
## Pattern 17: Registry Partitioning Strategies
|
## Pattern 17: Registry Partitioning Strategies
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/registry.ex:310-350` (start_link partitioning docs)
|
**Source:** [lib/elixir/lib/registry.ex#L310](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/registry.ex#L310) (start_link partitioning docs)
|
||||||
|
|
||||||
**What it does:** Duplicate registries support two partitioning strategies:
|
**What it does:** Duplicate registries support two partitioning strategies:
|
||||||
- `{:duplicate, :pid}` (default) — groups entries by the registering process's PID. Good for few keys with many entries (e.g., one PubSub topic with many subscribers).
|
- `{:duplicate, :pid}` (default) — groups entries by the registering process's PID. Good for few keys with many entries (e.g., one PubSub topic with many subscribers).
|
||||||
@@ -1812,7 +1812,7 @@ Registry.start_link(
|
|||||||
|
|
||||||
## Pattern 18: `init/1` Return Values — The Full Spectrum
|
## Pattern 18: `init/1` Return Values — The Full Spectrum
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex:498-545` (init callback spec)
|
**Source:** [lib/elixir/lib/gen_server.ex#L498](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L498) (init callback spec)
|
||||||
|
|
||||||
**What it does:** `init/1` supports five return values:
|
**What it does:** `init/1` supports five return values:
|
||||||
- `{:ok, state}` — normal start
|
- `{:ok, state}` — normal start
|
||||||
@@ -1910,3 +1910,5 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Why:** `:ignore` means "this child intentionally should not run right now." `{:stop, reason}` means "this child tried to start and failed." Conflating the two hides real failures from your supervision tree.
|
**Why:** `:ignore` means "this child intentionally should not run right now." `{:stop, reason}` means "this child tried to start and failed." Conflating the two hides real failures from your supervision tree.
|
||||||
|
|
||||||
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
+22
-20
@@ -6,7 +6,7 @@ Patterns extracted from the Elixir standard library source code — how the core
|
|||||||
|
|
||||||
## 1. Module-Level Async Declaration
|
## 1. Module-Level Async Declaration
|
||||||
|
|
||||||
**Source:** `lib/elixir/test/elixir/gen_server_test.exs:9`, `lib/elixir/test/elixir/enum_test.exs:8`, nearly all test files
|
**Source:** [lib/elixir/test/elixir/gen_server_test.exs#L9](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/gen_server_test.exs#L9), [lib/elixir/test/elixir/enum_test.exs#L8](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/enum_test.exs#L8), nearly all test files
|
||||||
|
|
||||||
**What it does:** Every test module declares `async: true` or `async: false` at the module level, making concurrency intent explicit.
|
**What it does:** Every test module declares `async: true` or `async: false` at the module level, making concurrency intent explicit.
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ end
|
|||||||
|
|
||||||
## 2. Parameterized Tests
|
## 2. Parameterized Tests
|
||||||
|
|
||||||
**Source:** `lib/elixir/test/elixir/registry_test.exs:12-22`
|
**Source:** [lib/elixir/test/elixir/registry_test.exs#L12](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/registry_test.exs#L12)
|
||||||
|
|
||||||
**What it does:** Runs the same test suite against multiple configurations using the `:parameterize` option (since v1.18).
|
**What it does:** Runs the same test suite against multiple configurations using the `:parameterize` option (since v1.18).
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ end
|
|||||||
|
|
||||||
## 3. Setup with `start_supervised/2`
|
## 3. Setup with `start_supervised/2`
|
||||||
|
|
||||||
**Source:** `lib/ex_unit/lib/ex_unit/callbacks.ex:277-340`, `lib/elixir/test/elixir/registry_test.exs:31`
|
**Source:** [lib/ex_unit/lib/ex_unit/callbacks.ex#L277](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/ex_unit/lib/ex_unit/callbacks.ex#L277), [lib/elixir/test/elixir/registry_test.exs#L31](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/registry_test.exs#L31)
|
||||||
|
|
||||||
**What it does:** Starts processes under a test supervisor that guarantees cleanup before the next test.
|
**What it does:** Starts processes under a test supervisor that guarantees cleanup before the next test.
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ end
|
|||||||
|
|
||||||
## 4. Named Setup Functions (Composable Pipelines)
|
## 4. Named Setup Functions (Composable Pipelines)
|
||||||
|
|
||||||
**Source:** `lib/ex_unit/lib/ex_unit/callbacks.ex:100-120` (docs)
|
**Source:** [lib/ex_unit/lib/ex_unit/callbacks.ex#L100](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/ex_unit/lib/ex_unit/callbacks.ex#L100) (docs)
|
||||||
|
|
||||||
**What it does:** Defines setup as a list of named functions rather than anonymous blocks.
|
**What it does:** Defines setup as a list of named functions rather than anonymous blocks.
|
||||||
|
|
||||||
@@ -364,7 +364,7 @@ end
|
|||||||
|
|
||||||
## 5. `on_exit` for Reversing Global Side Effects
|
## 5. `on_exit` for Reversing Global Side Effects
|
||||||
|
|
||||||
**Source:** `lib/elixir/test/elixir/task_test.exs:1128-1131`, `lib/logger/test/logger_test.exs:12-17`
|
**Source:** [lib/elixir/test/elixir/task_test.exs#L1128](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/task_test.exs#L1128), [lib/logger/test/logger_test.exs#L12](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/logger/test/logger_test.exs#L12)
|
||||||
|
|
||||||
**What it does:** Registers cleanup callbacks that always run, even if the test fails.
|
**What it does:** Registers cleanup callbacks that always run, even if the test fails.
|
||||||
|
|
||||||
@@ -437,7 +437,7 @@ end
|
|||||||
|
|
||||||
## 6. Pattern Match Assertions
|
## 6. Pattern Match Assertions
|
||||||
|
|
||||||
**Source:** `lib/ex_unit/lib/ex_unit/assertions.ex:145-175`
|
**Source:** [lib/ex_unit/lib/ex_unit/assertions.ex#L145](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/ex_unit/lib/ex_unit/assertions.ex#L145)
|
||||||
|
|
||||||
**What it does:** Uses `assert` with `=` for structural pattern matching in assertions.
|
**What it does:** Uses `assert` with `=` for structural pattern matching in assertions.
|
||||||
|
|
||||||
@@ -506,7 +506,7 @@ assert create_user("alice") == {:ok, %User{name: "alice", active: true}}
|
|||||||
|
|
||||||
## 7. `assert_receive` / `refute_receive` for Process Communication
|
## 7. `assert_receive` / `refute_receive` for Process Communication
|
||||||
|
|
||||||
**Source:** `lib/ex_unit/lib/ex_unit/assertions.ex:466-526`, `lib/elixir/test/elixir/process_test.exs:90-100`
|
**Source:** [lib/ex_unit/lib/ex_unit/assertions.ex#L466](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/ex_unit/lib/ex_unit/assertions.ex#L466), [lib/elixir/test/elixir/process_test.exs#L90](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/process_test.exs#L90)
|
||||||
|
|
||||||
**What it does:** Waits for messages matching a pattern within a timeout (default 100ms).
|
**What it does:** Waits for messages matching a pattern within a timeout (default 100ms).
|
||||||
|
|
||||||
@@ -590,7 +590,7 @@ end
|
|||||||
|
|
||||||
## 8. Testing GenServers via Public API (No Internal State Inspection)
|
## 8. Testing GenServers via Public API (No Internal State Inspection)
|
||||||
|
|
||||||
**Source:** `lib/elixir/test/elixir/gen_server_test.exs:87-106`
|
**Source:** [lib/elixir/test/elixir/gen_server_test.exs#L87](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/gen_server_test.exs#L87)
|
||||||
|
|
||||||
**What it does:** Tests GenServer behavior exclusively through `GenServer.call/cast/stop` — never peeks at internal state.
|
**What it does:** Tests GenServer behavior exclusively through `GenServer.call/cast/stop` — never peeks at internal state.
|
||||||
|
|
||||||
@@ -670,7 +670,7 @@ end
|
|||||||
|
|
||||||
## 9. `catch_exit` for Testing Process Failures
|
## 9. `catch_exit` for Testing Process Failures
|
||||||
|
|
||||||
**Source:** `lib/ex_unit/lib/ex_unit/assertions.ex:950-960`, `lib/elixir/test/elixir/gen_server_test.exs:118-137`
|
**Source:** [lib/ex_unit/lib/ex_unit/assertions.ex#L950](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/ex_unit/lib/ex_unit/assertions.ex#L950), [lib/elixir/test/elixir/gen_server_test.exs#L118](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/gen_server_test.exs#L118)
|
||||||
|
|
||||||
**What it does:** Catches exit signals from linked processes for assertion, or uses `Process.flag(:trap_exit, true)` + `assert_receive {:EXIT, ...}`.
|
**What it does:** Catches exit signals from linked processes for assertion, or uses `Process.flag(:trap_exit, true)` + `assert_receive {:EXIT, ...}`.
|
||||||
|
|
||||||
@@ -747,7 +747,7 @@ end
|
|||||||
|
|
||||||
## 10. `@tag capture_log: true` for Suppressing Expected Log Output
|
## 10. `@tag capture_log: true` for Suppressing Expected Log Output
|
||||||
|
|
||||||
**Source:** `lib/elixir/test/elixir/gen_server_test.exs:114`, `lib/elixir/test/elixir/task_test.exs:10`
|
**Source:** [lib/elixir/test/elixir/gen_server_test.exs#L114](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/gen_server_test.exs#L114), [lib/elixir/test/elixir/task_test.exs#L10](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/task_test.exs#L10)
|
||||||
|
|
||||||
**What it does:** Captures log output during the test, only printing it if the test fails.
|
**What it does:** Captures log output during the test, only printing it if the test fails.
|
||||||
|
|
||||||
@@ -833,7 +833,7 @@ end
|
|||||||
|
|
||||||
## 11. `capture_log` / `capture_io` for Content Assertions
|
## 11. `capture_log` / `capture_io` for Content Assertions
|
||||||
|
|
||||||
**Source:** `lib/ex_unit/lib/ex_unit/capture_log.ex:1-50`, `lib/elixir/test/elixir/task_test.exs:1138-1150`
|
**Source:** [lib/ex_unit/lib/ex_unit/capture_log.ex#L1](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/ex_unit/lib/ex_unit/capture_log.ex#L1), [lib/elixir/test/elixir/task_test.exs#L1138](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/task_test.exs#L1138)
|
||||||
|
|
||||||
**What it does:** Captures log/IO output and returns it as a string for assertion.
|
**What it does:** Captures log/IO output and returns it as a string for assertion.
|
||||||
|
|
||||||
@@ -918,7 +918,7 @@ end
|
|||||||
|
|
||||||
## 12. `describe` Blocks for Logical Grouping
|
## 12. `describe` Blocks for Logical Grouping
|
||||||
|
|
||||||
**Source:** `lib/elixir/test/elixir/task_test.exs:218,272,365`, `lib/elixir/test/elixir/process_test.exs:146`
|
**Source:** `lib/elixir/test/elixir/task_test.exs:218,272,365`, [lib/elixir/test/elixir/process_test.exs#L146](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/process_test.exs#L146)
|
||||||
|
|
||||||
**What it does:** Groups related tests under a named describe block. Setup inside describe only applies to that group.
|
**What it does:** Groups related tests under a named describe block. Setup inside describe only applies to that group.
|
||||||
|
|
||||||
@@ -1000,7 +1000,7 @@ end
|
|||||||
|
|
||||||
## 13. `ExUnit.CaseTemplate` for Shared Test Infrastructure
|
## 13. `ExUnit.CaseTemplate` for Shared Test Infrastructure
|
||||||
|
|
||||||
**Source:** `lib/mix/test/test_helper.exs:79-140`, `lib/logger/test/test_helper.exs:24-65`
|
**Source:** [lib/mix/test/test_helper.exs#L79](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/mix/test/test_helper.exs#L79), [lib/logger/test/test_helper.exs#L24](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/logger/test/test_helper.exs#L24)
|
||||||
|
|
||||||
**What it does:** Defines reusable test case templates with shared setup, helpers, and imports.
|
**What it does:** Defines reusable test case templates with shared setup, helpers, and imports.
|
||||||
|
|
||||||
@@ -1124,7 +1124,7 @@ defmodule MyApp.ChannelCase do ... end # WebSocket tests
|
|||||||
|
|
||||||
## 14. `doctest` Integration
|
## 14. `doctest` Integration
|
||||||
|
|
||||||
**Source:** `lib/ex_unit/lib/ex_unit/doc_test.ex:1-80`, `lib/elixir/test/elixir/agent_test.exs:9`
|
**Source:** [lib/ex_unit/lib/ex_unit/doc_test.ex#L1](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/ex_unit/lib/ex_unit/doc_test.ex#L1), [lib/elixir/test/elixir/agent_test.exs#L9](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/agent_test.exs#L9)
|
||||||
|
|
||||||
**What it does:** Generates tests from `@doc` and `@moduledoc` code examples.
|
**What it does:** Generates tests from `@doc` and `@moduledoc` code examples.
|
||||||
|
|
||||||
@@ -1209,7 +1209,7 @@ Creates a user.
|
|||||||
|
|
||||||
## 15. `Process.sleep(:infinity)` as a Process Parking Pattern
|
## 15. `Process.sleep(:infinity)` as a Process Parking Pattern
|
||||||
|
|
||||||
**Source:** `lib/elixir/test/elixir/task_test.exs:417`, `lib/elixir/test/elixir/registry_test.exs:71`
|
**Source:** [lib/elixir/test/elixir/task_test.exs#L417](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/task_test.exs#L417), [lib/elixir/test/elixir/registry_test.exs#L71](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/registry_test.exs#L71)
|
||||||
|
|
||||||
**What it does:** Spawns processes that block forever, used as test subjects that need to exist until explicitly killed.
|
**What it does:** Spawns processes that block forever, used as test subjects that need to exist until explicitly killed.
|
||||||
|
|
||||||
@@ -1297,7 +1297,7 @@ end
|
|||||||
|
|
||||||
## 16. Helper Functions for Test-Specific Behavior
|
## 16. Helper Functions for Test-Specific Behavior
|
||||||
|
|
||||||
**Source:** `lib/elixir/test/elixir/task_test.exs:12-36`, `lib/elixir/test/elixir/supervisor_test.exs:278-285`
|
**Source:** [lib/elixir/test/elixir/task_test.exs#L12](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/task_test.exs#L12), [lib/elixir/test/elixir/supervisor_test.exs#L278](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/supervisor_test.exs#L278)
|
||||||
|
|
||||||
**What it does:** Defines private helper functions within test modules for common test operations.
|
**What it does:** Defines private helper functions within test modules for common test operations.
|
||||||
|
|
||||||
@@ -1409,7 +1409,7 @@ end
|
|||||||
|
|
||||||
## 17. `@tag :tmp_dir` for Filesystem Tests
|
## 17. `@tag :tmp_dir` for Filesystem Tests
|
||||||
|
|
||||||
**Source:** `lib/ex_unit/lib/ex_unit/case.ex:281-304`, `lib/elixir/test/elixir/path_test.exs:12`
|
**Source:** [lib/ex_unit/lib/ex_unit/case.ex#L281](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/ex_unit/lib/ex_unit/case.ex#L281), [lib/elixir/test/elixir/path_test.exs#L12](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/path_test.exs#L12)
|
||||||
|
|
||||||
**What it does:** ExUnit automatically creates a unique temporary directory and passes its path via the test context.
|
**What it does:** ExUnit automatically creates a unique temporary directory and passes its path via the test context.
|
||||||
|
|
||||||
@@ -1482,7 +1482,7 @@ end
|
|||||||
|
|
||||||
## 18. `assert_raise` with Message Matching
|
## 18. `assert_raise` with Message Matching
|
||||||
|
|
||||||
**Source:** `lib/ex_unit/lib/ex_unit/assertions.ex:815-885`
|
**Source:** [lib/ex_unit/lib/ex_unit/assertions.ex#L815](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/ex_unit/lib/ex_unit/assertions.ex#L815)
|
||||||
|
|
||||||
**What it does:** Asserts both the exception type AND the message content (string or regex).
|
**What it does:** Asserts both the exception type AND the message content (string or regex).
|
||||||
|
|
||||||
@@ -1564,7 +1564,7 @@ end
|
|||||||
|
|
||||||
## 19. `@moduletag` / `@describetag` for Cross-Cutting Configuration
|
## 19. `@moduletag` / `@describetag` for Cross-Cutting Configuration
|
||||||
|
|
||||||
**Source:** `lib/elixir/test/elixir/system_test.exs:104,163`, `lib/elixir/test/elixir/task_test.exs:10`
|
**Source:** `lib/elixir/test/elixir/system_test.exs:104,163`, [lib/elixir/test/elixir/task_test.exs#L10](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/task_test.exs#L10)
|
||||||
|
|
||||||
**What it does:** Sets tags that apply to all tests in a module or describe block, used for filtering and configuration.
|
**What it does:** Sets tags that apply to all tests in a module or describe block, used for filtering and configuration.
|
||||||
|
|
||||||
@@ -1651,7 +1651,7 @@ ExUnit.configure(exclude: [:unix], include: [])
|
|||||||
|
|
||||||
## 20. Context Pattern Matching in Test Signatures
|
## 20. Context Pattern Matching in Test Signatures
|
||||||
|
|
||||||
**Source:** `lib/ex_unit/lib/ex_unit/case.ex:57-80`, `lib/elixir/test/elixir/gen_server_test.exs:166`
|
**Source:** [lib/ex_unit/lib/ex_unit/case.ex#L57](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/ex_unit/lib/ex_unit/case.ex#L57), [lib/elixir/test/elixir/gen_server_test.exs#L166](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/test/elixir/gen_server_test.exs#L166)
|
||||||
|
|
||||||
**What it does:** Destructures the test context directly in the test function signature.
|
**What it does:** Destructures the test context directly in the test function signature.
|
||||||
|
|
||||||
@@ -1724,3 +1724,5 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Why:** Context destructuring signals "this test depends on external setup." If the test is self-contained, the pattern match is misleading — readers will look for setup that doesn't exist or isn't needed.
|
**Why:** Context destructuring signals "this test depends on external setup." If the test is self-contained, the pattern match is misleading — readers will look for setup that doesn't exist or isn't needed.
|
||||||
|
|
||||||
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
+12
-10
@@ -6,7 +6,7 @@ Patterns extracted from the Elixir standard library source code.
|
|||||||
|
|
||||||
## 1. Public Type with @typedoc
|
## 1. Public Type with @typedoc
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex` lines 862–896
|
**Source:** [lib/elixir/lib/gen_server.ex#L862](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L862)
|
||||||
|
|
||||||
**What it does:** Every public `@type` is preceded by a `@typedoc` that explains what the type represents, often referencing which functions use it.
|
**What it does:** Every public `@type` is preceded by a `@typedoc` that explains what the type represents, often referencing which functions use it.
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ the current state of a user account.
|
|||||||
|
|
||||||
## 2. Private Types with @typep
|
## 2. Private Types with @typep
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/macro.ex` lines 84, 97
|
**Source:** [lib/elixir/lib/macro.ex#L84](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/macro.ex#L84), 97
|
||||||
|
|
||||||
**What it does:** Uses `@typep` for internal recursive type definitions that are implementation details not meant for external consumers.
|
**What it does:** Uses `@typep` for internal recursive type definitions that are implementation details not meant for external consumers.
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ end
|
|||||||
|
|
||||||
## 3. @opaque Types (Protocol t())
|
## 3. @opaque Types (Protocol t())
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/protocol.ex` lines 150–168 (documentation), runtime generation
|
**Source:** [lib/elixir/lib/protocol.ex#L150](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/protocol.ex#L150) (documentation), runtime generation
|
||||||
|
|
||||||
**What it does:** Protocols auto-generate an opaque `@type t :: term()` type that represents "any value implementing this protocol."
|
**What it does:** Protocols auto-generate an opaque `@type t :: term()` type that represents "any value implementing this protocol."
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ end
|
|||||||
|
|
||||||
## 4. Union Types in @spec Return Values
|
## 4. Union Types in @spec Return Values
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex` lines 577–583, 647–658
|
**Source:** [lib/elixir/lib/gen_server.ex#L577](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L577), 647–658
|
||||||
|
|
||||||
**What it does:** Uses union types with descriptive tagged tuples in callback specs, making all possible return shapes explicit.
|
**What it does:** Uses union types with descriptive tagged tuples in callback specs, making all possible return shapes explicit.
|
||||||
|
|
||||||
@@ -355,7 +355,7 @@ end
|
|||||||
|
|
||||||
## 5. `when` Constraints in Specs
|
## 5. `when` Constraints in Specs
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/kernel.ex` lines 635, 1072, 1455
|
**Source:** [lib/elixir/lib/kernel.ex#L635](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L635), 1072, 1455
|
||||||
|
|
||||||
**What it does:** Uses the `when` clause in `@spec` to bind type variables, expressing relationships between parameters and return values.
|
**What it does:** Uses the `when` clause in `@spec` to bind type variables, expressing relationships between parameters and return values.
|
||||||
|
|
||||||
@@ -414,7 +414,7 @@ end
|
|||||||
|
|
||||||
## 6. Map Types with required/optional Keys
|
## 6. Map Types with required/optional Keys
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/supervisor.ex` lines 602–607, 644–652
|
**Source:** [lib/elixir/lib/supervisor.ex#L602](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/supervisor.ex#L602), 644–652
|
||||||
|
|
||||||
**What it does:** Uses map type syntax with `required()` and `optional()` keys to define struct-like specs where some fields have defaults.
|
**What it does:** Uses map type syntax with `required()` and `optional()` keys to define struct-like specs where some fields have defaults.
|
||||||
|
|
||||||
@@ -499,7 +499,7 @@ end
|
|||||||
|
|
||||||
## 7. Keyword List Types for Options
|
## 7. Keyword List Types for Options
|
||||||
|
|
||||||
**Source:** `lib/logger/lib/logger.ex` lines 509–531
|
**Source:** [lib/logger/lib/logger.ex#L509](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/logger/lib/logger.ex#L509)
|
||||||
|
|
||||||
**What it does:** Defines option types as keyword lists with specific key-value constraints, sometimes nested.
|
**What it does:** Defines option types as keyword lists with specific key-value constraints, sometimes nested.
|
||||||
|
|
||||||
@@ -588,7 +588,7 @@ end
|
|||||||
|
|
||||||
## 8. Parameterized Types (t/1)
|
## 8. Parameterized Types (t/1)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/enum.ex` lines 58–73 (Enumerable protocol)
|
**Source:** [lib/elixir/lib/enum.ex#L58](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/enum.ex#L58) (Enumerable protocol)
|
||||||
|
|
||||||
**What it does:** Defines a parameterized type `t(_element)` that allows expressing the element type of an enumerable in function specs.
|
**What it does:** Defines a parameterized type `t(_element)` that allows expressing the element type of an enumerable in function specs.
|
||||||
|
|
||||||
@@ -670,7 +670,7 @@ integers and returns an enumerable of strings:
|
|||||||
|
|
||||||
## 9. Named Parameters in Specs (:: annotation)
|
## 9. Named Parameters in Specs (:: annotation)
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/gen_server.ex` line 577, `lib/elixir/lib/supervisor.ex` line 562
|
**Source:** [lib/elixir/lib/gen_server.ex#L577](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L577), [lib/elixir/lib/supervisor.ex#L562](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/supervisor.ex#L562)
|
||||||
|
|
||||||
**What it does:** Uses `name :: type` syntax in callback/spec parameter positions to give meaningful names to parameters.
|
**What it does:** Uses `name :: type` syntax in callback/spec parameter positions to give meaningful names to parameters.
|
||||||
|
|
||||||
@@ -735,7 +735,7 @@ integers and returns an enumerable of strings:
|
|||||||
|
|
||||||
## 10. @typedoc since: Annotation
|
## 10. @typedoc since: Annotation
|
||||||
|
|
||||||
**Source:** `lib/elixir/lib/supervisor.ex` lines 669–670, `lib/elixir/lib/enum.ex` line 72
|
**Source:** [lib/elixir/lib/supervisor.ex#L669](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/supervisor.ex#L669), [lib/elixir/lib/enum.ex#L72](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/enum.ex#L72)
|
||||||
|
|
||||||
**What it does:** Attaches a `since:` metadata annotation to `@typedoc` indicating when a type was introduced.
|
**What it does:** Attaches a `since:` metadata annotation to `@typedoc` indicating when a type was introduced.
|
||||||
|
|
||||||
@@ -797,3 +797,5 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Why:** `since:` annotations are for library consumers checking compatibility across versions. Application code doesn't have "consumers" checking which version introduced a type — it's all deployed together.
|
**Why:** `since:` annotations are for library consumers checking compatibility across versions. Application code doesn't have "consumers" checking which version introduced a type — it's all deployed together.
|
||||||
|
|
||||||
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
@@ -1035,3 +1035,5 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Why it's OK here:** The dispatch is by configured module (strategy pattern), not by data type. You want compile-time verification that the storage module implements all required operations. A protocol wouldn't help because the data going in is always the same type — it's the *implementation* that varies.
|
**Why it's OK here:** The dispatch is by configured module (strategy pattern), not by data type. You want compile-time verification that the storage module implements all required operations. A protocol wouldn't help because the data going in is always the same type — it's the *implementation* that varies.
|
||||||
|
|
||||||
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
@@ -1158,3 +1158,5 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Why it's OK here:** There's no conditional logic — every iteration tests the exact same behavior with a trivially predictable result. If one fails, the assertion message includes the specific value. The loop is purely for conciseness.
|
**Why it's OK here:** There's no conditional logic — every iteration tests the exact same behavior with a trivially predictable result. If one fails, the assertion message includes the specific value. The loop is purely for conciseness.
|
||||||
|
|
||||||
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
Reference in New Issue
Block a user