Skip to content

std/iter

Lazy iteration over any Iterable<'T>.

The lazy adapters (map, filter, take, flat_map, …) are Iter module functions — Iter.map(xs, f) — not protocol functions. Each returns Iterable<...> backed by a private wrapper, so chains stay lazy, consuming one element at a time through the whole pipeline without building intermediate lists. There are no eager, container-preserving adapter functions on the collections: to get a container back, append an explicit materialize step — Iter.map(xs, f) |> Iter.to_list(), Iter.filter(s, p) |> Iter.to_set().

The terminals are also Iter module functions — Iter.reduce(xs, f) — over any Iterable<'T>. The Iterable interface itself is just the { next, known_count } protocol. Terminals come in two kinds:

  • Streaming: reduce, each, find, any?, all?, empty? produce a scalar; no concrete collection is ever built.
  • Materializing: to_list/to_set/sort, plus to_map/to_string, consume the iterator and produce a concrete collection.

A collection’s own form of an op may diverge from the generic one (String.reverse → String rebuilds a String, not the generic List), so those collections keep a container-specific native function; the generic form (Iter.reverse → List) stays a module function here.

Infinite sequences: Iter.from(0) / Iter.iterate(seed, step) / Iter.repeat(x) / Iter.cycle(xs) produce iterators that don’t terminate on their own. Bound them with Iter.take(n) or Iter.take_while(pred) before a materializing terminal, or use short-circuiting terminals like Iter.find / Iter.any?.

If you want a List back from a List, the spelling is always the explicit Iter.map(xs, f) |> Iter.to_list() — the lazy adapter plus a materialize step. There is no eager List.map; the materialize step is what makes the result strict.

Iterables in Nomi are replayable: binding x = Iter.map(xs, f) and consuming x multiple times yields consistent results, because next returns a new iterator instead of mutating.

This module also hosts loop, the state-threading repetition primitive — it takes no Iter, but its callback speaks the same break/continue protocol as every other iteration callback here.

Import with import std/iter.

Modules

Interfaces

interface Iterable<'T> {
    fn next(collection: self): Maybe<('T, self)>
    fn known_count(collection: self): Maybe<Int>
}

Protocol for producing elements one at a time.

A type implementing Iterable<'T> provides a single function, next, that returns the next element paired with a new iterator value representing the remainder, or None when exhausted. Because the value typed self is returned fresh rather than mutated, Nomi iterators are replayable — consuming the same iterator binding twice yields the same sequence.

Built-in types like List, Map, String, and ranges over Discrete values (Int, Codepoint) already implement Iterable, so the operations below work directly on them. To implement it for a custom type:

pub struct Counter {
  n: Int
}

impl Iterable for Counter {
  fn next(c: Counter): Maybe<(Int, Counter)> {
    Some((c.n, Counter{n: c.n + 1}))
  }
}
fn loop(f: ('S) -> 'S): 'S

Loops indefinitely, invoking f each iteration. The callback either carries state (declare its first parameter with a default — that default is the initial state, e.g. |n = 0|) or is stateless (|| body). The callback’s return value becomes the next iteration’s state. The state type, the break value type, and the loop’s result type are all one type S: for a stated callback S comes from the default; for a stateless callback S is inferred from the break value.

Use break value to exit with value, or bare break to exit with Unit. Without an explicit break this loops forever.

Interactive Tests

assert loop(|n = 0|
  if n >= 3 {
    break n
  } else {
    n + 1
  }
) == 3
assert loop(|| { break 42 }) == 42
fn reduce<'T, 'U>(source: Iterable<'T>, f: ('U, 'T) -> 'U): 'U

Reduces a collection to a single value by folding.

The initial accumulator is provided by the lambda’s default parameter: If the lambda has no default (e.g., |a, b| ...), the first element of the collection is used as the initial accumulator and iteration starts at the second element. Errors if the collection is empty.

Supports break / break value / continue in the callback.

Interactive Tests

assert reduce([1, 2, 3], |acc = 0, x| acc + x) == 6
fn find<'T>(source: Iterable<'T>, f: ('T) -> Bool): Maybe<'T>

Returns the first element matching the predicate, or None.

Interactive Tests

assert find([1, 2, 3, 4], |x| x > 2) == Some(3)
assert find([1, 2], |x| x > 99) == None
fn sort_with<'T>(source: Iterable<'T>, compare: ('T, 'T) -> Ordering): List<'T>

Consumes the iterator and returns its elements as a List sorted by the comparator. A materializing terminal — like to_list, but ordered — so it works on any iterable (a lazy chain, a range, a List). The sort is stable: equal elements keep their input order.

This is the explicit-comparator sort primitive (no Comparable bound). When the element type is Comparable, prefer sort (natural order) or sort_by (key projection), which call this under the hood.

Interactive Tests

assert sort_with([3, 1, 2], Int.compare) == [1, 2, 3]
fn sort<'T>(source: Iterable<'T>, direction: Direction): List<'T> where 'T: Comparable

