Lazy iterators.

Cure comprehensions and Std.List are eager: every step materialises a new cons list. Std.Iter is the lazy half of the collections story. An iterator is an inductive wrapper around a step function. Each step returns either Some(Yield(element, next)) or None(), so the element and continuation stay typed without falling back to Any.

The lazy idiom

The recommended way to spell a lazy pipeline is to mark the entry point with lazy/1 and let the rest of the chain stay in Std.Iter:

cure
use Std.Iter

# Squares of the first five even integers in [1, 10].
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  |> lazy
  |> filter(fn(x) -> x % 2 == 0)
  |> map(fn(x) -> x * x)
  |> take(5)
# => [4, 16, 36, 64, 100]

Nothing materialises until the terminal take/2 (which returns a plain list). For an open-ended chain, end with to_list/1 -- guard infinite producers with take/2 or take_while/2 first.

Examples

cure
use Std.Iter

# Sum of the integers in [1, 10] via the iterator API.
fold(range(1, 10), 0, fn(x) -> fn(acc) -> acc + x)
# => 55
cure
use Std.Iter

# First five Fibonacci numbers from an infinite producer.
let pair_step = fn(p) ->
  match p
    %[a, b] -> Some(Emit(a, %[b, a + b]))
unfold(%[0, 1], pair_step) |> take(5)
# => [0, 1, 1, 2, 3]

Types

  • type StepToken
  • type Iter = Iter
  • type IterStep = Yield
  • type UnfoldStep = Emit
  • type EachDone

Functions

  • # fn all(it: Iter(t), pred: Function(t, Bool)) -> Bool

    true when every element satisfies pred (vacuously true for the empty iterator). Stops at the first miss.

  • # fn any(it: Iter(t), pred: Function(t, Bool)) -> Bool

    true when at least one element satisfies pred. Stops at the first hit.

  • # fn concat(a: Iter(t), b: Iter(t)) -> Iter(t)

    Concatenate two iterators. Stepping b is deferred until a is exhausted.

  • # fn count(it: Iter(t), pred: Function(t, Bool)) -> Int

    Count elements satisfying pred. Tail-recursive.

  • # fn cycle(list: List(t)) -> Iter(t)

    Infinite iterator that walks list repeatedly. An empty input collapses to empty/0 so consumers terminate.

  • # fn drop(it: Iter(t), n: Int) -> Iter(t)

    Skip the first n elements. Negative or zero n is a no-op.

  • # fn drop_while(it: Iter(t), pred: Function(t, Bool)) -> Iter(t)

    Skip elements while pred holds, then yield the rest unchanged.

  • # fn each(it: Iter(t), f: Function(t, u)) -> EachDone

    Apply f to every element for its side effects. Returns Done once the iterator is exhausted.

  • # fn empty() -> Iter(t)

    An iterator that is immediately exhausted.

  • # fn filter(it: Iter(t), pred: Function(t, Bool)) -> Iter(t)

    Keep elements of it for which pred(elem) is true.

  • # fn find(it: Iter(t), pred: Function(t, Bool), default: t) -> t

    First element satisfying pred; returns default when nothing matches. Stops at the first hit.

  • # fn flat_map(it: Iter(t), f: Function(t, Iter(u))) -> Iter(u)

    Map f over it and flatten one level of nesting; f returns an iterator per element.

  • # fn fold(it: Iter(t), acc: u, f: Function(t, Function(u, u))) -> u

    Left fold an iterator. f is a curried function elem -> acc -> acc.

  • # fn from_list(list: List(t)) -> Iter(t)

    An iterator over a list (walks left to right).

  • # fn intersperse(it: Iter(t), sep: t) -> Iter(t)

    Insert sep between consecutive elements of it. Empty and single-element iterators are passed through unchanged.

  • # fn iterate(x0: t, f: Function(t, t)) -> Iter(t)

    Infinite iterator: x0, f(x0), f(f(x0)), .... Always pair with a slicer such as take/2 or take_while/2 before forcing.

  • # fn lazy(list: List(t)) -> Iter(t)

    Bridge for the lazy-pipeline idiom: xs |> lazy |> map(...) |> .... Identical to from_list/1; the alternate name documents intent at the call site.

  • # fn map(it: Iter(t), f: Function(t, u)) -> Iter(u)

    Map f over every element of it, on demand.

  • # fn range(lo: Int, hi: Int) -> Iter(Int)

    A bounded integer range iterator (inclusive).

  • # fn repeat(x: t) -> Iter(t)

    Infinite iterator that yields x forever.

  • # fn take(it: Iter(t), n: Int) -> List(t)

    Take at most n values from the iterator as a plain list. Safe to call on infinite iterators.

  • # fn take_while(it: Iter(t), pred: Function(t, Bool)) -> Iter(t)

    Lazy prefix: keep elements while pred holds, then terminate.

  • # fn to_list(it: Iter(t)) -> List(t)

    Materialise an iterator as a list. Non-terminating iterators never return; guard with take/2 when unsure.

  • # fn unfold(seed: s, f: Function(s, Option(UnfoldStep(t, s)))) -> Iter(t)

    Stream-style unfold. f(seed) returns either Some(Emit(value, next_seed)) to emit value and continue, or None() to terminate.

  • # fn zip_with(a: Iter(t), b: Iter(u), f: Function(t, Function(u, v))) -> Iter(v)

    Combine two iterators element-wise using a curried f. Stops at the shorter iterator. f is a -> b -> c.