7.3 Functional Iterators

Where using a stateful iterator is like reading from a stream — you just issue :get calls to read the successive elements — using a functional iterator is like walking down a list: there’s a :first operation which is like car, and a :rest operation which is like cdr. You have to use :rest explicitly at each step to get the next instance of the iterator, just as you would have to use cdr to get the next cons of the list. And as with lists, the functional iterators are persistent: calling :rest on an iterator instance doesn’t invalidate it; you can stash it away, so you can restart the iteration at that point anytime.

FSet’s functional iterators are not quite as efficient as the stateful ones, since each :rest operation has to cons a new instance, so they’re not used by default. But they have occasional uses; for instance, in parsing, when you want to be able to backtrack easily. Also, they’re thread-safe.

To obtain a functional iterator, call fun-iterator on the collection. Functional iterators are also implemented as closures, with these operations:

Specifically, on a set iterator or a non-pair bag iterator, :first returns two values. If the iterator is not exhausted (there are more elements remaining), it returns the next element and a true second value; if it is exhausted, it returns nil and false. On a map iterator, a pair bag iterator, or a binary relation iterator, it returns three values: if not exhausted, the domain element, the range element or multiplicity, and true; if exhausted, two nils and false.

So there are actually three ways to detect exhaustion: :more? or :empty?, which return booleans with opposite senses, or just call :first until the second or third value is false. Use whichever is most convenient.

FSet provides functional iterators on these classes: set, map, bag, seq, replay-set, replay-map, and 2-relation. For convenience, it also provides fun-iterator methods on these CL builtin types: list, vector, string, and sequence.