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:
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user