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, plusto_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.
Exports
Section titled “Exports”module Iter
Section titled “module Iter”Iterable
Section titled “Iterable”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
reduce
Section titled “reduce”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
sort_with
Section titled “sort_with”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]
sort_by
Section titled “sort_by”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)
empty?
Section titled “empty?”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])
not_empty?
Section titled “not_empty?”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
partition
Section titled “partition”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])
group_by
Section titled “group_by”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]}
to_list
Section titled “to_list”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]
to_set
Section titled “to_set”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}
with_index
Section titled “with_index”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]
concat
Section titled “concat”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")]
take_while
Section titled “take_while”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]
filter
Section titled “filter”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]
drop_while
Section titled “drop_while”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]
flat_map
Section titled “flat_map”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])
chunks
Section titled “chunks”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]]
chunk_by
Section titled “chunk_by”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]
repeat
Section titled “repeat”fn repeat<'T>(x: 'T): Iterable<'T>
Infinite iterator yielding x forever.
Interactive Tests
assert repeat("ha") |> take(3) |> to_list() == ["ha", "ha", "ha"]
iterate
Section titled “iterate”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, socontinuehas no next state to fall forward to. UseIter.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])
to_map
Section titled “to_map”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}
to_string
Section titled “to_string”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"
flatten
Section titled “flatten”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]
reverse
Section titled “reverse”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