Iter 15g — std_either_list: first 3-way cross-module stdlib fn
First stdlib fn that imports three other stdlib modules (std_list,
std_either, std_pair) and returns a compound polymorphic ADT tree
(Pair<List<e>, List<a>>). Stresses monomorphisation across nested
parameterised ADTs.
What shipped:
- examples/std_either_list.{ailx,ail.json}: 3 combinators —
- lefts : forall e a. (List<Either<e, a>>) -> List<e>
- rights : forall e a. (List<Either<e, a>>) -> List<a>
- partition_eithers : forall e a. (List<Either<e, a>>)
-> Pair<List<e>, List<a>>
lefts/rights use depth-2 nested Ctor patterns (Cons (Left l) t)
— exercises 16a's desugar pass at a depth not reached by any
prior fixture.
- examples/std_either_list_demo.{ailx,ail.json}: drives all three
combinators on a five-element List<Either<Int, Int>>; expected
output one per line: 2, 3, 2, 3.
- crates/ail/tests/e2e.rs::std_either_list_demo: e2e count 34 → 35.
- docs/JOURNAL.md: Iter 15g entry.
Compiler bug surfaced (queued as 15g-aux, not fixed):
unify_for_subst in ailang-codegen/src/lib.rs accepts $u wildcards
only on the arg side. Mixing inline (Either Left n) and (Either
Right n) in a list literal lands $u on the param side via the
outer List<a>'s binding and errors. Reduced repro: `length [Left
1, Right 10]`. Demo works around with monomorphic mkleft/mkright
helpers; workaround documented inline. Fix is a one-line symmetric
extension of the early-return.
Tests: 94/94. Stdlib: 5 modules, 27 combinators.
This commit is contained in:
@@ -395,6 +395,28 @@ fn std_either_demo() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 15g: `std_either_list` end-to-end. First stdlib fn set that
|
||||
/// imports three other stdlib modules (`std_list`, `std_either`,
|
||||
/// `std_pair`) and returns a compound polymorphic ADT tree
|
||||
/// (`partition_eithers : List<Either<e,a>> -> Pair<List<e>, List<a>>`).
|
||||
///
|
||||
/// Property protected: monomorphisation across `List × Either × Pair`
|
||||
/// in a single call chain — `std_either_list.partition_eithers` is
|
||||
/// instantiated at `(e=Int, a=Int)` and the result feeds
|
||||
/// `std_pair.fst` / `std_pair.snd` (each at `(a=List<Int>, b=List<Int>)`)
|
||||
/// into `std_list.length` (at `a=Int`). If cross-module ctor resolution
|
||||
/// or substitution-through-nested-Con regressed, one of the
|
||||
/// instantiations would either fail to emit or produce a wrong length.
|
||||
/// The four expected outputs (2, 3, 2, 3) also pin the order
|
||||
/// preservation of `lefts` and `rights` against the input
|
||||
/// `[Left 1, Right 10, Left 2, Right 20, Right 30]`.
|
||||
#[test]
|
||||
fn std_either_list_demo() {
|
||||
let stdout = build_and_run("std_either_list_demo.ail.json");
|
||||
let lines: Vec<&str> = stdout.lines().collect();
|
||||
assert_eq!(lines, vec!["2", "3", "2", "3"]);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
||||
+102
@@ -2889,3 +2889,105 @@ section. Spot-checked each — all match current implementation.
|
||||
17a (per-fn arena). 17a remains the explicit checkpoint before
|
||||
any broader memory-management work — that decision is gated on
|
||||
a joint conversation with the user.
|
||||
|
||||
---
|
||||
|
||||
## Iter 15g — std_either_list: first 3-way cross-module stdlib fn
|
||||
|
||||
**Goal.** Through 15f the stdlib had 4 modules but no fn imported
|
||||
more than one foreign module (e.g. `std_list.head` returning
|
||||
`std_maybe.Maybe<a>`). 15g introduces the first stdlib module whose
|
||||
every fn imports three others — `std_list`, `std_either`,
|
||||
`std_pair` — and returns a compound polymorphic ADT tree
|
||||
(`Pair<List<e>, List<a>>`). Designed to stress monomorphisation
|
||||
across `List × Either × Pair`, cross-module ctor resolution at
|
||||
qualified `term-ctor` sites, and the 16a desugar layer's nested-
|
||||
Ctor pattern handling at depth 2.
|
||||
|
||||
**What shipped.**
|
||||
|
||||
- `examples/std_either_list.ailx` — three combinators, all
|
||||
`forall (vars e a)`, all using qualified cross-module names at
|
||||
`con` and `term-ctor` sites:
|
||||
- `lefts : List<Either<e, a>> -> List<e>` — depth-2 nested-Ctor
|
||||
match per Cons arm (`Cons (Left l) t` / `Cons (Right _) t`)
|
||||
plus a wildcard tail to anchor the desugar's fall-through
|
||||
chain in `List<e>` rather than the synthetic `Unit` fallback.
|
||||
- `rights : List<Either<e, a>> -> List<a>` — symmetric to
|
||||
`lefts`.
|
||||
- `partition_eithers : List<Either<e, a>> -> Pair<List<e>,
|
||||
List<a>>` — `Nil → MkPair(Nil, Nil)`; `Cons h t →` let-bind
|
||||
`rest = partition_eithers t`, then a flat match on `h` to
|
||||
splice `l` / `r` onto `fst rest` / `snd rest`. Single pass.
|
||||
- `examples/std_either_list_demo.ailx` — first demo importing four
|
||||
stdlib modules. Drives all three combinators on the same five-
|
||||
element list `[Left 1, Right 10, Left 2, Right 20, Right 30]`;
|
||||
prints lengths via `std_list.length`. Output: `2 / 3 / 2 / 3`.
|
||||
- `crates/ail/tests/e2e.rs::std_either_list_demo` (e2e count
|
||||
34 → 35).
|
||||
|
||||
**16a desugar exercised.** Yes — `lefts` and `rights` use depth-2
|
||||
nested Ctor patterns (`(pat-ctor Cons (pat-ctor Left l) t)`). Each
|
||||
expands into a chain of single-level matches via the 16a pass; the
|
||||
explicit trailing wildcard arm prevents the synthetic `Unit`
|
||||
fallback (the desugar's documented "valid programs never reach it"
|
||||
terminator) from leaking into the function's return type. Without
|
||||
the wildcard the checker reports `expected std_list.List<e>, got
|
||||
Unit` because the chain's `else`-arm body is `Lit Unit` — confirms
|
||||
the design note in `desugar.rs::desugar_match` that exhaustiveness
|
||||
is the caller's responsibility, not the desugar's.
|
||||
|
||||
**New compiler bug surfaced.** Yes — and it is **not** in the new
|
||||
combinators themselves but in the existing monomorphiser, surfaced
|
||||
the moment one tries to construct an inline list literal mixing
|
||||
`(term-ctor std_either.Either Left n)` and `(term-ctor
|
||||
std_either.Either Right n)`. Reduced repro is just `(app
|
||||
std_list.length (term-ctor std_list.List Cons (Left 1) (term-ctor
|
||||
std_list.List Cons (Right 10) (term-ctor std_list.List Nil))))` —
|
||||
no `lefts` / `rights` / `partition_eithers` involved.
|
||||
|
||||
Cause: `synth_arg_type` produces `Either<Int, $u>` for `Left 1` and
|
||||
`Either<$u, Int>` for `Right 10`. The outer `Cons`'s parameter
|
||||
type `List<a>` unifies first against `List<Either<Int, $u>>`
|
||||
(binds `a = Either<Int, $u>`) and then against `List<Either<$u,
|
||||
Int>>` for the tail. `unify_for_subst` recurses into the prev
|
||||
binding and ends up unifying param `$u` (from `Either<Int, $u>`)
|
||||
against arg `Int` — the existing `if name.starts_with("$u") {
|
||||
return Ok(()); }` early-return only fires when `$u` is on the
|
||||
**arg** side. Param-side `$u` falls through to the catch-all error
|
||||
`cannot match param `$u` to arg `Int``.
|
||||
|
||||
**Workaround in the demo.** Two monomorphic helpers `mkleft : Int
|
||||
-> Either<Int, Int>` and `mkright : Int -> Either<Int, Int>` pin
|
||||
both type vars at the call site, so each list element arrives with
|
||||
fully concrete `Either<Int, Int>` and the synth-time `$u` never
|
||||
appears. Documented in the demo's header comment with a pointer
|
||||
back to this entry. The combinators themselves (`lefts`, `rights`,
|
||||
`partition_eithers`) are bug-free — the demo just couldn't build
|
||||
the input list inline without dodging the `$u`-on-param-side path.
|
||||
|
||||
**Bug fix sketch (queued, not landed).** A symmetric early-return
|
||||
in `unify_for_subst` for param-side `$u` would close this — the
|
||||
wildcard semantics ("don't care, defer") are direction-agnostic.
|
||||
Filed as candidate iter 15g-aux. Single-line patch + a unit test
|
||||
under `ailang-codegen/src/lib.rs`.
|
||||
|
||||
**Tests: 94/94.**
|
||||
|
||||
- e2e: 35 (was 34, +1 for `std_either_list_demo`).
|
||||
- All other crates unchanged.
|
||||
|
||||
**Cumulative state, post-15g.**
|
||||
|
||||
- Stdlib: 5 modules (`std_maybe`, `std_list`, `std_either`,
|
||||
`std_pair`, `std_either_list`); 27 combinators total (24 + 3).
|
||||
- Deepest cross-module composition reached so far: `List × Either
|
||||
× Pair` in a single fn (`partition_eithers`).
|
||||
- Compiler bugs surfaced and fixed in dogfood since 14a: still 4
|
||||
fixed; 1 new (the `$u`-on-param-side case above), worked around
|
||||
in the demo, queued as 15g-aux.
|
||||
|
||||
**Queue update.** 15g done. Remaining: 15g-aux (param-side `$u`
|
||||
acceptance — small, one-line in `unify_for_subst`); 16b (local
|
||||
recursive `let`); 16c (Lit-in-Ctor patterns); 17a (per-fn arena,
|
||||
gated on user discussion).
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"defs":[{"body":{"arms":[{"body":{"args":[{"name":"l","t":"var"},{"args":[{"name":"t","t":"var"}],"fn":{"name":"lefts","t":"var"},"t":"app"}],"ctor":"Cons","t":"ctor","type":"std_list.List"},"pat":{"ctor":"Cons","fields":[{"ctor":"Left","fields":[{"name":"l","p":"var"}],"p":"ctor"},{"name":"t","p":"var"}],"p":"ctor"}},{"body":{"args":[{"name":"t","t":"var"}],"fn":{"name":"lefts","t":"var"},"t":"app"},"pat":{"ctor":"Cons","fields":[{"ctor":"Right","fields":[{"p":"wild"}],"p":"ctor"},{"name":"t","p":"var"}],"p":"ctor"}},{"body":{"args":[],"ctor":"Nil","t":"ctor","type":"std_list.List"},"pat":{"p":"wild"}}],"scrutinee":{"name":"xs","t":"var"},"t":"match"},"doc":"Project the Left payloads of a list of Eithers into a List<e>. Order-preserving. Uses the 16a nested-Ctor pattern to inline the inner Either dispatch within each Cons arm; the trailing wildcard arm seals the desugar's fall-through chain into a List<e> rather than the synthetic Unit fallback.","kind":"fn","name":"lefts","params":["xs"],"type":{"body":{"effects":[],"k":"fn","params":[{"args":[{"args":[{"k":"var","name":"e"},{"k":"var","name":"a"}],"k":"con","name":"std_either.Either"}],"k":"con","name":"std_list.List"}],"ret":{"args":[{"k":"var","name":"e"}],"k":"con","name":"std_list.List"}},"k":"forall","vars":["e","a"]}},{"body":{"arms":[{"body":{"args":[{"name":"t","t":"var"}],"fn":{"name":"rights","t":"var"},"t":"app"},"pat":{"ctor":"Cons","fields":[{"ctor":"Left","fields":[{"p":"wild"}],"p":"ctor"},{"name":"t","p":"var"}],"p":"ctor"}},{"body":{"args":[{"name":"r","t":"var"},{"args":[{"name":"t","t":"var"}],"fn":{"name":"rights","t":"var"},"t":"app"}],"ctor":"Cons","t":"ctor","type":"std_list.List"},"pat":{"ctor":"Cons","fields":[{"ctor":"Right","fields":[{"name":"r","p":"var"}],"p":"ctor"},{"name":"t","p":"var"}],"p":"ctor"}},{"body":{"args":[],"ctor":"Nil","t":"ctor","type":"std_list.List"},"pat":{"p":"wild"}}],"scrutinee":{"name":"xs","t":"var"},"t":"match"},"doc":"Project the Right payloads of a list of Eithers into a List<a>. Symmetric to lefts; same nested-Ctor + wildcard-tail pattern shape.","kind":"fn","name":"rights","params":["xs"],"type":{"body":{"effects":[],"k":"fn","params":[{"args":[{"args":[{"k":"var","name":"e"},{"k":"var","name":"a"}],"k":"con","name":"std_either.Either"}],"k":"con","name":"std_list.List"}],"ret":{"args":[{"k":"var","name":"a"}],"k":"con","name":"std_list.List"}},"k":"forall","vars":["e","a"]}},{"body":{"arms":[{"body":{"args":[{"args":[],"ctor":"Nil","t":"ctor","type":"std_list.List"},{"args":[],"ctor":"Nil","t":"ctor","type":"std_list.List"}],"ctor":"MkPair","t":"ctor","type":"std_pair.Pair"},"pat":{"ctor":"Nil","fields":[],"p":"ctor"}},{"body":{"body":{"arms":[{"body":{"args":[{"args":[{"name":"l","t":"var"},{"args":[{"name":"rest","t":"var"}],"fn":{"name":"std_pair.fst","t":"var"},"t":"app"}],"ctor":"Cons","t":"ctor","type":"std_list.List"},{"args":[{"name":"rest","t":"var"}],"fn":{"name":"std_pair.snd","t":"var"},"t":"app"}],"ctor":"MkPair","t":"ctor","type":"std_pair.Pair"},"pat":{"ctor":"Left","fields":[{"name":"l","p":"var"}],"p":"ctor"}},{"body":{"args":[{"args":[{"name":"rest","t":"var"}],"fn":{"name":"std_pair.fst","t":"var"},"t":"app"},{"args":[{"name":"r","t":"var"},{"args":[{"name":"rest","t":"var"}],"fn":{"name":"std_pair.snd","t":"var"},"t":"app"}],"ctor":"Cons","t":"ctor","type":"std_list.List"}],"ctor":"MkPair","t":"ctor","type":"std_pair.Pair"},"pat":{"ctor":"Right","fields":[{"name":"r","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"h","t":"var"},"t":"match"},"name":"rest","t":"let","value":{"args":[{"name":"t","t":"var"}],"fn":{"name":"partition_eithers","t":"var"},"t":"app"}},"pat":{"ctor":"Cons","fields":[{"name":"h","p":"var"},{"name":"t","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"xs","t":"var"},"t":"match"},"doc":"Partition a list of Eithers into a Pair<List<e>, List<a>>: lefts on the left, rights on the right. Single pass via direct recursion; head dispatch is a flat match on the recursive result's projections.","kind":"fn","name":"partition_eithers","params":["xs"],"type":{"body":{"effects":[],"k":"fn","params":[{"args":[{"args":[{"k":"var","name":"e"},{"k":"var","name":"a"}],"k":"con","name":"std_either.Either"}],"k":"con","name":"std_list.List"}],"ret":{"args":[{"args":[{"k":"var","name":"e"}],"k":"con","name":"std_list.List"},{"args":[{"k":"var","name":"a"}],"k":"con","name":"std_list.List"}],"k":"con","name":"std_pair.Pair"}},"k":"forall","vars":["e","a"]}}],"imports":[{"module":"std_list"},{"module":"std_either"},{"module":"std_pair"}],"name":"std_either_list","schema":"ailang/v0"}
|
||||
@@ -0,0 +1,71 @@
|
||||
; Iter 15g — fifth stdlib module: first 3-way cross-module fn set.
|
||||
; Imports std_list, std_either, std_pair simultaneously. All three
|
||||
; combinators dispatch over List<Either<e, a>>; partition_eithers
|
||||
; additionally returns Pair<List<e>, List<a>>. Stresses
|
||||
; monomorphisation across List × Either × Pair, cross-module ctor
|
||||
; resolution at qualified term-ctor sites, and the 16a desugar
|
||||
; layer at depth-2 nested Ctor patterns (in `lefts` / `rights`).
|
||||
|
||||
(module std_either_list
|
||||
|
||||
(import std_list)
|
||||
(import std_either)
|
||||
(import std_pair)
|
||||
|
||||
(fn lefts
|
||||
(doc "Project the Left payloads of a list of Eithers into a List<e>. Order-preserving. Uses the 16a nested-Ctor pattern to inline the inner Either dispatch within each Cons arm; the trailing wildcard arm seals the desugar's fall-through chain into a List<e> rather than the synthetic Unit fallback.")
|
||||
(type
|
||||
(forall (vars e a)
|
||||
(fn-type
|
||||
(params (con std_list.List (con std_either.Either e a)))
|
||||
(ret (con std_list.List e)))))
|
||||
(params xs)
|
||||
(body
|
||||
(match xs
|
||||
(case (pat-ctor Cons (pat-ctor Left l) t)
|
||||
(term-ctor std_list.List Cons l (app lefts t)))
|
||||
(case (pat-ctor Cons (pat-ctor Right _) t)
|
||||
(app lefts t))
|
||||
(case _ (term-ctor std_list.List Nil)))))
|
||||
|
||||
(fn rights
|
||||
(doc "Project the Right payloads of a list of Eithers into a List<a>. Symmetric to lefts; same nested-Ctor + wildcard-tail pattern shape.")
|
||||
(type
|
||||
(forall (vars e a)
|
||||
(fn-type
|
||||
(params (con std_list.List (con std_either.Either e a)))
|
||||
(ret (con std_list.List a)))))
|
||||
(params xs)
|
||||
(body
|
||||
(match xs
|
||||
(case (pat-ctor Cons (pat-ctor Left _) t)
|
||||
(app rights t))
|
||||
(case (pat-ctor Cons (pat-ctor Right r) t)
|
||||
(term-ctor std_list.List Cons r (app rights t)))
|
||||
(case _ (term-ctor std_list.List Nil)))))
|
||||
|
||||
(fn partition_eithers
|
||||
(doc "Partition a list of Eithers into a Pair<List<e>, List<a>>: lefts on the left, rights on the right. Single pass via direct recursion; head dispatch is a flat match on the recursive result's projections.")
|
||||
(type
|
||||
(forall (vars e a)
|
||||
(fn-type
|
||||
(params (con std_list.List (con std_either.Either e a)))
|
||||
(ret (con std_pair.Pair (con std_list.List e) (con std_list.List a))))))
|
||||
(params xs)
|
||||
(body
|
||||
(match xs
|
||||
(case (pat-ctor Nil)
|
||||
(term-ctor std_pair.Pair MkPair
|
||||
(term-ctor std_list.List Nil)
|
||||
(term-ctor std_list.List Nil)))
|
||||
(case (pat-ctor Cons h t)
|
||||
(let rest (app partition_eithers t)
|
||||
(match h
|
||||
(case (pat-ctor Left l)
|
||||
(term-ctor std_pair.Pair MkPair
|
||||
(term-ctor std_list.List Cons l (app std_pair.fst rest))
|
||||
(app std_pair.snd rest)))
|
||||
(case (pat-ctor Right r)
|
||||
(term-ctor std_pair.Pair MkPair
|
||||
(app std_pair.fst rest)
|
||||
(term-ctor std_list.List Cons r (app std_pair.snd rest)))))))))))
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,46 @@
|
||||
; Iter 15g — fifth consumer demo. First demo to import four stdlib
|
||||
; modules (std_list, std_either, std_pair, std_either_list); first to
|
||||
; thread a List<Either<Int, Int>> through three combinators that
|
||||
; together exercise every monomorphisation site introduced by 15g.
|
||||
;
|
||||
; The list is constructed via the monomorphic helpers `mkleft` and
|
||||
; `mkright` rather than bare `(term-ctor std_either.Either Left ...)` /
|
||||
; `(term-ctor std_either.Either Right ...)`. This sidesteps a codegen
|
||||
; bug that 15g surfaced: when an inline list literal mixes
|
||||
; `Either Left n` (pins e, leaves a as `$u`) and `Either Right n` (pins
|
||||
; a, leaves e as `$u`), the synth-time unification of the outer
|
||||
; `List<a>`'s parameter against the `$u`-bearing element types fails
|
||||
; on the param side because `unify_for_subst` only accepts arg-side
|
||||
; `$u` wildcards. The helpers fully pin both type vars at the call
|
||||
; site, so each list element arrives with concrete `Either<Int, Int>`
|
||||
; and the bug is dodged. Logged as known-debt under 15g; the fix is
|
||||
; a one-line symmetric extension of the `$u` early-return.
|
||||
|
||||
(module std_either_list_demo
|
||||
|
||||
(import std_list)
|
||||
(import std_either)
|
||||
(import std_pair)
|
||||
(import std_either_list)
|
||||
|
||||
(fn mkleft
|
||||
(doc "Monomorphic Left-injector at Either<Int, Int>. Pins both type vars at the call site so the codegen synth doesn't leave a `$u` wildcard on the second var.")
|
||||
(type (fn-type (params (con Int)) (ret (con std_either.Either (con Int) (con Int)))))
|
||||
(params x)
|
||||
(body (term-ctor std_either.Either Left x)))
|
||||
|
||||
(fn mkright
|
||||
(doc "Monomorphic Right-injector at Either<Int, Int>. Symmetric to mkleft.")
|
||||
(type (fn-type (params (con Int)) (ret (con std_either.Either (con Int) (con Int)))))
|
||||
(params x)
|
||||
(body (term-ctor std_either.Either Right x)))
|
||||
|
||||
(fn main
|
||||
(doc "Drive lefts, rights, and partition_eithers on the same five-element List<Either<Int, Int>> (Left 1, Right 10, Left 2, Right 20, Right 30). Expected output (one per line): 2, 3, 2, 3.")
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body
|
||||
(seq (do io/print_int (app std_list.length (app std_either_list.lefts (term-ctor std_list.List Cons (app mkleft 1) (term-ctor std_list.List Cons (app mkright 10) (term-ctor std_list.List Cons (app mkleft 2) (term-ctor std_list.List Cons (app mkright 20) (term-ctor std_list.List Cons (app mkright 30) (term-ctor std_list.List Nil)))))))))
|
||||
(seq (do io/print_int (app std_list.length (app std_either_list.rights (term-ctor std_list.List Cons (app mkleft 1) (term-ctor std_list.List Cons (app mkright 10) (term-ctor std_list.List Cons (app mkleft 2) (term-ctor std_list.List Cons (app mkright 20) (term-ctor std_list.List Cons (app mkright 30) (term-ctor std_list.List Nil)))))))))
|
||||
(seq (do io/print_int (app std_list.length (app std_pair.fst (app std_either_list.partition_eithers (term-ctor std_list.List Cons (app mkleft 1) (term-ctor std_list.List Cons (app mkright 10) (term-ctor std_list.List Cons (app mkleft 2) (term-ctor std_list.List Cons (app mkright 20) (term-ctor std_list.List Cons (app mkright 30) (term-ctor std_list.List Nil))))))))))
|
||||
(do io/print_int (app std_list.length (app std_pair.snd (app std_either_list.partition_eithers (term-ctor std_list.List Cons (app mkleft 1) (term-ctor std_list.List Cons (app mkright 10) (term-ctor std_list.List Cons (app mkleft 2) (term-ctor std_list.List Cons (app mkright 20) (term-ctor std_list.List Cons (app mkright 30) (term-ctor std_list.List Nil))))))))))))))))
|
||||
Reference in New Issue
Block a user