chore: remove leftover scripts and tooling artifacts

Remove Python enhancement scripts (enhance_macros3.py,
enhance_modules.py, enhance_modules2.py), watermark file,
and changelog directory. These were scaffolding from the
initial extraction pass.
This commit is contained in:
Rodin
2026-04-30 15:49:05 -07:00
parent 5f62dd0bf1
commit 4bc7f3b357
6 changed files with 0 additions and 1657 deletions
-7
View File
@@ -1,7 +0,0 @@
{
"source_repo": "elixir-lang/elixir",
"last_digest_sha": "55a3899e75efd579723da1927500b82f206329e4",
"last_digest_at": "2026-04-30T14:01:00Z",
"last_refresh_sha": null,
"last_refresh_at": null
}
View File
-44
View File
@@ -1,44 +0,0 @@
# Elixir Digest — 2026-04-30
## Type System / Gradual Typing
### #15324 — Fix invalid type inference on size compare in guards
- **Author:** lukaszsamson
- **Merged:** 2026-04-29
- Mirror expressions like `tuple_size(x) >= 2` and `2 <= tuple_size(x)` were producing different type inferences.
- Fix introduces `mirror_order/1` helper that correctly flips comparison operators when operands are swapped.
- Impact: Guards with size on the right now correctly infer the same type as left-sided equivalents.
### #15322 — Fix map union optimization for open maps
- **Author:** gldubc (Guillaume Duboc)
- **Merged:** 2026-04-29
- Optimizer subtype shortcut incorrectly concluded open map contained by closed map when field types matched.
- Open maps can have extra keys — fix rejects shortcut on tag (open/closed) mismatch.
- Impact: Fixes false type narrowing with unions of open and closed map types.
### #15319 — Fix map difference union optimization
- **Author:** gldubc (Guillaume Duboc)
- **Merged:** 2026-04-28
- Disabled `:union` case of single-key open-map difference optimization.
- Could build invalid union literals causing double negation to leave phantom empty maps.
## Core / Runtime
### #15306 — Fix reentrancy of Code.eval_*
- **Author:** lukaszsamson
- **Merged:** 2026-04-28
- Nested `Code.eval_string` clobbered outer eval's `dbg_callback` and `?elixir_eval_env` in process dict.
- Fix: save/restore pattern (save before, restore in `after` block).
- Discussion: Jonatan Kłosko caught test was not actually testing nested eval; José simplified implementation.
- Lesson: Process dictionary as implicit state = reentrancy bugs. Deliberate trade-off for version decoupling.
### #15316 — Consistently return path as binary in relative_to_cwd
- **Author:** lukaszsamson
- **Merged:** 2026-04-28
- `Path.relative_to_cwd/1` could return chardata on `:file.get_cwd` failure.
- All code paths now normalize to binary.
## Patterns to Extract
- **Process dict save/restore for reentrancy** (#15306): When using process dict as implicit state, always save/restore in try/after to handle reentrancy. The Elixir team chose this over closures to avoid coupling eval to specific Elixir versions.
- **Set-theoretic type system edge cases** (#15322, #15319): Open vs closed map distinction is subtle in BDD-based type representations. Subtype checks must respect structural tags, not just field types.
-749
View File
@@ -1,749 +0,0 @@
#!/usr/bin/env python3
"""Add When to Use / When NOT to Use sections to macros.md
Parts: 0=preamble, 1=section1, 2=section2, ..., 12=section12"""
with open("patterns/macros.md", "r") as f:
content = f.read()
separator = "\n\n---\n\n"
parts = content.split(separator)
assert len(parts) == 13, f"Expected 13 parts, got {len(parts)}"
# Index 1-12 maps to sections 1-12
when_sections = {
1: '''
### When to Use
**Triggers:**
- A macro must behave differently in guards vs normal code
- You're writing an operator-like macro that users will put in guard clauses
- The same syntax needs different compilation strategies per context
**Example — before:**
```elixir
# Macro that only works in normal code, crashes in guards
defmacro my_or(left, right) do
quote do
case unquote(left) do
x when x in [false, nil] -> unquote(right)
x -> x
end
end
end
```
**Example — after:**
```elixir
defmacro my_or(left, right) do
case __CALLER__.context do
nil -> quote do
case unquote(left) do
x when x in [false, nil] -> unquote(right)
x -> x
end
end
:guard -> quote(do: :erlang.orelse(unquote(left), unquote(right)))
:match -> raise ArgumentError, "cannot use or/2 in match"
end
end
```
### When NOT to Use
**Don't use this when:**
- Your macro will never be used in guards (most macros)
- A simple `defguard` would suffice for guard-only usage
**Over-application example:**
```elixir
# Checking __CALLER__.context in a macro that just generates module-level code
defmacro define_schema(fields) do
case __CALLER__.context do
nil -> generate_schema(fields)
:guard -> raise "can't use in guard" # Unnecessary
:match -> raise "can't use in match" # Unnecessary
end
end
```
**Better alternative:**
```elixir
defmacro define_schema(fields) do
generate_schema(fields)
end
```
**Why:** Context-checking adds complexity. Only use it when there's a legitimate code path for guards or matches. Module-level macros like schema definitions will never appear in guard context.''',
2: '''
### When to Use
**Triggers:**
- You need a guard-safe check that combines multiple guard expressions
- Multiple modules share the same guard condition
- The guard logic is complex enough to warrant a name
**Example — before:**
```elixir
# Repeating guard logic everywhere
def process(n) when is_integer(n) and rem(n, 2) == 0 do
# handle even...
end
def validate(n) when is_integer(n) and rem(n, 2) == 0 do
# validate even...
end
```
**Example — after:**
```elixir
defmodule Guards do
defguard is_even(n) when is_integer(n) and rem(n, 2) == 0
end
import Guards
def process(n) when is_even(n), do: # ...
def validate(n) when is_even(n), do: # ...
```
### When NOT to Use
**Don't use this when:**
- The check isn't guard-safe (calls functions, uses try/rescue, etc.)
- The guard is used in one place and is already readable
- You need runtime logic like database lookups in the check
**Over-application example:**
```elixir
# Trying to use defguard for something that needs runtime state
defguard is_admin(user_id) when user_id in @admin_ids
# @admin_ids is a module attribute — frozen at compile time!
```
**Better alternative:**
```elixir
def admin?(user_id), do: user_id in load_admin_ids()
def process(request) do
if admin?(request.user_id), do: # ...
end
```
**Why:** `defguard` expressions are evaluated at compile time with only BIF access. If your "guard" needs runtime data, config, or database state, it's not a guard — it's a function.''',
3: '''
### When to Use
**Triggers:**
- A macro receives arguments that should be evaluated once in the expansion
- You're generating code that interpolates compile-time values into runtime code
- The macro argument has side effects or is expensive to compute
**Example — before:**
```elixir
# unquote(fields) evaluated twice
defmacro setup(fields) do
quote do
validated = validate(unquote(fields))
@fields unquote(fields) # fields expression runs again!
end
end
```
**Example — after:**
```elixir
defmacro setup(fields) do
quote bind_quoted: [fields: fields] do
validated = validate(fields)
@fields fields # Same bound value, no double-evaluation
end
end
```
### When NOT to Use
**Don't use this when:**
- You need to unquote into a pattern match position (bind_quoted can't do this)
- The expression is a simple literal or variable with no side effects
- You're building complex AST where you need fine-grained unquote placement
**Over-application example:**
```elixir
# bind_quoted fails in pattern positions
defmacro match_field(field, value) do
quote bind_quoted: [field: field, value: value] do
def handle(%{field => result}) when result == value do
result
end
end
end
```
**Better alternative:**
```elixir
defmacro match_field(field, value) do
quote do
def handle(%{unquote(field) => result}) when result == unquote(value) do
result
end
end
end
```
**Why:** `bind_quoted` wraps values in regular variable bindings, which can't appear in pattern match positions. When you need to inject into patterns or guards, use direct `unquote`.''',
4: '''
### When to Use
**Triggers:**
- A macro must inject code that references a variable from the caller's scope
- Building test/assertion macros where you need access to the test binding
- Module-level macros that accumulate into a module attribute the caller defines
**Example — before:**
```elixir
# Hygienic variable — creates a NEW binding, doesn't access caller's
defmacro assert_stored(key) do
quote do
assert store[unquote(key)] # 'store' is hygienic — undefined in caller!
end
end
```
**Example — after:**
```elixir
defmacro assert_stored(key) do
quote do
assert var!(store)[unquote(key)] # Accesses caller's 'store' variable
end
end
```
### When NOT to Use
**Don't use this when:**
- You can pass the value as a macro argument instead
- The macro doesn't need to reference caller-scope variables
- You're creating new bindings (just let hygiene work naturally)
**Over-application example:**
```elixir
# Using var! when the value could just be passed as an argument
defmacro double_it do
quote do
var!(x) * 2 # Assumes caller has 'x' — fragile!
end
end
# Caller must have 'x' defined
x = 5
double_it() # => 10
```
**Better alternative:**
```elixir
defmacro double_it(value) do
quote do
unquote(value) * 2
end
end
double_it(5) # => 10, explicit, no hidden dependencies
```
**Why:** Every `var!` creates an invisible contract between macro and caller. Prefer explicit arguments. Reserve `var!` for cases where the contract is part of the documented API (like ExUnit's test context).''',
5: '''
### When to Use
**Triggers:**
- Your macro receives user input that could be an alias, module attribute, or compile-time expression
- You need to make compile-time decisions based on what type of thing was passed
- Distinguishing between module names and runtime expressions in macro arguments
**Example — before:**
```elixir
# Trying to pattern-match on the raw AST — breaks with aliases
defmacro wrap_error(module) when is_atom(module) do
# Never matches! Aliases are {:__aliases__, _, [...]} in AST
quote do: %{__exception__: true, __struct__: unquote(module)}
end
```
**Example — after:**
```elixir
defmacro wrap_error(module_or_msg) do
case Macro.expand(module_or_msg, __CALLER__) do
atom when is_atom(atom) ->
quote do: unquote(atom).exception([])
_ ->
quote do: RuntimeError.exception(unquote(module_or_msg))
end
end
```
### When NOT to Use
**Don't use this when:**
- The macro argument is always a literal (string, number)
- You don't need compile-time branching on the argument's type
- The argument should remain unevaluated (lazy evaluation semantics)
**Over-application example:**
```elixir
# Expanding an argument that's meant to be a runtime expression
defmacro log(message) do
expanded = Macro.expand(message, __CALLER__)
quote do
Logger.info(unquote(expanded))
end
end
```
**Better alternative:**
```elixir
defmacro log(message) do
quote do
Logger.info(unquote(message)) # Let it evaluate at runtime
end
end
```
**Why:** `Macro.expand` resolves compile-time constructs (aliases, module attributes). Expanding function calls or complex expressions can produce confusing results. Only expand when you need to determine the *type* of the argument at compile time.''',
6: '''
### When to Use
**Triggers:**
- Your macro defines module-level constructs (functions, types, modules)
- The macro makes no sense inside a guard or pattern match
- Early failure with a clear message prevents confusing downstream errors
**Example — before:**
```elixir
# No context validation — weird error deep in expansion
defmacro defroute(path, handler) do
quote do
@routes [{unquote(path), unquote(handler)} | @routes]
end
end
# User accidentally writes:
def check(x) when defroute("/foo", Foo), do: x # Cryptic error
```
**Example — after:**
```elixir
defmacro defroute(path, handler) do
assert_no_match_or_guard_scope(__CALLER__.context, "defroute/2")
quote do
@routes [{unquote(path), unquote(handler)} | @routes]
end
end
# Now gives: "cannot invoke defroute/2 inside a guard"
```
### When NOT to Use
**Don't use this when:**
- Your macro IS designed to work in guards (use `defguard` or handle context)
- Your macro is designed to work in match context (like custom patterns)
- The macro is simple enough that misuse produces a clear error naturally
**Over-application example:**
```elixir
# Adding guard assertion to a macro that could legitimately work anywhere
defmacro debug(expr) do
assert_no_match_or_guard_scope(__CALLER__.context, "debug/1")
quote do
IO.inspect(unquote(expr), label: unquote(Macro.to_string(expr)))
end
end
```
**Better alternative:**
```elixir
# Let it work anywhere it naturally can
defmacro debug(expr) do
quote do
IO.inspect(unquote(expr), label: unquote(Macro.to_string(expr)))
end
end
```
**Why:** Only add context assertions when misuse would produce confusing errors. If your macro naturally fails with a clear error in the wrong context, the assertion adds noise without value.''',
7: '''
### When to Use
**Triggers:**
- You're building a type system or dispatch mechanism that operates on open-ended types
- Users need to add behavior for their own types without modifying your library
- You need dynamic dispatch based on the data type with compile-time consolidation
**Example — before:**
```elixir
# Manual dispatch via case — closed, must modify to extend
def serialize(data) do
case data do
%User{} -> serialize_user(data)
%Post{} -> serialize_post(data)
_ -> raise "don't know how to serialize"
end
end
```
**Example — after:**
```elixir
defprotocol Serializable do
@doc "Converts a data structure to a wire format"
def serialize(data)
end
defimpl Serializable, for: User do
def serialize(%User{name: name, email: email}), do: %{name: name, email: email}
end
# Anyone can add implementations for their own types
```
### When NOT to Use
**Don't use this when:**
- You have a closed set of types that won't be extended by users
- Simple pattern matching or behaviours solve the problem
- The dispatch doesn't depend on the first argument's type
**Over-application example:**
```elixir
# Protocol for internal types that will never be extended
defprotocol InternalFormat do
def format(thing)
end
defimpl InternalFormat, for: [Map, List, BitString] do
# Only ever these three types, controlled by us
end
```
**Better alternative:**
```elixir
# Simple function with pattern matching — less machinery
def format(%{} = map), do: # ...
def format(list) when is_list(list), do: # ...
def format(binary) when is_binary(binary), do: # ...
```
**Why:** Protocols add indirection and compilation complexity (consolidation). If the type set is closed and you control all implementations, pattern matching is simpler, faster, and easier to understand.''',
8: '''
### When to Use
**Triggers:**
- A protocol should handle ANY value rather than crashing on unknown types
- The generic behavior is genuinely useful (inspect, encode, display)
- You want a safe default that users can override for specific types
**Example — before:**
```elixir
defprotocol Displayable do
def display(term)
end
# Every new struct crashes until someone adds an implementation:
# ** (Protocol.UndefinedError) protocol Displayable not implemented for %MyStruct{}
```
**Example — after:**
```elixir
defprotocol Displayable do
@fallback_to_any true
def display(term)
end
defimpl Displayable, for: Any do
def display(term), do: inspect(term) # Reasonable fallback
end
```
### When NOT to Use
**Don't use this when:**
- The protocol operation doesn't make sense for arbitrary types
- Silently returning a default would hide bugs
- You want to force implementors to think about their implementation
**Over-application example:**
```elixir
defprotocol Saveable do
@fallback_to_any true
def save(data, repo)
end
defimpl Saveable, for: Any do
def save(_data, _repo), do: :ok # Silently does nothing!
end
# Dangerous: %UnknownStruct{} |> Saveable.save(repo) succeeds but saves nothing
```
**Better alternative:**
```elixir
defprotocol Saveable do
# No fallback — forces explicit implementation
def save(data, repo)
end
# Clear error on missing implementation:
# ** (Protocol.UndefinedError) protocol Saveable not implemented for %Foo{}
```
**Why:** Fallbacks that silently succeed are a bug factory. Use `@fallback_to_any` only when the default behavior is *genuinely useful* (like `Inspect`), not when "do nothing" masks errors.''',
9: '''
### When to Use
**Triggers:**
- Your module needs to inject behaviours, default function implementations, or compile hooks
- Users need a one-line "opt in" that sets up complex module infrastructure
- The setup requires `@behaviour`, `@before_compile`, `defoverridable`, or module attributes
**Example — before:**
```elixir
# User must remember all the boilerplate
defmodule MyWorker do
@behaviour GenServer
def child_spec(init_arg) do
%{id: __MODULE__, start: {__MODULE__, :start_link, [init_arg]}}
end
def init(state), do: {:ok, state}
# ... more defaults ...
end
```
**Example — after:**
```elixir
defmodule MyWorker do
use GenServer
# All boilerplate injected, just implement what you need
def init(state), do: {:ok, state}
end
```
### When NOT to Use
**Don't use this when:**
- `import` or `alias` is all you need (no module attributes, no callbacks)
- The module doesn't need to inject code — just provides functions
- You're using `use` to inject large amounts of invisible code that surprises users
**Over-application example:**
```elixir
# use for something that should just be import
defmodule MyHelpers do
defmacro __using__(_opts) do
quote do
import MyHelpers # That's literally all it does
end
end
end
# User writes: use MyHelpers
# When they could just write: import MyHelpers
```
**Better alternative:**
```elixir
# Just tell users to import directly
defmodule MyHelpers do
def format_date(date), do: # ...
def format_money(amount), do: # ...
end
# In user's module:
import MyHelpers
```
**Why:** `use` implies "this module needs setup that goes beyond importing functions." If all you're doing is importing, `use` adds a layer of indirection that obscures what's happening. Reserve `use` for genuine module setup.''',
10: '''
### When to Use
**Triggers:**
- You have compile-time-known literal values that benefit from validation at compile time
- A domain has a specific syntax for literals (dates, regex, URIs, colors)
- You want zero runtime parsing cost for constant values
**Example — before:**
```elixir
# Runtime parsing — fails at runtime, no compile-time validation
def deadline do
Date.from_iso8601!("2024-13-45") # Explodes at runtime
end
```
**Example — after:**
```elixir
def deadline do
~D[2024-13-45] # Compile error! Invalid date caught immediately
end
```
### When NOT to Use
**Don't use this when:**
- Values come from runtime input (user data, config files, databases)
- The syntax doesn't provide meaningful compile-time validation
- A regular function or struct literal is equally clear
**Over-application example:**
```elixir
# Sigil for something with no compile-time validation benefit
defmacro sigil_u({:<<>>, _, [string]}, []) do
quote do: String.upcase(unquote(string))
end
name = ~u"hello" # Just uppercases a string... why not String.upcase("hello")?
```
**Better alternative:**
```elixir
name = String.upcase("hello")
```
**Why:** Sigils shine when they validate or transform at compile time in ways that prevent runtime errors. A sigil that just wraps a function call without validation adds syntax without value.''',
11: '''
### When to Use
**Triggers:**
- You want a zero-cost syntactic transformation (no runtime dispatch)
- The transformation is purely structural (rewriting argument positions)
- An operator or DSL benefits from left-to-right readability
**Example — before:**
```elixir
# Deeply nested function calls — read inside-out
String.trim(String.downcase(String.replace(input, "_", " ")))
```
**Example — after:**
```elixir
input
|> String.replace("_", " ")
|> String.downcase()
|> String.trim()
```
### When NOT to Use
**Don't use this when:**
- The pipe has only one step (just call the function directly)
- The piped value isn't the first argument (requires anonymous function wrappers)
- You're piping into a macro that needs special AST handling
**Over-application example:**
```elixir
# Single-step pipe — adds noise
user
|> Map.get(:name)
# Piping where the value isn't first argument
data
|> Jason.encode!()
|> send_to(socket) # Is this send_to(encoded, socket)? Unclear.
```
**Better alternative:**
```elixir
# Single step — just call it
name = Map.get(user, :name)
# When argument position is unclear, break the pipe
encoded = data |> build_map() |> Jason.encode!()
send_to(socket, encoded) # Clear which arg is which
```
**Why:** Pipes optimize for readability of *sequential transformations*. When the data doesn't flow naturally as the first argument, or there's only one step, the pipe adds syntactic overhead without improving clarity.''',
12: '''
### When to Use
**Triggers:**
- Your macro generates variable bindings in quoted code
- You need `n` variables for a generated function clause or pattern
- The macro expands in user code where variable names could clash
**Example — before:**
```elixir
# Hardcoded variable names — can clash with caller's variables
defmacro curry(fun, arity) do
args = Enum.map(1..arity, fn i -> Macro.var(:"arg\#{i}", __MODULE__) end)
quote do
fn unquote_splicing(args) -> unquote(fun).(unquote_splicing(args)) end
end
end
```
**Example — after:**
```elixir
defmacro curry(fun, arity) do
args = Macro.generate_unique_arguments(arity, __CALLER__.module)
quote do
fn unquote_splicing(args) -> unquote(fun).(unquote_splicing(args)) end
end
end
```
### When NOT to Use
**Don't use this when:**
- You're using `bind_quoted` (it handles hygiene for you)
- The variable is accessed via `var!` (intentionally unhygienic)
- You only need one variable (a simple `quote do: var = ... end` is hygienic by default)
**Over-application example:**
```elixir
# Using generate_unique_arguments for a single binding
defmacro time_it(expr) do
[start] = Macro.generate_unique_arguments(1, __CALLER__.module)
quote do
unquote(start) = System.monotonic_time()
result = unquote(expr)
IO.puts("Took \#{System.monotonic_time() - unquote(start)}")
result
end
end
```
**Better alternative:**
```elixir
# Regular quote hygiene handles single variables fine
defmacro time_it(expr) do
quote do
start = System.monotonic_time()
result = unquote(expr)
IO.puts("Took \#{System.monotonic_time() - start}")
result
end
end
```
**Why:** Variables created in `quote` are already hygienic by default — they can't clash with caller variables. `generate_unique_arguments` is needed when you're generating *multiple* variables dynamically (e.g., function parameters for a generated clause) where you need distinct names that also interoperate correctly.''',
}
for i in range(len(parts)):
if i in when_sections:
parts[i] = parts[i].rstrip() + "\n\n" + when_sections[i].strip()
output = separator.join(parts) + "\n"
with open("patterns/macros.md", "w") as f:
f.write(output)
print(f"Done! {len(output)} chars")
-425
View File
@@ -1,425 +0,0 @@
#!/usr/bin/env python3
"""Add When to Use / When NOT to Use sections to modules.md"""
with open("patterns/modules.md", "r") as f:
content = f.read()
separator = "\n\n---\n\n"
parts = content.split(separator)
print(f"Found {len(parts)} parts")
when_sections = {
1: '''
### When to Use
**Triggers:**
- Your module has grown beyond ~300 lines with distinct sub-responsibilities
- External code only needs the parent module but implementation is complex
- You find yourself prefixing private functions with a concept name (e.g., `scope_push`, `scope_pop`)
**Example — before:**
```elixir
# Everything crammed into one flat module
defmodule MyApp.Router do
# 800 lines mixing route compilation, scope tracking, and helper generation
def compile_route(...), do: # ...
def push_scope(...), do: # ...
def pop_scope(...), do: # ...
def generate_helper(...), do: # ...
end
```
**Example — after:**
```elixir
# Parent module is the public API
defmodule MyApp.Router do
# Public API delegates to focused submodules
def compile(routes), do: MyApp.Router.Compiler.compile(routes)
end
# Submodules handle implementation
defmodule MyApp.Router.Compiler do
@moduledoc false
# ...
end
defmodule MyApp.Router.Scope do
@moduledoc false
# ...
end
```
### When NOT to Use
**Don't use this when:**
- The module is small and cohesive (< 200 lines)
- Nesting would exceed 3 levels (`A.B.C.D` is usually too deep)
- The "submodule" has its own independent public API (make it a sibling instead)
**Over-application example:**
```elixir
# Over-nesting a simple utility
defmodule MyApp.Utils.String.Formatting.Case do
def upcase(s), do: String.upcase(s)
end
```
**Better alternative:**
```elixir
defmodule MyApp.StringUtils do
def upcase(s), do: String.upcase(s)
end
```
**Why:** Nesting should reflect genuine conceptual hierarchy. If you're creating submodules for 2-3 functions that don't have independent complexity, you're adding navigational overhead without architectural benefit.''',
2: '''
### When to Use
**Triggers:**
- You're writing a new module and need to decide function ordering
- A module has grown organically and functions are scattered randomly
- You're reviewing code and finding it hard to locate the public API
**Example — before:**
```elixir
defmodule UserService do
defp hash_password(pw), do: # ...
def create(attrs) do
# uses hash_password
end
def start_link(opts), do: GenServer.start_link(__MODULE__, opts)
defp validate(attrs), do: # ...
def get(id), do: # ...
end
```
**Example — after:**
```elixir
defmodule UserService do
# Lifecycle
def start_link(opts), do: GenServer.start_link(__MODULE__, opts)
# Public API
def create(attrs), do: # ...
def get(id), do: # ...
# Private helpers
defp validate(attrs), do: # ...
defp hash_password(pw), do: # ...
end
```
### When NOT to Use
**Don't use this when:**
- You have a tiny module (< 5 functions) where ordering doesn't matter much
- The module is a pure data module (just a struct + typespec)
- "Logical grouping" puts closely related public+private pairs together for readability
**Over-application example:**
```elixir
# Forcing start_link to the top in a module that isn't an OTP process
defmodule MyApp.Parser do
# This module has no lifecycle — don't force OTP ordering
def start_link(_), do: raise "not a process" # Just to match the pattern?
def parse(input), do: # ...
end
```
**Better alternative:**
```elixir
defmodule MyApp.Parser do
@moduledoc "Parses input format X into structs"
def parse(input), do: # ...
def parse!(input), do: # ...
defp tokenize(input), do: # ...
end
```
**Why:** The ordering convention exists to make OTP-aware modules predictable. For non-OTP modules, lead with the primary public function (the one callers reach for first) and let the rest follow logically.''',
3: '''
### When to Use
**Triggers:**
- A module exists purely for internal code organization
- Users of your library should never call this module directly
- The module is a helper that could change or disappear between versions
**Example — before:**
```elixir
defmodule MyApp.Repo.QueryBuilder do
@moduledoc """
Builds Ecto queries for the Repo module.
"""
# Now appears in docs, users try to call it directly
end
```
**Example — after:**
```elixir
defmodule MyApp.Repo.QueryBuilder do
@moduledoc false
# Hidden from docs, clearly internal
end
```
### When NOT to Use
**Don't use this when:**
- The module is part of your public API (even if rarely used)
- Users need to implement callbacks or extend the module
- The module defines a behaviour or protocol that others implement
**Over-application example:**
```elixir
# Hiding a module that users actually need
defmodule MyApp.Errors do
@moduledoc false # But users need to pattern-match on these!
defmodule NotFound do
defexception [:message]
end
end
```
**Better alternative:**
```elixir
defmodule MyApp.Errors do
@moduledoc "Error types raised by MyApp operations."
defmodule NotFound do
@moduledoc "Raised when a resource cannot be found."
defexception [:message]
end
end
```
**Why:** `@moduledoc false` means "this is not for you." If users catch your exceptions or match on your structs, they need documentation. Hide implementation details, not public contracts.''',
4: '''
### When to Use
**Triggers:**
- Your struct has fields that make no sense as `nil` (creating one without them is a bug)
- You're modeling a value object where all fields define its identity
- Incomplete structs would cause confusing runtime errors later
**Example — before:**
```elixir
defmodule Order do
defstruct [:id, :customer_id, :items, :total]
# Can create %Order{} with everything nil — meaningless
end
```
**Example — after:**
```elixir
defmodule Order do
@enforce_keys [:customer_id, :items, :total]
defstruct [:id | @enforce_keys]
# %Order{} without required fields → immediate compile/runtime error
end
```
### When NOT to Use
**Don't use this when:**
- The struct is built incrementally (e.g., a changeset or builder pattern)
- Most fields have sensible defaults
- The struct represents configuration where partial specs are valid
**Over-application example:**
```elixir
# Enforcing keys on a struct that's built in stages
defmodule FormState do
@enforce_keys [:step, :name, :email, :address, :payment]
defstruct @enforce_keys
# Can't create a partial form state for step 1!
end
```
**Better alternative:**
```elixir
defmodule FormState do
defstruct step: 1, name: nil, email: nil, address: nil, payment: nil
# Built incrementally as user progresses through steps
end
```
**Why:** `@enforce_keys` is for structs that represent *complete* values. If your struct represents an evolving state or has legitimate intermediate forms, enforcing all keys makes construction impossible at early stages.''',
5: '''
### When to Use
**Triggers:**
- Your `use` macro needs to give the caller access to specific functions
- You want to control exactly which functions enter the caller's namespace
- The imported functions are central to the DSL or workflow the module enables
**Example — before:**
```elixir
defmacro __using__(_opts) do
quote do
# Imports EVERYTHING from three modules — namespace soup
import MyApp.Router.Helpers
import MyApp.Router.Scoping
import MyApp.Router.Compilation
end
end
```
**Example — after:**
```elixir
defmacro __using__(_opts) do
quote do
import MyApp.Router, only: [get: 2, post: 2, resources: 2, scope: 2]
import MyApp.Conn, only: [assign: 3, put_status: 2]
end
end
```
### When NOT to Use
**Don't use this when:**
- The caller could just `import` what they need themselves
- You're importing utility functions that aren't part of your module's "DSL"
- The imports create naming conflicts with common functions
**Over-application example:**
```elixir
defmacro __using__(_opts) do
quote do
import MyApp.Utils # 50+ utility functions dumped into caller
import Enum # Why? Caller can do this themselves
import Map # Polluting namespace with standard lib
end
end
```
**Better alternative:**
```elixir
defmacro __using__(_opts) do
quote do
# Only import what THIS module's workflow requires
import MyApp.DSL, only: [field: 2, validate: 1]
end
end
```
**Why:** `use` should import the *minimum* needed for the module's intended workflow. If you're importing generic utilities, you're making decisions for the caller that they should make themselves.''',
6: '''
### When to Use
**Triggers:**
- Multiple modules from the same parent namespace are used together
- Full module paths are making code hard to read
- The aliased modules are used frequently (3+ times in the file)
**Example — before:**
```elixir
def process(input) do
Phoenix.Router.Route.new(input)
|> Phoenix.Router.Scope.apply_scope(Phoenix.Router.Scope.current())
|> Phoenix.Router.Helpers.generate()
end
```
**Example — after:**
```elixir
alias Phoenix.Router.{Route, Scope, Helpers}
def process(input) do
Route.new(input)
|> Scope.apply_scope(Scope.current())
|> Helpers.generate()
end
```
### When NOT to Use
**Don't use this when:**
- A module is referenced only once (inline the full path)
- The alias would be ambiguous (two `Route` modules from different namespaces)
- You're in a test file and the full path makes assertions clearer
**Over-application example:**
```elixir
# Aliasing a module used exactly once
alias MyApp.Workers.BatchProcessor
def run do
BatchProcessor.start() # Only reference — alias adds noise
end
```
**Better alternative:**
```elixir
def run do
MyApp.Workers.BatchProcessor.start() # One use — full path is fine
end
```
**Why:** Aliases trade verbosity for indirection. When a module appears once, the full path is documentation. When it appears many times, the alias is readability. Find the crossover point (typically 2-3 uses).''',
7: '''
### When to Use
**Triggers:**
- A struct field stores a boolean value
- The field answers a yes/no question about the struct
- You want the field's type to be self-evident without checking typespecs
**Example — before:**
```elixir
defstruct [:path, :trailing_slash, :verified]
# Is :trailing_slash the slash character? A boolean? The position?
```
**Example — after:**
```elixir
defstruct [:path, :trailing_slash?, :verified?]
# Immediately clear these are booleans
```
### When NOT to Use
**Don't use this when:**
- The field isn't a boolean (e.g., `:status` that can be `:active`/`:inactive`)
- You're working with external serialization that can't handle `?` in keys
- The field represents a count, enum, or value rather than a yes/no question
**Over-application example:**
```elixir
defstruct [:user?, :admin?, :count?]
# :user? — is this "is user present?" or "the user value"?
# :count? — definitely not a boolean
```
**Better alternative:**
```elixir
defstruct [:user, :admin?, :count]
# :user is the user struct, :admin? is a boolean, :count is an integer
```
**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.''',
}
for i in range(len(parts)):
if i in when_sections:
parts[i] = parts[i].rstrip() + "\n\n" + when_sections[i].strip()
output = separator.join(parts) + "\n"
with open("patterns/modules.md", "w") as f:
f.write(output)
print(f"Done! {len(output)} chars")
-432
View File
@@ -1,432 +0,0 @@
#!/usr/bin/env python3
"""Add When to Use / When NOT to Use sections to modules.md
Part 0 contains preamble + section 1. Parts 1-6 are sections 2-7."""
with open("patterns/modules.md", "r") as f:
content = f.read()
separator = "\n\n---\n\n"
parts = content.split(separator)
assert len(parts) == 7, f"Expected 7 parts, got {len(parts)}"
# Map: part index -> when section content
# Part 0 = preamble + section 1
# Part 1 = section 2
# ...
# Part 6 = section 7
when_sections = {
0: '''
### When to Use
**Triggers:**
- Your module has grown beyond ~300 lines with distinct sub-responsibilities
- External code only needs the parent module but implementation is complex
- You find yourself prefixing private functions with a concept name (e.g., `scope_push`, `scope_pop`)
**Example — before:**
```elixir
# Everything crammed into one flat module
defmodule MyApp.Router do
# 800 lines mixing route compilation, scope tracking, and helper generation
def compile_route(...), do: # ...
def push_scope(...), do: # ...
def pop_scope(...), do: # ...
def generate_helper(...), do: # ...
end
```
**Example — after:**
```elixir
# Parent module is the public API
defmodule MyApp.Router do
# Public API delegates to focused submodules
def compile(routes), do: MyApp.Router.Compiler.compile(routes)
end
# Submodules handle implementation
defmodule MyApp.Router.Compiler do
@moduledoc false
# ...
end
defmodule MyApp.Router.Scope do
@moduledoc false
# ...
end
```
### When NOT to Use
**Don't use this when:**
- The module is small and cohesive (< 200 lines)
- Nesting would exceed 3 levels (`A.B.C.D` is usually too deep)
- The "submodule" has its own independent public API (make it a sibling instead)
**Over-application example:**
```elixir
# Over-nesting a simple utility
defmodule MyApp.Utils.String.Formatting.Case do
def upcase(s), do: String.upcase(s)
end
```
**Better alternative:**
```elixir
defmodule MyApp.StringUtils do
def upcase(s), do: String.upcase(s)
end
```
**Why:** Nesting should reflect genuine conceptual hierarchy. If you're creating submodules for 2-3 functions that don't have independent complexity, you're adding navigational overhead without architectural benefit.''',
1: '''
### When to Use
**Triggers:**
- You're writing a new module and need to decide function ordering
- A module has grown organically and functions are scattered randomly
- You're reviewing code and finding it hard to locate the public API
**Example — before:**
```elixir
defmodule UserService do
defp hash_password(pw), do: # ...
def create(attrs) do
# uses hash_password
end
def start_link(opts), do: GenServer.start_link(__MODULE__, opts)
defp validate(attrs), do: # ...
def get(id), do: # ...
end
```
**Example — after:**
```elixir
defmodule UserService do
# Lifecycle
def start_link(opts), do: GenServer.start_link(__MODULE__, opts)
# Public API
def create(attrs), do: # ...
def get(id), do: # ...
# Private helpers
defp validate(attrs), do: # ...
defp hash_password(pw), do: # ...
end
```
### When NOT to Use
**Don't use this when:**
- You have a tiny module (< 5 functions) where ordering doesn't matter much
- The module is a pure data module (just a struct + typespec)
- "Logical grouping" puts closely related public+private pairs together for readability
**Over-application example:**
```elixir
# Forcing start_link to the top in a module that isn't an OTP process
defmodule MyApp.Parser do
# This module has no lifecycle — don't force OTP ordering
def start_link(_), do: raise "not a process" # Just to match the pattern?
def parse(input), do: # ...
end
```
**Better alternative:**
```elixir
defmodule MyApp.Parser do
@moduledoc "Parses input format X into structs"
def parse(input), do: # ...
def parse!(input), do: # ...
defp tokenize(input), do: # ...
end
```
**Why:** The ordering convention exists to make OTP-aware modules predictable. For non-OTP modules, lead with the primary public function (the one callers reach for first) and let the rest follow logically.''',
2: '''
### When to Use
**Triggers:**
- A module exists purely for internal code organization
- Users of your library should never call this module directly
- The module is a helper that could change or disappear between versions
**Example — before:**
```elixir
defmodule MyApp.Repo.QueryBuilder do
@moduledoc """
Builds Ecto queries for the Repo module.
"""
# Now appears in docs, users try to call it directly
end
```
**Example — after:**
```elixir
defmodule MyApp.Repo.QueryBuilder do
@moduledoc false
# Hidden from docs, clearly internal
end
```
### When NOT to Use
**Don't use this when:**
- The module is part of your public API (even if rarely used)
- Users need to implement callbacks or extend the module
- The module defines a behaviour or protocol that others implement
**Over-application example:**
```elixir
# Hiding a module that users actually need
defmodule MyApp.Errors do
@moduledoc false # But users need to pattern-match on these!
defmodule NotFound do
defexception [:message]
end
end
```
**Better alternative:**
```elixir
defmodule MyApp.Errors do
@moduledoc "Error types raised by MyApp operations."
defmodule NotFound do
@moduledoc "Raised when a resource cannot be found."
defexception [:message]
end
end
```
**Why:** `@moduledoc false` means "this is not for you." If users catch your exceptions or match on your structs, they need documentation. Hide implementation details, not public contracts.''',
3: '''
### When to Use
**Triggers:**
- Your struct has fields that make no sense as `nil` (creating one without them is a bug)
- You're modeling a value object where all fields define its identity
- Incomplete structs would cause confusing runtime errors later
**Example — before:**
```elixir
defmodule Order do
defstruct [:id, :customer_id, :items, :total]
# Can create %Order{} with everything nil — meaningless
end
```
**Example — after:**
```elixir
defmodule Order do
@enforce_keys [:customer_id, :items, :total]
defstruct [:id | @enforce_keys]
# %Order{} without required fields -> immediate compile/runtime error
end
```
### When NOT to Use
**Don't use this when:**
- The struct is built incrementally (e.g., a changeset or builder pattern)
- Most fields have sensible defaults
- The struct represents configuration where partial specs are valid
**Over-application example:**
```elixir
# Enforcing keys on a struct that's built in stages
defmodule FormState do
@enforce_keys [:step, :name, :email, :address, :payment]
defstruct @enforce_keys
# Can't create a partial form state for step 1!
end
```
**Better alternative:**
```elixir
defmodule FormState do
defstruct step: 1, name: nil, email: nil, address: nil, payment: nil
# Built incrementally as user progresses through steps
end
```
**Why:** `@enforce_keys` is for structs that represent *complete* values. If your struct represents an evolving state or has legitimate intermediate forms, enforcing all keys makes construction impossible at early stages.''',
4: '''
### When to Use
**Triggers:**
- Your `use` macro needs to give the caller access to specific functions
- You want to control exactly which functions enter the caller's namespace
- The imported functions are central to the DSL or workflow the module enables
**Example — before:**
```elixir
defmacro __using__(_opts) do
quote do
# Imports EVERYTHING from three modules — namespace soup
import MyApp.Router.Helpers
import MyApp.Router.Scoping
import MyApp.Router.Compilation
end
end
```
**Example — after:**
```elixir
defmacro __using__(_opts) do
quote do
import MyApp.Router, only: [get: 2, post: 2, resources: 2, scope: 2]
import MyApp.Conn, only: [assign: 3, put_status: 2]
end
end
```
### When NOT to Use
**Don't use this when:**
- The caller could just `import` what they need themselves
- You're importing utility functions that aren't part of your module's "DSL"
- The imports create naming conflicts with common functions
**Over-application example:**
```elixir
defmacro __using__(_opts) do
quote do
import MyApp.Utils # 50+ utility functions dumped into caller
import Enum # Why? Caller can do this themselves
import Map # Polluting namespace with standard lib
end
end
```
**Better alternative:**
```elixir
defmacro __using__(_opts) do
quote do
# Only import what THIS module's workflow requires
import MyApp.DSL, only: [field: 2, validate: 1]
end
end
```
**Why:** `use` should import the *minimum* needed for the module's intended workflow. If you're importing generic utilities, you're making decisions for the caller that they should make themselves.''',
5: '''
### When to Use
**Triggers:**
- Multiple modules from the same parent namespace are used together
- Full module paths are making code hard to read
- The aliased modules are used frequently (3+ times in the file)
**Example — before:**
```elixir
def process(input) do
Phoenix.Router.Route.new(input)
|> Phoenix.Router.Scope.apply_scope(Phoenix.Router.Scope.current())
|> Phoenix.Router.Helpers.generate()
end
```
**Example — after:**
```elixir
alias Phoenix.Router.{Route, Scope, Helpers}
def process(input) do
Route.new(input)
|> Scope.apply_scope(Scope.current())
|> Helpers.generate()
end
```
### When NOT to Use
**Don't use this when:**
- A module is referenced only once (inline the full path)
- The alias would be ambiguous (two `Route` modules from different namespaces)
- You're in a test file and the full path makes assertions clearer
**Over-application example:**
```elixir
# Aliasing a module used exactly once
alias MyApp.Workers.BatchProcessor
def run do
BatchProcessor.start() # Only reference — alias adds noise
end
```
**Better alternative:**
```elixir
def run do
MyApp.Workers.BatchProcessor.start() # One use — full path is fine
end
```
**Why:** Aliases trade verbosity for indirection. When a module appears once, the full path is documentation. When it appears many times, the alias is readability. Find the crossover point (typically 2-3 uses).''',
6: '''
### When to Use
**Triggers:**
- A struct field stores a boolean value
- The field answers a yes/no question about the struct
- You want the field's type to be self-evident without checking typespecs
**Example — before:**
```elixir
defstruct [:path, :trailing_slash, :verified]
# Is :trailing_slash the slash character? A boolean? The position?
```
**Example — after:**
```elixir
defstruct [:path, :trailing_slash?, :verified?]
# Immediately clear these are booleans
```
### When NOT to Use
**Don't use this when:**
- The field isn't a boolean (e.g., `:status` that can be `:active`/`:inactive`)
- You're working with external serialization that can't handle `?` in keys
- The field represents a count, enum, or value rather than a yes/no question
**Over-application example:**
```elixir
defstruct [:user?, :admin?, :count?]
# :user? — is this "is user present?" or "the user value"?
# :count? — definitely not a boolean
```
**Better alternative:**
```elixir
defstruct [:user, :admin?, :count]
# :user is the user struct, :admin? is a boolean, :count is an integer
```
**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.''',
}
for i in range(len(parts)):
if i in when_sections:
parts[i] = parts[i].rstrip() + "\n\n" + when_sections[i].strip()
output = separator.join(parts) + "\n"
with open("patterns/modules.md", "w") as f:
f.write(output)
print(f"Done! {len(output)} chars")