Python/Ruby style generators for PLT Scheme


This package provides a simple syntax for defining generators
similar to those in Python or Ruby.  As a quick-and-dirty example,
once has been required, we can define the following:

    > (define-generator (integers n)
       (let loop ([n n])
         (yield n)
         (loop (add1 n))))

This defines a generator function called INTEGERS that produces
generator values.  Each generator value returns successive
values to its caller by using the YIELD keyword.  We can extract
sequential values out of a generator by using GENERATOR-NEXT:

    > (define my-nums (integers 42))
    > (generator-next my-nums)
    > (generator-next my-nums)

Here is another simple example:
    > (define-generator (rotate . args)
        (let loop ()
          (for-each yield args)
    > (define colors (rotate 'green 'yellow 'red 'yellow))
    > (generator-next colors)
    > (generator-next colors)
    > (generator-next colors)
    > (generator-next colors)
    > (generator-next colors)
    > (generator-next colors)

Within the body of a DEFINE-GENERATOR, the YIELD keyword suspends
the generator's context and raises the yielded value back to the caller.
Subsequent calls to GENERATOR-NEXT will restore the generator's
context and let it continue processing.

The rest of the documentation here will present the API and
some more examples.

_generator.ss_: generator iteration support


> generator

> exn:fail:generator-exhausted


> (define-generator (name args ...) body ...)              SYNTAX

Defines a generator function that, when called, produces a generator
struct.  Values can be extracted by the generator by using
GENERATOR-NEXT.  In the context of the body, the YIELD keyword
is used to send values back to the caller.

> (yield a-value)                                          SYNTAX
Keyword for sending values to the caller.  Useful only in the context
of a DEFINE-GENERATOR form.  If used outside of DEFINE-GENERATOR,
it should raise a proper syntax error.

> (generator? datum)

Returns #t if the datum is a generator.

> (generator-next generator [finished-f])                  FUNCTION

Returns the next element in the generator.  By default,
if there are no more elements, raises exn:fail:generator-exhausted.
But if finished-f is provided, finished-f is applied.  finished-f
must be a function that takes a single exception argument --- the
exn:fail:generator-exhausted instance.

New to (= major 2):

> (generator-next generator [finished-f] [resume-value])   FUNCTION

We allow cross communication back to the generator at the
point of resumption by passing an additional resume-value.  See some
of the examples in for example usage.

> (generator-fold fold-f accumulator generator)            FUNCTION

Performs a standard fold of fold-f across the items in the

> (generator-for-each f generator)                         FUNCTION

Performs a standard for-each iteration of f across the items
in the generator.

> (make-generator seed-function)                           FUNCTION

Low-level function for creating generators.  The DEFINE-GENERATOR
macro expands to a use of MAKE-GENERATOR.  To a first approximation,

  (define-generator (repeat-forever thing)
    (let loop ()
      (yield thing)))
is equivalent to:

  (define (repeat-forever thing)
     (lambda (yield)
       (let loop ()
         (yield thing)

Example functions

> (list/gen a-list)                                        FUNCTION
Takes a list and returns a generator that iterates across the
items in the list.

> (list->flattened/gen list)                               FUNCTION
Takes a list and returns a generator that iterates deeply across
the datums of the list.

More examples 

Here are a few more examples to show how we can define and
use generators:

(Note: most of these are just cribbed from:

List flattening:

    (define (flatten l)
       (generator-fold cons '() (list->flattened/gen l))))

Yielding just 1 and 2:

    (define-generator (one-and-two)
      (yield 1)
      (yield 2))
Approximations to PI:

    (define-generator (pi-series)
      (let loop ([i 1.0]
                 [j 1]
                 [sum 0])
        (yield (* 4 sum))
        (loop (+ i 2) (* j -1) (+ sum (/ j i)))))
First n elements of a generator:

    (define-generator (first-n gen n)
      (let loop ([n n])
        (cond [(= n 0) 'done]
               (yield (generator-next gen))
               (loop (sub1 n))])))

Displaying the first twenty elements of PI-SERIES:

     (lambda (x) 
       (display x) 
     (first-n (pi-series) 20))


Development History

I want to describe how this module got started, since this was
a good learning project for me.

Nusret asked a question on how the continuation/tree-traversal example
in "Teach Yourself Scheme in Fixnum Days" worked:

I saw this as an opportunity to make sure I really understood
continuations, so I wrote a tutorial on the concepts behind that

Much of this I borrowed from material in PLAI and The Little Schemer

I thought that I should polish this and make it a PLaneT package,
so I asked for some recommendations on making things nicer.  I got a
bit of friendly feedback, and incorporated what I could.

Ryan Culpepper suggested that I use syntax-parameterize to bind YIELD 

which was something I had always wanted to learn how to do.


Programming Languages: Application and Interpretation, Ch 19.5.

Python PEP 255: Simple Generators

Generators in Icon, Python, and Scheme

Generating Iterators

Thank you!
Thanks to Guillaume Marceau, Dave Herman, Ryan Culpepper,
and Robby Findler for suggestions to improve this package.
And thanks to the PLT group in general for providing such
a pleasant language to play and learn with!