From 4852df51fc5169495ad8698c37f12e6f5b59489a Mon Sep 17 00:00:00 2001 From: Brummel Date: Thu, 7 May 2026 13:10:28 +0200 Subject: [PATCH] Iter 9c: docs (DESIGN.md + JOURNAL.md) DESIGN.md: - CLI block: add `ail run`. - Smoke-test list: add list_map.ail.json (the dogfood example). JOURNAL: append Iter 9 entry covering the dogfood result, two friction points surfaced (single-arg fn-type pretty-print parens; absence of a sequencing operator), the `ail run` CLI helper, and a ranked Iter 10 plan with three candidates (polymorphism, `;`, GC). Tentative pick is (2) `;` next, (1) polymorphism for Iter 11. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/DESIGN.md | 3 ++ docs/JOURNAL.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index dc0a628..b7f3506 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -226,6 +226,7 @@ ail render — JSON → pretty-print ail parse — pretty-print → JSON (for bootstrapping) ail emit-ir — writes .ll ail build — full pipeline → binary +ail run — build + execute (tempdir), passthrough exit code ``` ## Verification and correctness (across cycles) @@ -282,3 +283,5 @@ Pipeline regression smoke tests: - `examples/list.ail.json` → prints 42 (ADTs + match). - `examples/hof.ail.json` → prints 42 (first-class fn-refs, indirect call). - `examples/closure.ail.json` → prints 42 (lambda capturing a let-bound var). +- `examples/list_map.ail.json` → prints 2/4/6 (ADTs + closure + recursive + HOF + IO; the dogfood smoke test). diff --git a/docs/JOURNAL.md b/docs/JOURNAL.md index 871a738..1c2bfd0 100644 --- a/docs/JOURNAL.md +++ b/docs/JOURNAL.md @@ -657,3 +657,105 @@ Leaning toward (1) for the next iteration: it's the smaller bite *and* the bigger expressivity unlock. (2) becomes acute only when someone tries to run an unbounded loop, which the current examples don't. + +## 2026-05-07 — Iter 9 done: dogfood + `ail run` + +Course-corrected from the Iter-8 plan. Polymorphism is the bigger +expressivity unlock on paper, but I hadn't actually proved that the +language was sufficient for "small but real" programs without it. So +Iter 9 became a dogfood iteration: write a non-trivial program +that exercises everything Iter 1-8 shipped, and use `ail run` / +errors / type-checker output as the user would. If something broke, +fix it. If nothing broke, document the boundary moved. + +**`examples/list_map.ail.json`**: + +```jsonc +type IntList = Nil | Cons Int IntList + +map_int :: ((Int) -> Int, IntList) -> IntList +map_int(f, xs) = match xs { + Nil -> Nil + Cons(h, t) -> Cons(f(h), map_int(f, t)) +} + +print_list :: (IntList) -> Unit !IO +print_list(xs) = match xs { + Nil -> () + Cons(h, t) -> let _ = do io/print_int(h) in print_list(t) +} + +main = let xs = Cons 1 (Cons 2 (Cons 3 Nil)) in + print_list(map_int(\\x. x * 2, xs)) +``` + +**Result:** nothing broke. Output `2\\n4\\n6\\n`, exit 0. The full +pipeline (`ail run`) covers: ADTs with two ctors of different +arity; pattern matching with nested `Var` fields; recursion over +ADT; closures (with no captures here, so env is null but the +closure-pair plumbing still gets exercised); fn-typed parameters in +a top-level def; `do io/...` inside a match arm body, with `let _` +to sequence two effectful operations; effect propagation through +the call chain. This validates Iter 1-8 as a self-contained +foundation. + +**Friction surfaced:** writing the AST by hand is tedious — the +JSON for this 4-def module is 200+ lines. That's not surprising +(the format is for LLMs, not humans), but it suggests an Iter 10 +priority: a richer pretty-print form, or an `ail snippet` helper +for common boilerplate (`mk_list_int`, etc.). Not blocking; noted. + +**`ail run` (Iter 9b):** Builds into a tempdir + execs the binary, +exit code passthrough. Saves a `cd && ./bin` step in the dogfood +loop. Tiny addition — `Cmd::Build`'s body factored into a shared +`build_to` helper. + +**Architecture self-check:** + +- *Would I use this language now?* For self-contained Int-typed + programs over recursive ADTs: yes. The list_map example is what + I would have wanted to write since Iter 6 and bounced off + repeatedly. It now compiles and runs without me adapting the + source — the language is what its authors said it was, end to + end. +- *Did I think of everything?* Two cracks observed during the + dogfood: + - `(Int)` parens around single-param fn-types in pretty-print are + visual noise. Cosmetic, can wait. + - `let _ = do in ` is the only way to sequence + effects today. Working as intended given KISS, but a `;` + operator (sequencing) would be cheap polish. +- *Consistency:* DESIGN.md CLI block + smoke-test list updated. + Iter 8c invariant — "What is not (yet) supported" ≡ truth at end + of latest iteration — held; no new pending items. +- *KISS:* Iter 9 added 0 LOC of language semantics. All gain came + from validating the existing surface and a small CLI helper. + +**Tests:** 50 green (was 49). New e2e +`list_map_doubles_then_prints`. No test for `ail run` itself — +`build_and_run` already exercises the equivalent path. + +**Plan iteration 10:** + +The dogfood revealed two real-but-not-blocking pain points and one +big architectural gap. Candidates, ranked: + +1. **Polymorphic let-bindings with monomorphisation at codegen.** + Allows `let id = \\x. x in (id 1, id true)` and ultimately + `map :: (a -> b) -> List a -> List b`. The ground truth-ier + answer for the "would I use it for X?" question, but a + non-trivial pipeline change (typechecker→codegen needs to thread + instantiation info to the call site). + +2. **Sequencing operator `;` and richer effect ergonomics.** A + `Term::Seq { lhs, rhs }` (or compile sugar to `Let { name: "_", + value: lhs, body: rhs }`) plus a small pretty-print update. + Cheap, satisfying. + +3. **GC.** Heap reclamation for ADT boxes, lambda envs, closure + pairs. Real architecture step. Becomes acute the moment someone + writes a long-running loop; the current examples don't. + +Tentative pick: (2) for the next sprint as a satisfying small +polish, then (1) as Iter 11. (3) bides its time until a real +program needs it.