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.
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
# 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 -->
|
||||
Reference in New Issue
Block a user