74101b513c
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.
267 lines
7.7 KiB
Markdown
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 -->
|