elisp: Generators
10.5 Generators
===============
A “generator” is a function that produces a potentially-infinite stream
of values. Each time the function produces a value, it suspends itself
and waits for a caller to request the next value.
-- Macro: iter-defun name args [doc] [declare] [interactive] body...
‘iter-defun’ defines a generator function. A generator function
has the same signature as a normal function, but works differently.
Instead of executing BODY when called, a generator function returns
an iterator object. That iterator runs BODY to generate values,
emitting a value and pausing where ‘iter-yield’ or
‘iter-yield-from’ appears. When BODY returns normally, ‘iter-next’
signals ‘iter-end-of-sequence’ with BODY’s result as its condition
data.
Any kind of Lisp code is valid inside BODY, but ‘iter-yield’ and
‘iter-yield-from’ cannot appear inside ‘unwind-protect’ forms.
-- Macro: iter-lambda args [doc] [interactive] body...
‘iter-lambda’ produces an unnamed generator function that works
just like a generator function produced with ‘iter-defun’.
-- Macro: iter-yield value
When it appears inside a generator function, ‘iter-yield’ indicates
that the current iterator should pause and return VALUE from
‘iter-next’. ‘iter-yield’ evaluates to the ‘value’ parameter of
next call to ‘iter-next’.
-- Macro: iter-yield-from iterator
‘iter-yield-from’ yields all the values that ITERATOR produces and
evaluates to the value that ITERATOR’s generator function returns
normally. While it has control, ITERATOR receives values sent to
the iterator using ‘iter-next’.
To use a generator function, first call it normally, producing a
“iterator” object. An iterator is a specific instance of a generator.
Then use ‘iter-next’ to retrieve values from this iterator. When there
are no more values to pull from an iterator, ‘iter-next’ raises an
‘iter-end-of-sequence’ condition with the iterator’s final value.
It’s important to note that generator function bodies only execute
inside calls to ‘iter-next’. A call to a function defined with
‘iter-defun’ produces an iterator; you must drive this iterator with
‘iter-next’ for anything interesting to happen. Each call to a
generator function produces a _different_ iterator, each with its own
state.
-- Function: iter-next iterator value
Retrieve the next value from ITERATOR. If there are no more values
to be generated (because ITERATOR’s generator function returned),
‘iter-next’ signals the ‘iter-end-of-sequence’ condition; the data
value associated with this condition is the value with which
ITERATOR’s generator function returned.
VALUE is sent into the iterator and becomes the value to which
‘iter-yield’ evaluates. VALUE is ignored for the first ‘iter-next’
call to a given iterator, since at the start of ITERATOR’s
generator function, the generator function is not evaluating any
‘iter-yield’ form.
-- Function: iter-close iterator
If ITERATOR is suspended inside an ‘unwind-protect’’s ‘bodyform’
and becomes unreachable, Emacs will eventually run unwind handlers
after a garbage collection pass. (Note that ‘iter-yield’ is
illegal inside an ‘unwind-protect’’s ‘unwindforms’.) To ensure
that these handlers are run before then, use ‘iter-close’.
Some convenience functions are provided to make working with
iterators easier:
-- Macro: iter-do (var iterator) body ...
Run BODY with VAR bound to each value that ITERATOR produces.
The Common Lisp loop facility also contains features for working with
iterators. See (cl)Loop Facility.
The following piece of code demonstrates some important principles of
working with iterators.
(require 'generator)
(iter-defun my-iter (x)
(iter-yield (1+ (iter-yield (1+ x))))
;; Return normally
-1)
(let* ((iter (my-iter 5))
(iter2 (my-iter 0)))
;; Prints 6
(print (iter-next iter))
;; Prints 9
(print (iter-next iter 8))
;; Prints 1; iter and iter2 have distinct states
(print (iter-next iter2 nil))
;; We expect the iter sequence to end now
(condition-case x
(iter-next iter)
(iter-end-of-sequence
;; Prints -1, which my-iter returned normally
(print (cdr x)))))