Iter 15h — std_list extension: take, drop

Adds the two canonical prefix-slicing combinators to std_list.
First fns in std_list to combine `if` + Int arithmetic + recursive
ADT pattern in one body.

  take : forall a. (Int, List<a>) -> List<a>
       — first n elements (or all of xs if shorter than n).
  drop : forall a. (Int, List<a>) -> List<a>
       — list with first n elements removed (Nil if n >= length).

Both use `(if (<= n 0) ...)` as the base-case guard since
literal sub-patterns inside Ctor patterns aren't yet supported
(queued as 16c — would let take/drop collapse the if into the
match).

examples/std_list_more_demo: builds [1, 2, 3, 4, 5] once and
exercises take/drop at n=0, n=mid, n=overflow on each. Expected
output 0, 3, 5, 5, 3, 0 (one per line).

Tests: 95/95 (e2e went from 35 to 36). std_list grew from 10 to
12 combinators; total stdlib 5 modules / 29 combinators. No new
compiler bug surfaced.

Pre-existing std_list defs are byte-identical (verified via diff
on .ailx and round-trip on .ail.json), so the five downstream
importers (std_list_demo, std_list_stress, std_either_list,
std_either_list_demo, list_map_poly) need no regeneration.
This commit is contained in:
2026-05-07 20:05:45 +02:00
parent de674c4d0b
commit ee5807d73c
6 changed files with 156 additions and 2 deletions
+15
View File
@@ -417,6 +417,21 @@ fn std_either_list_demo() {
assert_eq!(lines, vec!["2", "3", "2", "3"]);
}
/// Iter 15h: `std_list.take` and `std_list.drop` end-to-end. Both are
/// index-driven recursive ADT-pattern fns — the first `std_list`
/// combinators that pair `if (<= n 0)` Int-arithmetic guards with a
/// recursive Cons/Nil match. Property protected: the `if` base-case
/// guard wraps a `match` whose recursive arm bodies use `(- n 1)` and
/// re-enter through `app` — a shape no other `std_list` fn has so
/// far. Six checks pin all three boundary regimes (n=0, 0<n<length,
/// n>length) on each combinator. Expected stdout: 0, 3, 5, 5, 3, 0.
#[test]
fn std_list_more_demo() {
let stdout = build_and_run("std_list_more_demo.ail.json");
let lines: Vec<&str> = stdout.lines().collect();
assert_eq!(lines, vec!["0", "3", "5", "5", "3", "0"]);
}
/// 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.
+74
View File
@@ -3042,3 +3042,77 @@ not change observable behaviour for any prior fixture.
**Queue update.** 15g-aux done. Unchanged: 16b (local recursive
let), 16c (Lit-in-Ctor patterns), 17a (per-fn arena, gated on
user discussion of memory management).
---
## Iter 15h — std_list extension: take, drop
**Goal.** Extend `std_list` with the two index-driven prefix
combinators `take` and `drop`. They are the first `std_list` fns to
combine `if` + Int arithmetic + recursive ADT pattern in a single
body — every previous `std_list` fn was either a fold/HOF
(fold_left, fold_right, length, map, filter, reverse) or a pure
match-on-Cons (head, tail, append, is_empty). 15h closes the
canonical-prefix-slicing gap and dogfoods the `if (<= n 0)` +
`(- n 1)` interaction inside a recursive Cons-pattern body.
**What shipped.**
- `examples/std_list.ailx` — `take` and `drop` appended after
`filter`. All ten pre-existing defs byte-identical (verified via
`cargo run -p ail -- check` on every downstream importer:
`std_list_demo`, `std_list_stress`, `std_either_list`,
`std_either_list_demo`, `list_map_poly`). Both new fns are
`forall (vars a). (Int, List<a>) -> List<a>`. `take` is
constructor-blocked recursive (`Cons h (take (n-1) t)`); `drop`
is direct recursive (the recursive call is the arm body, no
Cons wrap), so the `match` arm is in tail position relative to
the `if`'s `else` branch — but neither call site is marked
`tail-app` because the surrounding `if` is not the fn's
immediate body. Conservative; matches the rest of `std_list`'s
marking discipline.
- `examples/std_list_more_demo.ailx` — six prints exercising both
combinators at all three boundary regimes (n=0, 0<n<length,
n>length). Reuses `std_list_demo`'s `(const xs)` idiom for the
canonical `[1, 2, 3, 4, 5]`. Output: `0 / 3 / 5 / 5 / 3 / 0`.
- `crates/ail/tests/e2e.rs::std_list_more_demo` (e2e count
35 → 36).
**`(if (<= n 0) ...)` as the base-case guard.** Both fns gate the
recursion on the int counter at the top of the body, _outside_ the
match. The dual-arm `(case (pat-ctor Cons h t) ...)` then handles
only the n>0 path — so the n=0 branch never re-enters the match,
and the recursive call is reached only when the list is non-empty
and n is still positive. This is a deliberate workaround for the
absence of literal sub-patterns inside Ctor patterns: a
hypothetical `(case (pat-ctor Cons _ _) (case-when (== n 0) ...))`
or matching `n` against `0` directly is queued as 16c. With 16c
landed, both fns could collapse the `if` into the match.
**No new compiler bug surfaced.** The `if` + Int-arithmetic +
recursive ADT pattern shape is the first instance for `std_list`
in particular but not new for the compiler — `gc_stress` and
`std_list_stress` already exercised `(if (== n 0) ...)` with
`(- n 1)` recursion at the top level. `<=` on `Int` and the
forall-`a` instantiation through the `match` cleanly reused the
same monomorphisation paths. Round-trip `render | parse` stays
canonical for both new files.
**Tests: 95/95.**
- e2e: 36 (was 35, +1 for `std_list_more_demo`).
- All other crates unchanged.
**Cumulative state, post-15h.**
- Stdlib: 5 modules (`std_maybe`, `std_list`, `std_either`,
`std_pair`, `std_either_list`); **29** combinators total
(27 + 2). All four primary `std_list` operations — length, map,
filter, take/drop — are now present, plus the head/tail/append/
reverse/is_empty/fold_left/fold_right surface from 15b.
- Compiler bugs surfaced and fixed in dogfood since 14a: 5/5
(unchanged from 15g-aux).
**Queue update.** 15h done. Remaining: 16b (local recursive let),
16c (Lit-in-Ctor patterns — would simplify `take`/`drop`), 17a
(per-fn arena, gated on user discussion).
File diff suppressed because one or more lines are too long
+33 -1
View File
@@ -161,4 +161,36 @@
(case (pat-ctor Cons h t)
(if (app p h)
(term-ctor List Cons h (app filter p t))
(app filter p t)))))))
(app filter p t))))))
(fn take
(doc "First n elements of xs (or all of xs if it has fewer than n).")
(type
(forall (vars a)
(fn-type
(params (con Int) (con List a))
(ret (con List a)))))
(params n xs)
(body
(if (app <= n 0)
(term-ctor List Nil)
(match xs
(case (pat-ctor Nil) (term-ctor List Nil))
(case (pat-ctor Cons h t)
(term-ctor List Cons h (app take (app - n 1) t)))))))
(fn drop
(doc "All elements of xs after the first n (or Nil if xs has fewer than n elements).")
(type
(forall (vars a)
(fn-type
(params (con Int) (con List a))
(ret (con List a)))))
(params n xs)
(body
(if (app <= n 0)
xs
(match xs
(case (pat-ctor Nil) (term-ctor List Nil))
(case (pat-ctor Cons _ t)
(app drop (app - n 1) t)))))))
+1
View File
@@ -0,0 +1 @@
{"defs":[{"doc":"The canonical [1, 2, 3, 4, 5] used across all checks.","kind":"const","name":"xs","type":{"args":[{"k":"con","name":"Int"}],"k":"con","name":"std_list.List"},"value":{"args":[{"lit":{"kind":"int","value":1},"t":"lit"},{"args":[{"lit":{"kind":"int","value":2},"t":"lit"},{"args":[{"lit":{"kind":"int","value":3},"t":"lit"},{"args":[{"lit":{"kind":"int","value":4},"t":"lit"},{"args":[{"lit":{"kind":"int","value":5},"t":"lit"},{"args":[],"ctor":"Nil","t":"ctor","type":"std_list.List"}],"ctor":"Cons","t":"ctor","type":"std_list.List"}],"ctor":"Cons","t":"ctor","type":"std_list.List"}],"ctor":"Cons","t":"ctor","type":"std_list.List"}],"ctor":"Cons","t":"ctor","type":"std_list.List"}],"ctor":"Cons","t":"ctor","type":"std_list.List"}},{"body":{"lhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":0},"t":"lit"},{"name":"xs","t":"var"}],"fn":{"name":"std_list.take","t":"var"},"t":"app"}],"fn":{"name":"std_list.length","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":3},"t":"lit"},{"name":"xs","t":"var"}],"fn":{"name":"std_list.take","t":"var"},"t":"app"}],"fn":{"name":"std_list.length","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":100},"t":"lit"},{"name":"xs","t":"var"}],"fn":{"name":"std_list.take","t":"var"},"t":"app"}],"fn":{"name":"std_list.length","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":0},"t":"lit"},{"name":"xs","t":"var"}],"fn":{"name":"std_list.drop","t":"var"},"t":"app"}],"fn":{"name":"std_list.length","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":2},"t":"lit"},{"name":"xs","t":"var"}],"fn":{"name":"std_list.drop","t":"var"},"t":"app"}],"fn":{"name":"std_list.length","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":100},"t":"lit"},{"name":"xs","t":"var"}],"fn":{"name":"std_list.drop","t":"var"},"t":"app"}],"fn":{"name":"std_list.length","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"t":"seq"},"t":"seq"},"t":"seq"},"t":"seq"},"t":"seq"},"doc":"Expected output (one per line): 0, 3, 5, 5, 3, 0.","kind":"fn","name":"main","params":[],"type":{"effects":["IO"],"k":"fn","params":[],"ret":{"k":"con","name":"Unit"}}}],"imports":[{"module":"std_list"}],"name":"std_list_more_demo","schema":"ailang/v0"}
+32
View File
@@ -0,0 +1,32 @@
; Iter 15h — demo for the new std_list combinators `take` and `drop`.
; Uses a top-level `(const xs)` for the canonical [1, 2, 3, 4, 5] —
; pure ctor expression, so a const is fine (same idiom as
; std_list_demo). Six checks pin both n=0, 0<n<length, and n>length
; boundaries on each combinator.
(module std_list_more_demo
(import std_list)
(const xs
(doc "The canonical [1, 2, 3, 4, 5] used across all checks.")
(type (con std_list.List (con Int)))
(body
(term-ctor std_list.List Cons 1
(term-ctor std_list.List Cons 2
(term-ctor std_list.List Cons 3
(term-ctor std_list.List Cons 4
(term-ctor std_list.List Cons 5
(term-ctor std_list.List Nil))))))))
(fn main
(doc "Expected output (one per line): 0, 3, 5, 5, 3, 0.")
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(seq (do io/print_int (app std_list.length (app std_list.take 0 xs)))
(seq (do io/print_int (app std_list.length (app std_list.take 3 xs)))
(seq (do io/print_int (app std_list.length (app std_list.take 100 xs)))
(seq (do io/print_int (app std_list.length (app std_list.drop 0 xs)))
(seq (do io/print_int (app std_list.length (app std_list.drop 2 xs)))
(do io/print_int (app std_list.length (app std_list.drop 100 xs)))))))))))