CLAUDE/DESIGN/JOURNAL: design rationale ≠ implementation effort
User correction during the post-RC-overnight check-in: the 18a "Type::Fn metadata vs. new Type variant" call was justified across DESIGN.md, JOURNAL, and the commit message primarily by "avoids ~250 match-arm sites". That is an observation about the current state of the code, not a design rationale. CLAUDE.md gains two binding rules: - Design rationale ≠ implementation effort. Effort is at most a tiebreaker; a choice whose only stated reason is effort is suspect. The rule names the 18a misstep as the canonical anti-example so future sessions catch it earlier. - Direction freedom + bounce-back conditions inlined (was previously a cross-reference to a private auto-memory file outside the repo, which the user couldn't see). DESIGN.md Decision 10's Schema-additions block now leads with the substantive reasons for per-position metadata: semantic locality (modes belong to fn-parameter positions, not to types in general — Decision 1 line); compositional clarity (type identity vs. calling convention factor apart); future-proofing (per-position metadata generalises; Type-variant approach combinatoric blows up). The match-arm count remains parenthetical, explicitly named a tiebreaker. JOURNAL records the correction itself — the mistake stays as a data point because how design discipline corrupts is informative.
This commit is contained in:
+37
-4
@@ -907,12 +907,39 @@ Type::Fn {
|
||||
enum ParamMode { Implicit, Own, Borrow } // default: Implicit
|
||||
```
|
||||
|
||||
The substantive reasons for per-position metadata over a
|
||||
`Type::Borrow` / `Type::Own` variant approach:
|
||||
|
||||
- **Semantic locality.** Modes are properties of fn-signature
|
||||
parameter positions, not of types in general. `Int` does not
|
||||
have a mode; a fn-parameter slot does. Embedding modes in
|
||||
`Type` would let the schema express forms like
|
||||
`(con List (borrow Int))` — syntactically possible, semantically
|
||||
meaningless (you cannot separately own/borrow a list element
|
||||
from the list it lives in). Decision 1 is "schema = data,
|
||||
schema permits exactly what is meaningful"; per-position
|
||||
metadata is the option that holds that line.
|
||||
- **Compositional clarity.** A `Type` value's identity should
|
||||
depend only on the type. Two functions with the same param /
|
||||
ret types but different calling conventions share `Type::Fn.params`
|
||||
and differ only in `param_modes`. That is the right factoring:
|
||||
"what data does this carry" is one axis, "how is it transferred"
|
||||
is another. Mixing them under a single hierarchy conflates the
|
||||
two and makes both harder to reason about.
|
||||
- **Future-proof against more position metadata.** If later iters
|
||||
add other per-position properties (streaming receiver, captured-
|
||||
by-closure, lifetime witness), they generalise as additional
|
||||
metadata fields on `Type::Fn` — one consistent hierarchy. The
|
||||
variant approach would force every new dimension into its own
|
||||
`Type::*` variant (`Type::Streamed`, `Type::Captured`, ...) and
|
||||
combinatorics blow up: `Type::Borrow(Type::Streamed(T))` versus
|
||||
`Type::Streamed(Type::Borrow(T))` raise questions of canonical
|
||||
ordering that don't exist when modes live in a flat metadata
|
||||
vector.
|
||||
|
||||
`Implicit` is the legacy (pre-18a) state — semantically
|
||||
equivalent to `Own` but printed bare (`(con T)`, no wrapper).
|
||||
`Own` and `Borrow` are explicitly annotated. The implementation
|
||||
keeps `Type` itself unchanged, so unification, occurs, apply,
|
||||
and ~190 other `Type` match-arms in the typechecker need no new
|
||||
branch.
|
||||
`Own` and `Borrow` are explicitly annotated.
|
||||
|
||||
JSON canonical hash for every pre-18a fixture stays bit-
|
||||
identical: `param_modes` is skipped when every entry is
|
||||
@@ -924,6 +951,12 @@ throughout the 18-series; a later iter (deferred) makes the
|
||||
explicit annotation mandatory and rejects `Implicit` for boxed
|
||||
parameter types.
|
||||
|
||||
(An incidental observation, not a design reason: keeping `Type`
|
||||
itself unchanged also avoids touching ~250 sites across the
|
||||
typechecker / desugar / codegen that match on `Type` variants.
|
||||
This is a tiebreaker, not a rationale — the substantive reasons
|
||||
above are what justify the choice.)
|
||||
|
||||
**Iter 18c/18d — new `Term` variants.**
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user