Scheme Programming/Continuations

Introduction to call-with-current-continuation
call-with-current-continuation is a procedure that takes as an argument another procedure, that itself takes one argument. That argument is a continuation, which in the words of Scheme inventors Gerald Sussman and Guy Steel, is "just about to return from" call-with-current-continuation. A continuation is a procedure that represents "the rest of the computation." When you call a continuation as a procedure, control jumps to where call-with-current-continuation was originally called and whatever arguments you provide to the continuation become the return values of that call to call-with-current-continuation.

Save the following program:

cont-test.scm:

Then, try this from the REPL:

What's going on here? After evaluating (define numbers (map capture-from-map '(1 2 3 4 5 6))), the continuations variable contains the continuation from each time map called capture-from-map, in reverse order. So when we evaluate <tt>((car (reverse continuations) 76)</tt>, execution control jumps back to where <tt>capture-from-map</tt> originally returned <tt>1</tt> to the <tt>map</tt> function, and the entire program stack as it existed at that time is restored. The "rest of the computation" from that point is <tt>map</tt> mapping <tt>capture-from-map</tt> across the rest of the list, which is <tt>(2 3 4 5 6)</tt>, and then the resulting list being defined as the variable <tt>numbers</tt>. But this time around, we changed the result of the part of the computation which took place inside the <tt>call-with-current-continuation</tt> lambda.

(Note: the result stored in <tt>numbers</tt> after calling the continuation is implementation dependent, since the standard doesn't specify the order in which <tt>map</tt> calls the procedure passed as argument.)

The <tt>continuations</tt> variable is outside the scope of all this, and did not get reverted to <tt>'</tt> when control jumped back. As a result, five new continuations were added to <tt>continuations</tt>:

In some Scheme implementations, <tt>call-with-current-continuation</tt> is also defined as <tt>call/cc</tt>. However, SCM doesn't define the shorter form. If you want it, you can define it yourself:

C++-like Exceptions with Continuations
Some Scheme implementations have their own error-handling syntax, but SCM does not. We can define an exception system using a combination of continuations and macros. C++ exceptions are dynamic: When you throw one, control jumps to the next <tt>catch</tt> block up the call stack. To make that happen in our implementation, we'll keep a list of continuations that represent the <tt>catch</tt> blocks. Every time a <tt>try</tt> block is entered, it'll add a continuation to this stack, and every time a <tt>try</tt> block exits, a continuation will be removed from the stack. The easy part is implementing the continuation stack:

Every time we call <tt>push-catch</tt>, a new continuation is added to the stack. When we call <tt>pop-catch</tt>, we get the last continuation that was added (and then the last one before that, etc), and that continuation is also removed from the stack.

dynamic-wind
The hard part is making sure that we detect every time that the block exits or is re-entered (which can happen because of continuations), so that we can maintain the continuation stack in the correct state. Fortunately, Scheme provides <tt>dynamic-wind</tt>, which take three procedures as arguments and has the same return value(s) as the middle lambda. The first and last procedures are guard procedures that can perform initialization and cleanup before entering and after exiting the middle procedure:

The only thing left to do is define the <tt>escape</tt> continuation, which allows execution to leave the <tt>try</tt> block after the <tt>catch</tt> executes (otherwise the <tt>body</tt> would be executed again), and wrap it all in a macro. The entire program looks like this:

exception.scm

With the above program loaded into Scheme, you now have the ability to put error-handling code in one place, and you can jump to that place by calling the <tt>throw</tt> procedure, whose argument can be any value but <tt>#f</tt>. Use the argument to send information about what went wrong to the error-handling code.