stateless-iterators

Overview

stateless-iterators is a library which provides Lua-like stateless iterators for Common Lisp. Stateless iterators work as follows:

  1. Suppose you have a function next which takes an argument state and returns two values: a result of the current iteration step and a new state. Should iteration stop next returns a symbol stateless-iterators:stop as the new state.
  2. Iterator is a pair of the previously described function and an initial state: (stateless-iterator:iterator #'next initial-state).
  3. A function stateless-iterators:iterate-for-effects takes an iterator and a function fn. It calls the iterating function next with the initial state of the iterator. If iteration does not stop, the function fn is called with the first value returned by next as its only argument. Values returned by fn are ignored.
  4. next is called with the second value previously returned by next and the process repeates again until the next state is a symbol stateless-iterators:stop.

Here is a simple iterator which infinitely counts from n by 1:

(defun count-from/next (state)
  (values state (1+ state)))

(defun count-from (n)
  (stateles-iterators:iterator #'count-from/next n))
   

What's the difference with other iterator libraries (snakes being my favorite)? Iterators in this library do not contain mutable state inside themselves and so are reusable:

CL-USER> (defparameter *iter* (stateless-iterators:range 1 10))
*ITER*
CL-USER> (stateless-iterators:collect *iter*)
(1 2 3 4 5 6 7 8 9)
CL-USER> (stateless-iterators:collect *iter*)
(1 2 3 4 5 6 7 8 9)
CL-USER>
   

This allows to write iterators like product where one of the iterators must be restartable:

CL-USER> (let ((a (stateless-iterators:range 0 3)))
           (stateless-iterators:collect
               (stateless-iterators:product a a)))
((0 . 0)(0 . 1)(0 . 2)(1 . 0)(1 . 1)(1 . 2)(2 . 0)(2 . 1)(2 . 2))
CL-USER>
   

NB: All functions which are passed as arguments to functions of this library are called in unspecified order, therefore they must not have any side-effects. One exception to this is a function passed to iterate-for-effects. It is called right after a new value is obtained from the iterator.