Consumes the iterator and returns its elements as a List in natural order, using the element type’s Comparable impl. Defaults to ascending; pass Descending to flip. A materializing terminal — works on any iterable (a lazy chain, a Range, a List). The sort is stable: equal elements keep their input order.

Interactive Tests

assert sort([3, 1, 2]) == [1, 2, 3]
assert sort([3, 1, 2], Direction.Descending) == [3, 2, 1]
fn sort_by<'T, 'K>(source: Iterable<'T>, direction: Direction, key: ('T) -> 'K): List<'T> where 'K: Comparable

Sorts by a Comparable key projected from each element, returning a List in key order. Defaults to ascending; pass Descending to flip. A materializing terminal; the sort is stable. The trailing key lambda may be passed with direction omitted (Iter.sort_by(xs, |x| x.k)); the default fills the skipped slot.

Interactive Tests

assert sort_by(["bb", "a", "ccc"], |s| String.length(s)) == ["a", "bb", "ccc"]
assert sort_by([1, 2, 3], Direction.Descending, |n| n) == [3, 2, 1]
fn each<'T>(source: Iterable<'T>, f: ('T) -> Unit): Unit

Calls f for each element. Returns Unit (discards the accumulator).

fn any?<'T>(source: Iterable<'T>, f: ('T) -> Bool): Bool

Returns true if any element matches the predicate. Short-circuits on the first match.

Interactive Tests

assert any?([1, 2, 3], |x| x > 2)
refute any?([1, 2], |x| x > 99)
fn all?<'T>(source: Iterable<'T>, f: ('T) -> Bool): Bool

Returns true if all elements match the predicate. Vacuously true for an empty iterator. Short-circuits on the first miss.

Interactive Tests

assert all?([2, 4, 6], |x| x > 0)
refute all?([2, -4, 6], |x| x > 0)
fn empty?<'T>(source: Iterable<'T>): Bool

Returns true if the iterator is empty. Short-circuits on the first element.

Interactive Tests

assert empty?([])
refute empty?([1])
fn not_empty?<'T>(source: Iterable<'T>): Bool

Returns true if the iterator has at least one element. Short-circuits.

Interactive Tests

assert not_empty?([1])
refute not_empty?([])
fn first<'T>(source: Iterable<'T>): Maybe<'T>

Returns the first element, or None if the iterator is empty. Terminal; short-circuits after one step. (List spells this head, paired with tail — the cons-list idiom; first is the stream-vocabulary name that every iterator shares.)

Interactive Tests

assert first([1, 2, 3]) == Some(1)
assert first([]) == None
fn last<'T>(source: Iterable<'T>): Maybe<'T>

Returns the last element, or None if the iterator is empty. Terminal; consumes the whole iterator (does not terminate on an infinite source).

Interactive Tests

assert last([1, 2, 3]) == Some(3)
assert last([]) == None
fn at<'T>(source: Iterable<'T>, index: Int): Maybe<'T>

Returns the element at the given zero-based index, or None if the index is out of range (or negative). Walks the iterator, so it’s O(index).

Interactive Tests

assert at([10, 20, 30], 1) == Some(20)
assert at([10, 20, 30], 9) == None
fn partition<'T>(source: Iterable<'T>, f: ('T) -> Bool): (List<'T>, List<'T>)

Splits the elements into (matches, rest) by a predicate, preserving the original order within each. Materializing.

Interactive Tests

assert partition([1, 2, 3, 4], |x| x > 2) == ([3, 4], [1, 2])
fn group_by<'T, 'K>(source: Iterable<'T>, key_fn: ('T) -> 'K): Map<'K, List<'T>>

Groups elements by a key function. Each group preserves the original order of its members. Materializing.

Interactive Tests

assert group_by([1, 2, 3, 4], |x| x % 2) == {1 => [1, 3], 0 => [2, 4]}
fn to_list<'T>(source: Iterable<'T>): List<'T>

