design: feature-acceptance criterion clause 3 + iteration-discipline roadmap milestone
Sharpen the feature-acceptance criterion (DESIGN.md, mirrored in the
brainstorm gate) with a third conjunctive-necessary clause: a feature
reintroduces no bug class the core constraint exists to eliminate.
Criterion 1 ("an LLM reaches for it") does not discriminate — an LLM
reaches for every construct native to its imperative training
distribution, so utility alone would launder the imperative paradigm
back in one construct at a time. Clause 3 is the discriminator; a
documentation note is not a reshape (the gate is whether the wrong
code fails to typecheck).
Record the decided iteration story as a P1 milestone: structural
recursion permitted (total-by-construction under Decision 10's
acyclic-ADT invariant) + named loop/recur as the only other
repetition + tail-app retired. Discharges fieldtest finding F1 without
introducing an imperative construct; closes the residual "forgot to
mark tail" trap completely; unifies with Decision 10 (acyclic values
AND acyclic implicit call graph). Supersedes the "while-loops legal"
scope bullet of the Stateful-islands milestone, which is amended for
internal consistency. The escaping-state path (ref/MutArray/Stateful)
is unaffected and stays in Stateful-islands.
This commit is contained in:
+75
-2
@@ -40,6 +40,76 @@ clean. Pick the next milestone from P2.)_
|
||||
|
||||
## P1 — Next
|
||||
|
||||
- [ ] **\[milestone\]** Iteration discipline — structural recursion +
|
||||
named `loop`/`recur`; recursion-by-call restricted; `tail-app`
|
||||
retired. The iteration story of the language, decided in principle
|
||||
2026-05-15. Two constructs survive, one is removed:
|
||||
- **Structural recursion stays — and becomes total-by-construction.**
|
||||
A recursive call is permitted iff it is structurally guarded:
|
||||
each recursive call is on a constructor-smaller component of an
|
||||
algebraic argument. Decision 10 already guarantees ADTs are
|
||||
acyclic, so a structurally-guarded recursion terminates *by the
|
||||
invariant the language already holds* — permitting it costs
|
||||
nothing on the provability pillar and it stays locally checkable.
|
||||
This is the most LLM-natural correct pattern (tree / AST / JSON /
|
||||
list `match`-and-recurse, saturated in every FP corpus) and must
|
||||
not be amputated.
|
||||
- **All other repetition must go through a named `(loop ...)` head +
|
||||
`recur`.** `recur` is the single explicit backward jump; it
|
||||
targets a lexically-enclosing named loop, never an implicit fn
|
||||
head (Clojure permits the implicit form — AILang does not, for
|
||||
the same no-outward-inference reason that motivated explicit
|
||||
`tail-app`). Accumulator loops, counted iteration, streaming,
|
||||
state machines — none are structurally decreasing, so all are
|
||||
forced into the explicit named-loop form.
|
||||
- **`tail-app` is retired.** `Term::App { tail: bool }` and the
|
||||
`musttail`-lowering surface go away: a call is either structural
|
||||
recursion (a plain call, checked total) or it is `loop`/`recur`
|
||||
(the only loop). No marked-tail-call concept survives.
|
||||
|
||||
**Motivation.** Discharges fieldtest finding F1 — the most
|
||||
consequential mut-local finding: mut-local's own motivation
|
||||
("removes friction from accumulators that today need tail-recursive
|
||||
helpers") was structurally unmet, because without a loop the only
|
||||
iteration mechanism remained the very tail-rec helper. `loop`/`recur`
|
||||
closes that honestly without introducing an imperative construct
|
||||
(it is pure: no mutable loop variable, no effect, composes into pure
|
||||
contexts freely). It also closes the residual "forgot to mark tail"
|
||||
trap *completely* — a recursive call that is not structurally
|
||||
guarded is a compile error that names itself, not a silent
|
||||
stack-overflow. And it unifies with Decision 10: acyclic values
|
||||
*and* an acyclic implicit call graph; the only cycles anywhere in a
|
||||
program are explicit, named, typed `loop` points. Both surviving
|
||||
constructs pass all three feature-acceptance clauses including the
|
||||
2026-05-15 clause 3 (structural recursion and named loop/recur are
|
||||
LLM-natural *and* their bug class is closed by construction; a bare
|
||||
`while` is exactly what clause 3 rejects).
|
||||
|
||||
**The open spec question (the real brainstorm work).** Not "whether"
|
||||
but the precise definition of "structurally smaller" / the
|
||||
guardedness check. Two hard points: (1) *mutual* structural
|
||||
recursion (needs a measure / size over the ADT, not just a
|
||||
one-argument syntactic decrease); (2) the accumulator-carrying
|
||||
structural walk — `foldl`-shaped, simultaneously structural *and*
|
||||
tail-shaped — the spec must decide whether it counts as structural
|
||||
recursion (a plain call) or must be expressed as `loop`/`recur`.
|
||||
This is the load-bearing spec decision; brainstorm + grounding-check
|
||||
own it.
|
||||
|
||||
**Relation to Stateful islands.** This entry owns the iteration
|
||||
story and *supersedes* the "while-loops legal" scope bullet of the
|
||||
Stateful-islands milestone below. The genuinely-escaping
|
||||
mutable-state path (`ref a` / `MutArray a` / `Stateful a b` /
|
||||
`pipe`) is unaffected and stays in Stateful-islands — that is the
|
||||
deliberately-fenced streaming escape hatch for *escaping* state,
|
||||
not the default iteration story, and F1 no longer motivates it.
|
||||
|
||||
- context: 2026-05-15 chat — design convergence from the
|
||||
mut-local fieldtest (F1) through the fenced-`while` /
|
||||
`!Mut`-visibility dead end to the structural-recursion +
|
||||
named-loop synthesis. Pending brainstorm produces
|
||||
`docs/specs/<date>-iteration-discipline.md`.
|
||||
|
||||
- [x] **\[milestone\]** Heap-`Str` ABI — runtime infrastructure for
|
||||
malloc-backed, refcounted `Str` values alongside the existing
|
||||
static `@.str_*` globals. Today the `Str` path is static-only
|
||||
@@ -319,8 +389,11 @@ clean. Pick the next milestone from P2.)_
|
||||
- `!Mut` effect, the first non-IO / non-Diverge effect; forces
|
||||
the effect-handler infrastructure forward.
|
||||
- `mut` block as the syntactic boundary in Form A — outside,
|
||||
AILang's pure self; inside, `var` / `assign` / mutable arrays /
|
||||
while-loops legal.
|
||||
AILang's pure self; inside, `var` / `assign` / mutable arrays
|
||||
legal. (Iteration is *not* part of this boundary: the "Iteration
|
||||
discipline" P1 milestone supersedes the earlier "while-loops
|
||||
legal" scope here — repetition is structural recursion or named
|
||||
`loop`/`recur`, never a `while` over mutable state.)
|
||||
- `var x = expr` + `assign x expr` AST nodes, legal only inside
|
||||
`mut`.
|
||||
- Mutable array primitive (`MutArray a`, O(1) index/update under
|
||||
|
||||
Reference in New Issue
Block a user