LEVIATHAN v962456e · 962456eee1

Library

The iterator protocol — IIterable and IIterator

Two interfaces that make any type usable in a for-in loop, and the rules for which loop path the compiler picks.

since 0.1.0-alpha.1linuxwindowswasm

Description

Two interfaces in the prelude let any type take part in for (T x in e):

interface IIterator<T> { bool hasNext(); T next(); }
interface IIterable<T> { IIterator<T> iterator(); }

An IIterable<T> produces an IIterator<T>, and the iterator yields the elements: hasNext() says whether another element is available, and next() returns it and moves on. To make your own type iterable, implement IIterable<T> on it, and implement IIterator<T> on a small cursor class that iterator() returns.

When the static type of e implements IIterable<T>, for (T x in e) body runs as

var it = e.iterator();
while (it.hasNext()) { T x = it.next(); body }

for (var x in e) takes the loop variable's type from the IIterable<T> it finds. break and continue behave as in the hand-written while; continue goes back to hasNext(). A type that is neither a built-in collection nor an IIterable<T> is a compile error that names the protocol.

A custom iterable used in for-in

class Countdown : IIterable<int> {
    int from;
    new Countdown(int n) { from = n; }
    IIterator<int> iterator() => CountdownIterator(from);
}
class CountdownIterator : IIterator<int> {
    int cur;
    new CountdownIterator(int n) { cur = n; }
    bool hasNext() => cur > 0;
    int next() {
        int v = cur;
        cur = cur - 1;
        return v;
    }
}
for (int n in Countdown(3)) { console.writeln(n); }
for (var n in Countdown(5)) {
    if (n == 4) { continue; }
    if (n == 2) { break; }
    console.writeln("n=${n}");
}
IIterator<int> it = Countdown(2).iterator();
while (it.hasNext()) { console.writeln(it.next()); }
3
2
1
n=5
n=3
2
1

Which path a loop takes

The compiler chooses the path for a for-in loop from the static type, never by testing at run time:

  1. A range written as a..b is a counted loop and creates no object.
  2. An Array, Map or Range value uses the built-in indexed fast path.
  3. Any other type uses the protocol described above.

The built-in collections never go through the protocol for a for loop; that is why looping over an array is fast. Array<T>, Map<K, V> and Range still implement IIterable, so an array or a range can be passed where an IIterable<T> is expected, and its iterator() yields the same sequence the loop would see (a Map yields Pair<K, V> entries in insertion order). A stream type such as InStream<T> is neither a built-in collection nor a range, so a loop over a stream always uses the protocol.

next() past the end is not specified by the protocol; callers are expected to test hasNext() first. The iterators of the standard library throw RuntimeException in that case. Standard iterators run over a snapshot of a pure value, so the collection can never change under them. An iterator over a mutable object that you write yourself must handle that case itself.

A string is not iterable. Loop over s.chars(), which returns an array and makes the choice between bytes and characters explicit.

Rules

  • for (T x in e) uses the protocol only when e is not an Array, Map or Range.
  • IIterable<T> has one method, iterator(); IIterator<T> has hasNext() and next().
  • hasNext() must be safe to call repeatedly without moving the iterator.
  • The standard iterators throw RuntimeException when next() is called past the end.
  • A string has no iterator; use chars().

Examples

Using iterators of the built-in collections

Array<int> xs = [1, 2];
IIterator<int> it = xs.iterator();
console.writeln(it.next());
console.writeln(it.next());
console.writeln(it.hasNext());
try {
    it.next();
} catch (RuntimeException e) {
    console.writeln("past the end");
}
void total(IIterable<int> src) {
    IIterator<int> i = src.iterator();
    int sum = 0;
    while (i.hasNext()) { sum = sum + i.next(); }
    console.writeln(sum);
}
total([4, 5]);
total(1..3);
1
2
false
past the end
9
6

Notes

Seq builds a lazy pipeline on top of this protocol; see the Seq entry.

See also

  • IIterable — A source of values that can be walked with for.
  • IIterator — The pull protocol for walking a sequence one value at a time.
  • Range — A run of consecutive integers written a..b, including both ends.
  • Array — An ordered sequence of values of one type, Array<T>, with value semantics.
  • Map — An associative collection from keys of type K to values of type V, with value semantics.
  • Seq — A lazy sequence: a pipeline of steps that does no work until its result is requested.