Consumes the iterator and materializes its elements into a List. A List argument is returned as-is (O(1) — lists are immutable); any other iterator is folded into a fresh List.

Interactive Tests

assert from(0) |> take(3) |> to_list() == [0, 1, 2]
fn to_set<'T>(source: Iterable<'T>): Set<'T>

Consumes the iterator and materializes its elements into a Set, discarding duplicates.

Interactive Tests

assert to_set([1, 2, 2, 3]) == #{1, 2, 3}
fn with_index<'T>(source: Iterable<'T>): Iterable<(Int, 'T)>

Lazy: pairs each element of source with a zero-based index.

Interactive Tests

assert ["a", "b", "c"] |> with_index() |> to_list() == [
  (0, "a"),
  (1, "b"),
  (2, "c"),
]
fn take<'T>(source: Iterable<'T>, n: Int): Iterable<'T>

Lazy: yields at most the first n elements of source. Useful for bounding an infinite iterator.

Interactive Tests

assert from(0) |> take(3) |> to_list() == [0, 1, 2]
fn cycle<'T>(source: Iterable<'T>): Iterable<'T>

Lazy: cycles through source endlessly. Empty source yields nothing.

Interactive Tests

assert [1, 2, 3] |> cycle() |> take(7) |> to_list() == [1, 2, 3, 1, 2, 3, 1]
fn concat<'T>(a: Iterable<'T>, b: Iterable<'T>): Iterable<'T>

Lazy: yields all elements of a, then all of b.

Interactive Tests

assert concat([1, 2], [3, 4]) |> to_list() == [1, 2, 3, 4]
fn zip<'T, 'U>(a: Iterable<'T>, b: Iterable<'U>): Iterable<('T, 'U)>

Lazy: pairs elements from two iterators. Stops when either exhausts.

Interactive Tests

assert zip([1, 2, 3], ["a", "b"]) |> to_list() == [(1, "a"), (2, "b")]
fn take_while<'T>(source: Iterable<'T>, pred: ('T) -> Bool): Iterable<'T>

Lazy: yields elements of source while pred returns true. In the callback, value is the keep/stop decision: break True takes the current element then stops, break False/bare break stop without it, continue skips it.

Interactive Tests

assert from(1) |> take_while(|x| x < 4) |> to_list() == [1, 2, 3]
fn map<'T, 'U>(source: Iterable<'T>, f: ('T) -> 'U): Iterable<'U>

Lazy: applies f to each element of source.

Interactive Tests

assert [1, 2, 3] |> map(|x| x * 10) |> to_list() == [10, 20, 30]
fn filter<'T>(source: Iterable<'T>, pred: ('T) -> Bool): Iterable<'T>

Lazy: keeps only elements where pred returns true. In the callback, value is the keep/drop decision: break True keeps the current element then stops, break False drops it then stops, bare break stops without deciding, continue skips. Append |> Iter.to_list() for the strict, List-returning form.

Interactive Tests

assert [1, 2, 3, 4] |> filter(|x| x > 2) |> to_list() == [3, 4]
fn drop<'T>(source: Iterable<'T>, n: Int): Iterable<'T>

Lazy: skips the first n elements of source.

Interactive Tests

assert [1, 2, 3, 4] |> drop(2) |> to_list() == [3, 4]
fn drop_while<'T>(source: Iterable<'T>, pred: ('T) -> Bool): Iterable<'T>

Lazy: drops the leading run of elements where pred is true.

Interactive Tests

assert [1, 2, 3, 1] |> drop_while(|x| x < 3) |> to_list() == [3, 1]
fn flat_map<'T, 'U>(source: Iterable<'T>, f: ('T) -> Iterable<'U>): Iterable<'U>

Lazy: for each element of source, apply f and yield all its elements.

Interactive Tests

assert [1, 2, 3]
  |> flat_map(|x| [x, x * 10])
  |> to_list()
  |> List.equal?([1, 10, 2, 20, 3, 30])
fn chunks<'T>(source: Iterable<'T>, size: Int): Iterable<List<'T>>

Lazy: yields adjacent fixed-size chunks as Lists. The final chunk may be shorter. A non-positive size yields no chunks.

Interactive Tests

assert [1, 2, 3, 4, 5] |> chunks(2) |> to_list() == [[1, 2], [3, 4], [5]]
fn chunk_by<'T, 'K>(source: Iterable<'T>, key_fn: ('T) -> 'K): Iterable<List<'T>> where 'K: Equatable

Lazy: yields adjacent runs whose elements produce equal keys. Unlike group_by, this never reorders or merges non-adjacent elements.

Interactive Tests

assert [1, 1, 2, 2, 1] |> chunk_by(|x| x) |> to_list() == [[1, 1], [2, 2], [1]]
assert ["ant", "ape", "bee", "bat", "cat"]
  |> chunk_by(|s| String.contains?(s, "a"))
  |> to_list()
  |> List.equal?([["ant", "ape"], ["bee"], ["bat", "cat"]])
fn from(start: Int): Iterable<Int>

Infinite iterator yielding start, start+1, start+2, …

Interactive Tests

assert from(10) |> take(3) |> to_list() == [10, 11, 12]
fn repeat<'T>(x: 'T): Iterable<'T>

