Coroutines

Chapter: Coroutines

The term non-standard control structure refers to program execution patterns that are beyond the range of simple sequencing, iteration or recursion. Many important algorithms require non-standard control for efficient implementation, and large programs such as operating systems make use of well-defined control disciplines for process management.

Scheme's capacity for modeling first-class continuations, either with CPS or call/cc, makes it possible to implement any control structure in Scheme. In this chapter we will take a close look at just such a discipline called a coroutine.

Coroutine Definition

A coroutine set is a collection of executing Scheme procedures that interact with each other according to the following rules.

Most coroutine systems implement some mechanism for transfering values between the source and target of a resume operation. In Scheme there is a natural way to make this work, as you will now see.

We have extended the Scheme language by a new special form, (coroutine (lambda (v) <body>)) that evaluates to a coroutine object. This form is in the file coroutine.ss and any program using coroutines must have (require "coroutine.ss").

Name a coroutine the same way you name an ordinary function; e.g.

(define Coroutine-1 (coroutine (lambda (v) )))
The body of a coroutine is a non-terminating expression using standard Scheme constructs and one additional form. This is (resume <co> <val>), where <co> refers to any other coroutine defined in the same way.

Suppose coroutine A calls (resume B x). The value of x is to be sent to coroutine B. Now, there are only two possibilities for B's : either B has never been run at all, or it is blocked by a call to resume. In the first case, x will be bound to the lambda variable v in the coroutine's definition. In the second case, B is frozen at a point just about to return from the resume call. It would be natural for the value of x to be returned in B by the call to resume that blocked B.

Confused? Let's look at a simple example: A pair of coroutines that each print some value and then resume the other.

Foo and Goo

We define 2 coroutines, foo and goo that loop passing an integer back and forth between m in foo and n in goo. Each coroutine prints its name and the current value of its parameter before resuming the other with some new value.

(define foo
  (coroutine
   (lambda (m)
     (letrec ([loop
               (lambda ()
                 (printf "foo: ~a~n" m)
                 (if (<= m 100)
                     (begin
                       (set! m (resume goo (+ m 2)))
                       (loop))))])
       (loop)))))
 
(define goo
  (coroutine
   (lambda (n)
     (letrec ([loop
               (lambda ()
                 (printf "goo: ~a~n" n)
                 (set! n (resume foo (- n 1)))
                 (loop))])
       (loop)))))
 


Exercise 12

Start the program with either (foo 0) or (goo 0) and see what happens.

  1. Suppose m has value x just before (set! m (resume goo (+ m 2))) is executed in foo. What value is assigned to m by this statement? Verify your answer by tracing m's value through this statement and into the other coroutine.

  2. The use of set! is convenient but unnecessary. Rewrite the bodies of foo and goo to do the same computation without set!.

  3. The algebraic identity (x + 1)2 = x2 + 2x + 1 can be used to produce the sequence of integer squares 0, 1, 4, 9, 16, ... without ever actually squaring any value. Note that the increment from 0 to 1 is 1, from 1 to 4 is 3, from 4 to 9 is 5, etc.

    Rewrite foo and goo as sqfoo and sqgoo so that when the program is called with (sqfoo 0),

    You may use set! if you want to, but the program can be written without it.

    Example:

    > (sqfoo 0)
    sqfoo: 0
    sqgoo: 0
    sqfoo: 1
    sqgoo: 1
    sqfoo: 2
    sqgoo: 4
    sqfoo: 3
    sqgoo: 9
    sqfoo: 4
    sqgoo: 16
    sqfoo: 5
    sqgoo: 25
    sqfoo: 6
    sqgoo: 36
    sqfoo: 7
    sqgoo: 49
    sqfoo: 8
    sqgoo: 64
    sqfoo: 9
    sqgoo: 81
    sqfoo: 10
    sqgoo: 100
    sqfoo: 11
    sqgoo: 121
    sqfoo: 12
    sqgoo: 144
    sqfoo: 13
    sqgoo: 169
    sqfoo: 14
    sqgoo: 196
    sqfoo: 15
    sqgoo: 225
    sqfoo: 16
    sqgoo: 256 
    ...
    




rms@cs.oberlin.edu