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) <noreply@anthropic.com>
This commit is contained in:
@@ -226,6 +226,7 @@ ail render <module> — JSON → pretty-print
|
||||
ail parse <module.ail> — pretty-print → JSON (for bootstrapping)
|
||||
ail emit-ir <module> — writes .ll
|
||||
ail build <module> — full pipeline → binary
|
||||
ail run <module> — 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).
|
||||
|
||||
+102
@@ -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 <effect> in <body>` 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.
|
||||
|
||||
Reference in New Issue
Block a user