Infinite iterator yielding x forever.

Interactive Tests

assert repeat("ha") |> take(3) |> to_list() == ["ha", "ha", "ha"]
fn iterate<'T>(seed: 'T, step: ('T) -> 'T): Iterable<'T>

Infinite iterator: seed, step(seed), step(step(seed)), …

The step callback runs in iter-callback context:

  • Plain return value v: emit the current state, advance to v.
  • break v: emit v as the final element, then stop.
  • bare break: stop immediately without emitting.
  • continue: runtime error — iterate’s step is the state-advancement, so continue has no next state to fall forward to. Use Iter.iterate(...) |> Iter.filter(...) to skip states.

Interactive Tests

assert iterate(1, |x| x * 2) |> take(4) |> to_list() == [1, 2, 4, 8]
assert iterate(0, |n|
  if n > 5 {
    break n
  } else {
    n + 1
  }
)
  |> to_list()
  |> List.equal?([0, 1, 2, 3, 4, 5, 6])
fn to_map(source: Iterable<('K, 'V)>): Map<'K, 'V>

Consumes the iterator and materializes its (‘K, ‘V) pair elements into a Map. Runtime error if elements aren’t pairs.

Stays a Go extern: it keeps the pair-element-type constraint error at the builtin boundary, and avoids iter reaching for std/map.

Interactive Tests

assert to_map([("a", 1), ("b", 2)]) == {"a" => 1, "b" => 2}
fn to_string(source: Iterable<String>): String

Consumes the iterator and concatenates its string elements. Runtime error if elements aren’t strings.

Interactive Tests

assert to_string(["he", "llo"]) == "hello"
fn flatten<'U>(source: Iterable<Iterable<'U>>): List<'U>

Flattens an iterator of iterators into a single List, preserving order. Like to_map/to_string, its source has a specific element shape — the elements must themselves be iterable — so it’s a module function rather than a self-based Iter interface function.

Interactive Tests

assert flatten([[1, 2], [3], [4, 5]]) == [1, 2, 3, 4, 5]
fn reverse<'T>(source: Iterable<'T>): List<'T>

Returns the elements of source reversed, as a List. A materializing terminal (consumes the whole iterator). Generic over any iterator. A collection that wants its own container back keeps a container-specific native function (String.reverse → String), which is why this generic form is a module function rather than an Iter protocol function: the two return types would otherwise have to diverge, and String.reverse could not equal Iter.reverse.

Interactive Tests

assert reverse([1, 2, 3]) == [3, 2, 1]
fn count<'T>(source: Iterable<'T>): Int

Returns the number of elements in source, consuming it. The single generic count over any Iterable<'T>: it consults the known_count protocol function first, so it’s O(1) on any source that stores its size (List/Map/Set/bounded ranges whose element type can count steps); everything else (lazy pipelines, String) inherits the None default and folds in O(n). Because the fold runs to exhaustion, Iter.count does not terminate on an infinite source — bound it with Iter.take(n) first. For the sized-only, non-consuming answer (None when unknown or unbounded), use Iterable.known_count directly.

Interactive Tests

assert count([10, 20, 30]) == 3
assert from(0) |> take(5) |> count() == 5