Iter 11: deeper dogfood — insertion sort

examples/sort.ail.json: insertion sort over IntList. Defines `insert
:: Int -> IntList -> IntList` and `sort :: IntList -> IntList`
recursively, plus print_list using Iter 10's seq. Sorts an 11-element
input and prints `1 1 2 3 3 4 5 5 5 6 9` one-per-line.

Validates that the language handles deeper ADT recursion + branching
(if + <=) + ctor construction + IO sequencing without surprises.
Wrote, typechecked, ran first try.

The Iter 11 plan called for polymorphism, but on reflection the right
move was one more validation cycle before disturbing the pipeline.
Polymorphism is now queued explicitly as Iter 12 (12a typechecker
substitution, 12b codegen monomorphisation, 12c docs + generic
example).

Tests: 52 green (was 51). New e2e `insertion_sort_orders_list`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 13:17:07 +02:00
parent c75517ac79
commit 1d24907f84
4 changed files with 427 additions and 0 deletions
+15
View File
@@ -93,6 +93,21 @@ fn list_map_doubles_then_prints() {
assert_eq!(lines, vec!["2", "4", "6"]);
}
/// Iter 11 dogfood: insertion sort over an 11-element IntList.
/// Exercises `<=`, `if`, mutual-leaf recursion (`insert` and `sort`),
/// nested ctor construction, and the Iter 10 seq operator inside
/// print_list. Validates that the language handles deeper ADT
/// recursion + branching without surprises.
#[test]
fn insertion_sort_orders_list() {
let stdout = build_and_run("sort.ail.json");
let lines: Vec<&str> = stdout.lines().collect();
assert_eq!(
lines,
vec!["1", "1", "2", "3", "3", "4", "5", "5", "5", "6", "9"],
);
}
/// Guards `ail diff`: a modified body changes the hash of `sum`, while
/// `main` stays unchanged. Expects exit code 1, `changed` contains exactly
/// `sum`, `unchanged` contains `main`, `added`/`removed` empty.
+2
View File
@@ -288,3 +288,5 @@ Pipeline regression smoke tests:
- `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).
- `examples/sort.ail.json` → prints sorted [3,1,4,1,5,9,2,6,5,3,5]
one-per-line (insertion sort over an 11-element list).
+82
View File
@@ -810,3 +810,85 @@ isation at codegen time. Touches the typechecker→codegen pipeline.
Bigger commit than the recent stretch, will probably need to be
phased (typechecker substitution machinery, then codegen
specialisation, then docs).
## 2026-05-07 — Iter 11 done: deeper dogfood (insertion sort)
Pulled back from polymorphism for one more validation cycle before
the architectural step. Polymorphism is a substantial pipeline
change (typechecker substitution + codegen monomorphisation) and I
wanted one more "small but real" program to confirm the existing
foundation holds before disturbing it.
`examples/sort.ail.json` — insertion sort over `IntList`:
```jsonc
insert :: Int -> IntList -> IntList
insert(y, xs) = match xs {
Nil -> [y]
Cons(h, t) -> if y <= h then Cons(y, Cons(h, t))
else Cons(h, insert(y, t))
}
sort :: IntList -> IntList
sort(xs) = match xs {
Nil -> Nil
Cons(h, t) -> insert(h, sort(t))
}
print_list :: IntList -> Unit !IO // uses Iter 10 seq
main = print_list(sort([3,1,4,1,5,9,2,6,5,3,5]))
```
**Result:** typechecks first try, runs first try, prints
`1 1 2 3 3 4 5 5 5 6 9` (each on its own line). 11-element input,
correct sorted output. The combination of recursive ADT pattern
match + comparison ops + branching + leaf recursion + IO
sequencing all worked end to end without the language tripping me
up. Iter 10's seq made `print_list` notably cleaner than the
`let _ = ...` form would have been.
**Architecture self-check:**
- *Would I use this language now?* For "small but real"
monomorphic programs over Int, Bool, Unit, Str, and ADTs of
those: confidently yes. Insertion sort writes out as the
textbook recursion, no bookkeeping that the language couldn't
do for me.
- *Did I think of everything?* The remaining wall is still
polymorphism. Sort over `IntList` needs hand-monomorphisation;
a generic `sort :: (a -> a -> Bool) -> List a -> List a` is
what the language eventually wants. No new architectural cracks
surfaced from this dogfood.
- *Visualisation:* `ail describe sort.ail.json sort` reads the
way I'd expect a sort definition to read, with `IntList` types
inline and the recursive call rendered cleanly.
- *KISS:* Iter 11 added 0 LOC of language semantics and 1 e2e
test. The 250-line JSON for the example is verbose but
mechanical — no friction once you accept that the JSON is the
surface for LLM authors.
**Tests:** 52 green (was 51). New e2e
`insertion_sort_orders_list`. Pure addition; existing tests
untouched.
**Plan iteration 12:**
Now polymorphism. Two more dogfood programs would just keep
producing the "the language is fine for monomorphic programs"
result, which is already established. The real expressivity
unlock — and the answer to "would I use it for X?" for X that
actually needs generic data — is HM inference + let-generalisation
+ monomorphisation. Phased plan:
12a. Typechecker: introduce a `Subst` (type variable substitution)
and unification. Thread through `synth`. At `let`, generalise
syntactic values (lambdas) — no value-restriction subtlety
needed yet, the MVP has no mutable refs.
12b. Codegen: at each polymorphic call site, the typechecker
records the instantiation. Codegen walks the AST a second
time per (def, instantiation) pair and emits a specialised
version with the type variables substituted by concrete
types in fn signatures.
12c. Docs + a polymorphic `id` test + a generic `map :: (a -> b)
-> List a -> List b` rewrite of `list_map.ail.json`.
+328
View File
@@ -0,0 +1,328 @@
{
"schema": "ailang/v0",
"name": "sort",
"imports": [],
"defs": [
{
"kind": "type",
"name": "IntList",
"doc": "Singly-linked Int list, boxed.",
"ctors": [
{ "name": "Nil", "fields": [] },
{
"name": "Cons",
"fields": [
{ "k": "con", "name": "Int" },
{ "k": "con", "name": "IntList" }
]
}
]
},
{
"kind": "fn",
"name": "insert",
"type": {
"k": "fn",
"params": [
{ "k": "con", "name": "Int" },
{ "k": "con", "name": "IntList" }
],
"ret": { "k": "con", "name": "IntList" },
"effects": []
},
"params": ["y", "xs"],
"doc": "Insert y into a sorted list xs, preserving order.",
"body": {
"t": "match",
"scrutinee": { "t": "var", "name": "xs" },
"arms": [
{
"pat": { "p": "ctor", "ctor": "Nil", "fields": [] },
"body": {
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "var", "name": "y" },
{
"t": "ctor",
"type": "IntList",
"ctor": "Nil",
"args": []
}
]
}
},
{
"pat": {
"p": "ctor",
"ctor": "Cons",
"fields": [
{ "p": "var", "name": "h" },
{ "p": "var", "name": "t" }
]
},
"body": {
"t": "if",
"cond": {
"t": "app",
"fn": { "t": "var", "name": "<=" },
"args": [
{ "t": "var", "name": "y" },
{ "t": "var", "name": "h" }
]
},
"then": {
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "var", "name": "y" },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "var", "name": "h" },
{ "t": "var", "name": "t" }
]
}
]
},
"else": {
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "var", "name": "h" },
{
"t": "app",
"fn": { "t": "var", "name": "insert" },
"args": [
{ "t": "var", "name": "y" },
{ "t": "var", "name": "t" }
]
}
]
}
}
}
]
}
},
{
"kind": "fn",
"name": "sort",
"type": {
"k": "fn",
"params": [{ "k": "con", "name": "IntList" }],
"ret": { "k": "con", "name": "IntList" },
"effects": []
},
"params": ["xs"],
"doc": "Insertion sort: build the result by inserting each head into the sorted tail.",
"body": {
"t": "match",
"scrutinee": { "t": "var", "name": "xs" },
"arms": [
{
"pat": { "p": "ctor", "ctor": "Nil", "fields": [] },
"body": {
"t": "ctor",
"type": "IntList",
"ctor": "Nil",
"args": []
}
},
{
"pat": {
"p": "ctor",
"ctor": "Cons",
"fields": [
{ "p": "var", "name": "h" },
{ "p": "var", "name": "t" }
]
},
"body": {
"t": "app",
"fn": { "t": "var", "name": "insert" },
"args": [
{ "t": "var", "name": "h" },
{
"t": "app",
"fn": { "t": "var", "name": "sort" },
"args": [{ "t": "var", "name": "t" }]
}
]
}
}
]
}
},
{
"kind": "fn",
"name": "print_list",
"type": {
"k": "fn",
"params": [{ "k": "con", "name": "IntList" }],
"ret": { "k": "con", "name": "Unit" },
"effects": ["IO"]
},
"params": ["xs"],
"body": {
"t": "match",
"scrutinee": { "t": "var", "name": "xs" },
"arms": [
{
"pat": { "p": "ctor", "ctor": "Nil", "fields": [] },
"body": { "t": "lit", "lit": { "kind": "unit" } }
},
{
"pat": {
"p": "ctor",
"ctor": "Cons",
"fields": [
{ "p": "var", "name": "h" },
{ "p": "var", "name": "t" }
]
},
"body": {
"t": "seq",
"lhs": {
"t": "do",
"op": "io/print_int",
"args": [{ "t": "var", "name": "h" }]
},
"rhs": {
"t": "app",
"fn": { "t": "var", "name": "print_list" },
"args": [{ "t": "var", "name": "t" }]
}
}
}
]
}
},
{
"kind": "fn",
"name": "main",
"type": {
"k": "fn",
"params": [],
"ret": { "k": "con", "name": "Unit" },
"effects": ["IO"]
},
"params": [],
"doc": "Sort and print [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5].",
"body": {
"t": "let",
"name": "xs",
"value": {
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 3 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 1 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 4 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 1 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 5 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 9 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 2 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 6 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 5 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 3 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 5 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Nil",
"args": []
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
]
},
"body": {
"t": "app",
"fn": { "t": "var", "name": "print_list" },
"args": [
{
"t": "app",
"fn": { "t": "var", "name": "sort" },
"args": [{ "t": "var", "name": "xs" }]
}
]
}
}
}
]
}