Cogenerator functions: back to BASIC!

The idea of a cogenerator is that it is to a generator what a coroutine is to a routine. As coroutines can pass control to fellow coroutines, so can cogenerator pass 'yielding control' to fellow cogenerators. The function co() and the objects HERE, GOTO, GOSUB, RETURN are all defined in the file cogenerator.py (download here).

A cogenerator function is a generator function that can yield HERE, GOTO and GOSUB objects, and raise a RETURN object. The cogenerator could be run as a normal, everyday generator but that's not taking advantage of it at all. A cogenerator function cogenf can be turned into a generator function using the co() function: genf = co(cogenf)

In the following examples I use a function called trace() that prints all the values yielded by a generator prefixed by their index. Below is the definition of this function

def trace(gen):
    for x in enumerate(gen):
        print "%s\t%s" % x

GOTO - two cogenerators passing control to each other

>>> def cofoo():
...     self = yield HERE
...     other = cobar(self)
...     yield 'foo start'
...     msg_from_cobar = yield GOTO(other)
...     yield 'foo middle, cobar says "%s"' % msg_from_cobar
...     yield GOTO(other)
...     yield 'foo end'
... 
>>> def cobar(other):
...     yield 'bar start'
...     yield GOTO(other, 'hello')
...     yield 'bar middle'
...     yield GOTO(other)
...     # We'll never get past here...
...     yield 'bar end'
... 
>>> foo = co(cofoo) # make a generator function out of cofoo
>>> trace(foo())
0 foo start 1 bar start 2 foo middle, cobar says "hello" 3 bar middle 4 foo end
>>>

GOSUB - flattening nested cogenerators

Note that co() can be used as a decorator if the cogenerator it decorates doesn't need to be GOTOed to from any other cogenerator.

>>> @co
... def main_gen():
...    yield 'start main'
...    result = yield GOSUB(nested_cogen())
...    yield 'returned %s' % result
...    yield GOTO(nested_cogen())
...    yield 'end main' # Never happens
...
>>> def nested_cogen():
...    yield 'nested'
...    raise RETURN(1)
... 
>>> trace(main_gen())
0 start main 1 nested 2 returned 1 3 nested
>>>

Recursive cogenerators

The example below takes a list or tuple and flattens it. It is defined as a recursive cogenerator. Note that co() can't be used as a decorator here as coflatten() called from within itself.

>>> def coflatten(x):
...    if isinstance(x, (list, tuple)):
...       for i in x:
...          yield GOSUB(coflatten(i))
...    else:
...       yield x
... 
>>> flatten = co(coflatten)
>>> trace(flatten([1, [2, [[3], 4]], 5]))
0 1 1 2 2 3 3 4 4 5
>>>
Last updated on Fri Feb 20 09:44:14 2009
arno AT marooned.org.uk