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.
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.
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.(define Coroutine-1 (coroutine (lambda (v) )))
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.
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)))))
Rewrite foo and goo as sqfoo and sqgoo so that when the program is called with (sqfoo 0),
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 ...