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:
+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).
|
||||
|
||||
Reference in New Issue
Block a user