01 · plural · spatial · getter

Iterators: lazy, pulled one value at a time

An iterator is the plural getter for values in space. It implements next(), which returns an iteration: an object with a value, a done flag, or both. The consumer pulls, and nothing is computed until it does.
What distinguishes an iterator from an array is that it is lazy. An iterator does not necessarily end.

An iteration at a time

[123]
Call next() to get an iteration.

Calling next() after the end keeps returning done: true. There is no hasNext(): the iterator only knows it is finished once it tries to produce a value.

range(), lazy and eager

lazy: returns an iterator
function range(start, stop, step) {
    return {next: function () {
        var iteration;
        if (start < stop) {
            iteration = {value: start};
            start += step;
        } else {
            iteration = {done: true};
        }
        return iteration;
    }};
}
eager: returns an array
function range(start, stop, step) {
    var result = [];
    while (start < stop) {
        result.push(start);
        start += step;
    }
    return result;
}

expect(range(0, 6, 2)).toEqual([0, 2, 4]);

The same pipeline, read two ways

This chain is eager or lazy depending only on whether range returns an array or an iterator. With arrays, map and filter each build another large array. With iterators, a single value percolates through: the reducer pulls from the filter, the filter from the map, the map from the range.

range(0, 1000, 1)
.map(function (n) {
    return n * 2;
})
.filter(function (n) {
    return n % 3 !== 0;
})
.reduce(function (a, b) {
    return a + b;
})
Interpret as
N =

Lazy: the reducer pulls one value at a time

range(0, N, 1)source
last value—
next() called0×
→←
map(n => n * 2)transform
last value—
next() called0×
→←
filter(n => n % 3 !== 0)transform
last value—
next() called0×
→←
reduce((a, b) => a + b)sink
accumulator—
next() called0×
Iterations filter returned to the reducer
Nothing pulled yet. Nothing has been computed either.
arrays allocated
0
values in memory
0
  • why it mattersLaziness saves time and space. A lazy pipeline never builds an array of any size, so the memory cost doesn't depend on how many values flow through it.
  • why it mattersAn iterator can be infinite. range(0, Infinity, 1) is fine as an iterator and impossible as an array, which is why eager mode refuses to run it.
  • why it mattersThe consumer sets the pace: values only move when someone calls next(). That pull is the spatial version of what pressure does for streams later on.