Files
elixir-patterns/sources/elixir-lang.md
T
Rodin 74101b513c chore: merge elixir-conventions and oban-conventions into sources/
Absorbed content from rodin/elixir-conventions and rodin/oban-conventions
into a sources/ directory. These are reference material — descriptive,
not prescriptive. Patterns that prove broadly applicable get promoted
into patterns/.

Part of taxonomy cleanup (issue #4):
- Pattern = prescriptive, follow these
- Convention/Source = reference, study for ideas

The original repos can now be archived.
2026-05-07 18:01:42 -07:00

267 lines
7.7 KiB
Markdown

# Elixir Language Source: Convention Reference
Quick-reference for conventions extracted from the elixir-lang/elixir
source code. Each entry: pattern name, location, example, when to use,
when NOT to use, origin.
---
## Version-Gated TODOs
**Location:** Throughout `lib/`
```elixir
# TODO: Remove me on v2.0
# TODO: Deprecate me on Elixir v1.23
# TODO: Make an error on Elixir v2.0
```
**When to use:** Any backward-compatible code that should be removed at
a known future version. Deprecation paths, compatibility shims, feature
flags.
**When NOT to use:** Performance improvements ("make this faster
someday"), refactoring desires, or anything without a clear version
boundary. Those belong in issues, not TODOs.
**Origin:** Consistent throughout history. The pattern predates the
repo's earliest commits — José's convention from the start.
---
## Independent OTP Applications
**Location:** `lib/` (6 top-level dirs)
```
lib/elixir/mix.exs # The language core
lib/ex_unit/mix.exs # Testing framework
lib/mix/mix.exs # Build tool
lib/logger/mix.exs # Logging
lib/iex/mix.exs # Interactive shell
lib/eex/mix.exs # Templates
```
**When to use:** When components have distinct lifecycle, deployment, or
dependency requirements. When you want components to be independently
testable.
**When NOT to use:** Small projects where the overhead of multiple
applications exceeds the organizational benefit. If components always
deploy together and never independently, a single app is simpler.
**Origin:** Elixir 0.x — the language was always structured this way.
---
## Erlang for Performance-Critical Paths
**Location:** `lib/elixir/src/` (33 .erl files)
```erlang
%% elixir_tokenizer.erl — binary pattern matching is faster in Erlang
%% elixir_erl_pass.erl — code generation benefits from proximity to BEAM
```
**When to use:** When Erlang's binary pattern matching or NIF interface
provides measurable performance advantage. When code must exist before
Elixir's compiler is available (bootstrap).
**When NOT to use:** For new features that could be written in Elixir.
The trajectory is always toward less Erlang. Don't write new Erlang
unless profiling proves it's necessary.
**Origin:** The entire language started as Erlang (2011). Bootstrap file
(`elixir_bootstrap.erl`) formalized as distinct from "Elixir written
in Erlang" in 2013 (commit `260be7c8e`).
---
## Protocol Consolidation
**Location:** `lib/elixir/lib/protocol.ex`
```elixir
# In mix.exs (default for prod):
consolidate_protocols: true
# Disable for tests:
consolidate_protocols: Mix.env() != :test
```
**When to use:** Always in production (it's the default). Consolidated
protocols dispatch in two function calls instead of dynamic lookup.
**When NOT to use:** In tests where you define new protocol
implementations at runtime. In dev if you're frequently recompiling
protocol implementations.
**Origin:** PR adding consolidation to escript.build (2014, issue
#2699). Later made default for Mix projects.
---
## Code.Formatter via Inspect.Algebra
**Location:** `lib/elixir/lib/code/formatter.ex` (2,605 lines)
```elixir
# The formatter is a library function:
Code.format_string!("def foo( x,y),do: x+y")
# => "def foo(x, y), do: x + y"
# Internally uses Wadler-Lindig algebra:
import Inspect.Algebra, except: [format: 2, surround: 3, surround: 4]
```
**When to use:** When building code generation tools, macros that
produce source, or custom formatting rules. The algebra is available
to any Elixir code.
**When NOT to use:** Don't reimplement formatting logic. Use
`Code.format_string!/2` or the `mix format` task. The algebra is for
building formatters, not for end-user formatting.
**Origin:** Oct 7, 2017 (PR #6639, José Valim). Merged in 1 hour, zero
review comments.
---
## Mix Task Convention
**Location:** `lib/mix/lib/mix/tasks/` (55 files)
```elixir
defmodule Mix.Tasks.Deps.Clean do
@moduledoc "Removes the given dependencies' build artifacts."
@shortdoc "Deletes generated files and artifacts for dependencies"
use Mix.Task
@recursive true
@impl true
def run(args) do
# ...
end
end
```
**When to use:** Any command-line operation that should be available via
`mix <task>`. One file per task, module name determines task name.
**When NOT to use:** Tasks that require interactive user input (use
escript or IEx helpers instead). Tasks that need to run without Mix
loaded.
**Origin:** Part of Mix since its creation. The one-file convention
is strict — all 55 stdlib tasks follow it.
---
## ExUnit.CaseTemplate
**Location:** `lib/ex_unit/lib/ex_unit/case_template.ex` (162 lines)
```elixir
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()})
:ok
end
end
# Usage:
defmodule MyApp.SomeTest do
use MyApp.DataCase, async: true
end
```
**When to use:** When multiple test modules share setup logic,
assertions, or helper functions. The inheritance model allows
composition of test concerns.
**When NOT to use:** For test helpers that don't need lifecycle
callbacks. Simple `import` or `alias` is cleaner for utility functions
that don't need `setup`/`setup_all`.
**Origin:** Commit `1f491dfbe` — "Add support to case templates." The
pattern Phoenix adopted for `ConnCase`/`DataCase` comes directly from
ExUnit.
---
## Logger Wrapping OTP
**Location:** `lib/logger/`
```elixir
# Elixir's Logger dispatches to Erlang's :logger
# Translation layer converts Erlang messages → Elixir format
# Filters and handlers use OTP's infrastructure
```
**When to use:** When OTP provides the infrastructure you need.
Wrap it — don't replace it. Provide Elixir-idiomatic API on top.
**When NOT to use:** When OTP's solution has fundamental architectural
limitations you can't work around with a wrapper (rare).
**Origin:** PR #9333 (Sep-Nov 2019). Rewrote Logger from custom
dispatch to wrapping Erlang's `:logger`. ~2 month implementation.
---
## BDFL Merge Pattern
**Location:** Throughout git history
```
PR #6639 (Formatter): 0 comments, merged in 1 hour, 2,605 new lines
PR #14021 (JSON): 38 review comments, 13 days, community input
PR #13385 (Duration): 75+116 comments, 33 days, community redirected
```
**When to use:** Foundational infrastructure where one person holds the
architectural vision. The BDFL can ship without review when the change
is self-evidently correct (formatter) or redirect community work when
the abstraction isn't right (Duration).
**When NOT to use:** In team-maintained projects without a clear
authority. In projects where consensus is required for API decisions.
The BDFL model fails when the BDFL is wrong and no one can override.
**Origin:** José Valim has been sole authority since Elixir's creation
(2011). The core team exists but José has final say on language design.
---
## Type System Architecture (Set-Theoretic)
**Location:** `lib/elixir/lib/module/types/` (13,034 lines, 7 files)
```elixir
# Types are sets. Operations are set operations.
# descr.ex (6,301 lines) defines the algebra:
# - Union of types
# - Intersection of types
# - Difference (negation types)
# - Subtype checking via set inclusion
```
**When to use:** Understanding how Elixir's gradual type system works
internally. The set-theoretic approach means types compose naturally —
`integer() | String.t()` is literally a set union.
**When NOT to use:** This is internal compiler infrastructure. Don't
depend on `Module.Types` internals — they change frequently (504
commits and counting).
**Origin:** Aug 2019 (PR #9270 by Eric Meadows-Jönsson). Originally
just function clause exhaustiveness checking, now growing into full
gradual typing. 96% of subsequent work by José.
<!-- PATTERN_COMPLETE -->