diff --git a/crates/ail/tests/e2e.rs b/crates/ail/tests/e2e.rs index 4b946d5..c6e9b5a 100644 --- a/crates/ail/tests/e2e.rs +++ b/crates/ail/tests/e2e.rs @@ -361,6 +361,20 @@ fn nested_ctor_pattern_first_two_sum() { assert_eq!(lines, vec!["30"]); } +/// Iter 15f: `std_pair` end-to-end. Polymorphic product type with +/// two type vars and a single constructor; six combinators including +/// the 3-type-var `map_first` / `map_second` reshapers. Property +/// protected: `Pair -> Pair` and `Pair -> Pair` +/// monomorphise correctly when each is exercised at distinct +/// `(a, b, c)` triples — the codegen must emit the right field types +/// for the substituted MkPair output. +#[test] +fn std_pair_demo() { + let stdout = build_and_run("std_pair_demo.ail.json"); + let lines: Vec<&str> = stdout.lines().collect(); + assert_eq!(lines, vec!["7", "9", "9", "7", "8", "18"]); +} + /// Iter 15d: `std_either` end-to-end. First stdlib ADT with two type /// parameters (`Either`); first combinator with three type vars /// (`either : (e -> c) -> (a -> c) -> Either -> c`). diff --git a/docs/JOURNAL.md b/docs/JOURNAL.md index b018315..be97e8c 100644 --- a/docs/JOURNAL.md +++ b/docs/JOURNAL.md @@ -2768,3 +2768,50 @@ trees) plug in at the same desugar layer. **Queue update.** 16a done. Remaining: 15f (`std_pair`, optional); 16b (local recursive `let`); 17a (per-fn arena); future "lit-in-Ctor" follow-up under 16c if and when needed. + +--- + +## Iter 15f — `std_pair`: 2-type-var product, no recursion + +**Goal.** Round out the small-ADT stdlib triad (Maybe, Either, Pair). +Pair is the canonical product with two type vars and a single +constructor — no recursion in the data def or any combinator. +Smallest dogfood for the parameterised-ADT path so far. + +**What shipped.** + +- `examples/std_pair.ailx` (~75 LOC, 5 combinators): + - `fst`, `snd` — projections. + - `swap : Pair -> Pair`. + - `map_first : (a -> c) -> Pair -> Pair`. + - `map_second : (b -> c) -> Pair -> Pair`. + Each combinator is a single-arm match on `MkPair` with no + fall-through, so the desugar pass added in 16a is a no-op + (the patterns are already flat). +- `examples/std_pair_demo.ailx` exercises every combinator. Output + (deterministic): 7, 9, 9, 7, 8, 18. +- `crates/ail/tests/e2e.rs::std_pair_demo` guards the path. + +**No new compiler bug surfaced.** Stdlib iter #4 in a row that +landed clean. The only fixable issue was a paren-balance typo in +the demo's `seq` chain, surfaced immediately by the parser's +"unexpected end of input, expected `)`" diagnostic. + +**Tests: 93/93** (e2e went from 33 to 34). + +**Cumulative state, post-15f.** + +- Stdlib: 4 modules (`std_maybe`, `std_list`, `std_either`, + `std_pair`); 24 combinators total. +- Type-system surface area exercised end-to-end: 1- and 2- and + 3-type-var data; 1- and 2- and 3-type-var fns; recursive ADTs; + cross-module imports of all of the above; flat *and* nested + patterns (16a); TCO via monomorphised `musttail`; Boehm GC. +- Pipeline layers: `load → desugar → check → codegen → clang`. + Each layer has a public, narrow contract; the desugar layer + is the natural home for further surface-level smoothing + without enlarging the core AST. + +**Queue update.** 15f done. Remaining: 16b (local recursive `let`), +16c (Lit-in-Ctor patterns), 17a (per-fn arena). None blocking +further stdlib growth; each is a quality-of-life improvement. diff --git a/examples/std_pair.ail.json b/examples/std_pair.ail.json new file mode 100644 index 0000000..3d9e2c9 --- /dev/null +++ b/examples/std_pair.ail.json @@ -0,0 +1 @@ +{"defs":[{"ctors":[{"fields":[{"k":"var","name":"a"},{"k":"var","name":"b"}],"name":"MkPair"}],"doc":"Polymorphic product: a single ctor MkPair holding one value of each parameter.","kind":"type","name":"Pair","vars":["a","b"]},{"body":{"arms":[{"body":{"name":"x","t":"var"},"pat":{"ctor":"MkPair","fields":[{"name":"x","p":"var"},{"p":"wild"}],"p":"ctor"}}],"scrutinee":{"name":"p","t":"var"},"t":"match"},"doc":"First projection. Returns the `a` payload.","kind":"fn","name":"fst","params":["p"],"type":{"body":{"effects":[],"k":"fn","params":[{"args":[{"k":"var","name":"a"},{"k":"var","name":"b"}],"k":"con","name":"Pair"}],"ret":{"k":"var","name":"a"}},"k":"forall","vars":["a","b"]}},{"body":{"arms":[{"body":{"name":"y","t":"var"},"pat":{"ctor":"MkPair","fields":[{"p":"wild"},{"name":"y","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"p","t":"var"},"t":"match"},"doc":"Second projection. Returns the `b` payload.","kind":"fn","name":"snd","params":["p"],"type":{"body":{"effects":[],"k":"fn","params":[{"args":[{"k":"var","name":"a"},{"k":"var","name":"b"}],"k":"con","name":"Pair"}],"ret":{"k":"var","name":"b"}},"k":"forall","vars":["a","b"]}},{"body":{"arms":[{"body":{"args":[{"name":"y","t":"var"},{"name":"x","t":"var"}],"ctor":"MkPair","t":"ctor","type":"Pair"},"pat":{"ctor":"MkPair","fields":[{"name":"x","p":"var"},{"name":"y","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"p","t":"var"},"t":"match"},"doc":"Exchange the components: Pair -> Pair.","kind":"fn","name":"swap","params":["p"],"type":{"body":{"effects":[],"k":"fn","params":[{"args":[{"k":"var","name":"a"},{"k":"var","name":"b"}],"k":"con","name":"Pair"}],"ret":{"args":[{"k":"var","name":"b"},{"k":"var","name":"a"}],"k":"con","name":"Pair"}},"k":"forall","vars":["a","b"]}},{"body":{"arms":[{"body":{"args":[{"args":[{"name":"x","t":"var"}],"fn":{"name":"f","t":"var"},"t":"app"},{"name":"y","t":"var"}],"ctor":"MkPair","t":"ctor","type":"Pair"},"pat":{"ctor":"MkPair","fields":[{"name":"x","p":"var"},{"name":"y","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"p","t":"var"},"t":"match"},"doc":"Apply f to the first component, leave the second intact.","kind":"fn","name":"map_first","params":["f","p"],"type":{"body":{"effects":[],"k":"fn","params":[{"effects":[],"k":"fn","params":[{"k":"var","name":"a"}],"ret":{"k":"var","name":"c"}},{"args":[{"k":"var","name":"a"},{"k":"var","name":"b"}],"k":"con","name":"Pair"}],"ret":{"args":[{"k":"var","name":"c"},{"k":"var","name":"b"}],"k":"con","name":"Pair"}},"k":"forall","vars":["a","b","c"]}},{"body":{"arms":[{"body":{"args":[{"name":"x","t":"var"},{"args":[{"name":"y","t":"var"}],"fn":{"name":"f","t":"var"},"t":"app"}],"ctor":"MkPair","t":"ctor","type":"Pair"},"pat":{"ctor":"MkPair","fields":[{"name":"x","p":"var"},{"name":"y","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"p","t":"var"},"t":"match"},"doc":"Apply f to the second component, leave the first intact.","kind":"fn","name":"map_second","params":["f","p"],"type":{"body":{"effects":[],"k":"fn","params":[{"effects":[],"k":"fn","params":[{"k":"var","name":"b"}],"ret":{"k":"var","name":"c"}},{"args":[{"k":"var","name":"a"},{"k":"var","name":"b"}],"k":"con","name":"Pair"}],"ret":{"args":[{"k":"var","name":"a"},{"k":"var","name":"c"}],"k":"con","name":"Pair"}},"k":"forall","vars":["a","b","c"]}}],"imports":[],"name":"std_pair","schema":"ailang/v0"} diff --git a/examples/std_pair.ailx b/examples/std_pair.ailx new file mode 100644 index 0000000..b57f0f4 --- /dev/null +++ b/examples/std_pair.ailx @@ -0,0 +1,77 @@ +; Iter 15f — fourth stdlib module: product types. +; Pair is the canonical 2-type-var, single-ctor product. No +; recursion in the data def or any combinator. Combinators here +; build on the new (Iter 16a) nested-Ctor-pattern support: `swap` +; flattens via `(pat-ctor MkPair x y)` and the eliminator `pair` +; matches the same shape directly without an outer var-binding. + +(module std_pair + + (data Pair (vars a b) + (doc "Polymorphic product: a single ctor MkPair holding one value of each parameter.") + (ctor MkPair a b)) + + (fn fst + (doc "First projection. Returns the `a` payload.") + (type + (forall (vars a b) + (fn-type + (params (con Pair a b)) + (ret a)))) + (params p) + (body + (match p + (case (pat-ctor MkPair x _) x)))) + + (fn snd + (doc "Second projection. Returns the `b` payload.") + (type + (forall (vars a b) + (fn-type + (params (con Pair a b)) + (ret b)))) + (params p) + (body + (match p + (case (pat-ctor MkPair _ y) y)))) + + (fn swap + (doc "Exchange the components: Pair -> Pair.") + (type + (forall (vars a b) + (fn-type + (params (con Pair a b)) + (ret (con Pair b a))))) + (params p) + (body + (match p + (case (pat-ctor MkPair x y) + (term-ctor Pair MkPair y x))))) + + (fn map_first + (doc "Apply f to the first component, leave the second intact.") + (type + (forall (vars a b c) + (fn-type + (params (fn-type (params a) (ret c)) + (con Pair a b)) + (ret (con Pair c b))))) + (params f p) + (body + (match p + (case (pat-ctor MkPair x y) + (term-ctor Pair MkPair (app f x) y))))) + + (fn map_second + (doc "Apply f to the second component, leave the first intact.") + (type + (forall (vars a b c) + (fn-type + (params (fn-type (params b) (ret c)) + (con Pair a b)) + (ret (con Pair a c))))) + (params f p) + (body + (match p + (case (pat-ctor MkPair x y) + (term-ctor Pair MkPair x (app f y))))))) diff --git a/examples/std_pair_demo.ail.json b/examples/std_pair_demo.ail.json new file mode 100644 index 0000000..1c09bb9 --- /dev/null +++ b/examples/std_pair_demo.ail.json @@ -0,0 +1 @@ +{"defs":[{"body":{"args":[{"name":"x","t":"var"},{"lit":{"kind":"int","value":1},"t":"lit"}],"fn":{"name":"+","t":"var"},"t":"app"},"doc":"Add 1. Used as the (a -> c) arg.","kind":"fn","name":"inc","params":["x"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"}],"ret":{"k":"con","name":"Int"}}},{"body":{"args":[{"name":"x","t":"var"},{"lit":{"kind":"int","value":2},"t":"lit"}],"fn":{"name":"*","t":"var"},"t":"app"},"doc":"Multiply by 2. Used as the (b -> c) arg.","kind":"fn","name":"double","params":["x"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"}],"ret":{"k":"con","name":"Int"}}},{"body":{"lhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":7},"t":"lit"},{"lit":{"kind":"int","value":9},"t":"lit"}],"ctor":"MkPair","t":"ctor","type":"std_pair.Pair"}],"fn":{"name":"std_pair.fst","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":7},"t":"lit"},{"lit":{"kind":"int","value":9},"t":"lit"}],"ctor":"MkPair","t":"ctor","type":"std_pair.Pair"}],"fn":{"name":"std_pair.snd","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":7},"t":"lit"},{"lit":{"kind":"int","value":9},"t":"lit"}],"ctor":"MkPair","t":"ctor","type":"std_pair.Pair"}],"fn":{"name":"std_pair.swap","t":"var"},"t":"app"}],"fn":{"name":"std_pair.fst","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":7},"t":"lit"},{"lit":{"kind":"int","value":9},"t":"lit"}],"ctor":"MkPair","t":"ctor","type":"std_pair.Pair"}],"fn":{"name":"std_pair.swap","t":"var"},"t":"app"}],"fn":{"name":"std_pair.snd","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[{"name":"inc","t":"var"},{"args":[{"lit":{"kind":"int","value":7},"t":"lit"},{"lit":{"kind":"int","value":9},"t":"lit"}],"ctor":"MkPair","t":"ctor","type":"std_pair.Pair"}],"fn":{"name":"std_pair.map_first","t":"var"},"t":"app"}],"fn":{"name":"std_pair.fst","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"args":[{"args":[{"args":[{"name":"double","t":"var"},{"args":[{"lit":{"kind":"int","value":7},"t":"lit"},{"lit":{"kind":"int","value":9},"t":"lit"}],"ctor":"MkPair","t":"ctor","type":"std_pair.Pair"}],"fn":{"name":"std_pair.map_second","t":"var"},"t":"app"}],"fn":{"name":"std_pair.snd","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"t":"seq"},"t":"seq"},"t":"seq"},"t":"seq"},"t":"seq"},"doc":"Drive every combinator. Expected (per line): 7, 9, 9, 7, 8, 18.","kind":"fn","name":"main","params":[],"type":{"effects":["IO"],"k":"fn","params":[],"ret":{"k":"con","name":"Unit"}}}],"imports":[{"module":"std_pair"}],"name":"std_pair_demo","schema":"ailang/v0"} diff --git a/examples/std_pair_demo.ailx b/examples/std_pair_demo.ailx new file mode 100644 index 0000000..1d63954 --- /dev/null +++ b/examples/std_pair_demo.ailx @@ -0,0 +1,32 @@ +; Iter 15f — fourth consumer demo. Drives every std_pair combinator +; once. fst/snd are stable at 7/9; swap reverses; map_first/map_second +; demonstrate the Pair -> Pair / Pair reshape with +; b/a held rigid. + +(module std_pair_demo + + (import std_pair) + + (fn inc + (doc "Add 1. Used as the (a -> c) arg.") + (type (fn-type (params (con Int)) (ret (con Int)))) + (params x) + (body (app + x 1))) + + (fn double + (doc "Multiply by 2. Used as the (b -> c) arg.") + (type (fn-type (params (con Int)) (ret (con Int)))) + (params x) + (body (app * x 2))) + + (fn main + (doc "Drive every combinator. Expected (per line): 7, 9, 9, 7, 8, 18.") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body + (seq (do io/print_int (app std_pair.fst (term-ctor std_pair.Pair MkPair 7 9))) + (seq (do io/print_int (app std_pair.snd (term-ctor std_pair.Pair MkPair 7 9))) + (seq (do io/print_int (app std_pair.fst (app std_pair.swap (term-ctor std_pair.Pair MkPair 7 9)))) + (seq (do io/print_int (app std_pair.snd (app std_pair.swap (term-ctor std_pair.Pair MkPair 7 9)))) + (seq (do io/print_int (app std_pair.fst (app std_pair.map_first inc (term-ctor std_pair.Pair MkPair 7 9)))) + (do io/print_int (app std_pair.snd (app std_pair.map_second double (term-ctor std_pair.Pair MkPair 7 9))))))))))))