Beyond Java - Bruce Tate [76]
5 end # cont.call returns here
6 return nil
7 end
8
9 puts "Before loop call"
10 cont=loop()
11 puts "After loop call"
12 cont.call if cont
13 puts "After continuation call"
gives you this result:
>ruby continuation.rb
Before loop call
1
2
After loop call
3
4
5
After loop call
After continuation call
So, we were able to exit the loop when something happened and return to the loop on command. Since continuations are so alien, let's look at this example in a little more detail. It's not too bad to read, once you know what's happening. Line 4 saves the game, putting it into a container. Line 12 restores the game. Let's break it down a little further, thinking like a Ruby interpreter:
Start on line 9, after the method declaration.
Execute line 9, printing the string Before loop call.
Execute line 10, calling the method called loop. Put line 10 on the call stack, so you'll remember where to return after the method call.
Enter the method loop, specified in line 1.
Do the first pass through the for loop in lines 2–5. i has a value of 1. You'll print 1.
Start the second pass through the for loop. i now has a value of 2. You'll print 2.
At line 4, i is 2, so make the callcc call in three steps. First, make a copy of the call stack. Second, make a copy of the instance variables (i is 2). Third, push the line after the continuation block (line 5) onto the copy of the call stack, so now the continuation's copy of the stack has (line 5, line 10). The call stack simply has (line 10).
At line 4, execute the return statement. You'll return the value of continuation to the line on the top of the call stack. The call stack has line 10, so you'll return the value of continuation to line 10. Set cont to the returned continuation. Recall the continuation has the current execution context—the call stack has (line 5, line 10), and variable i has a value of 2.
Execute line 11, printing the screen After call loop.
Execute line 12. Calling the continuation restores the execution state. Set the value of i to 2. Go to the line number on the top of the call stack so that you'll remove it from the call stack. Now the call stack has only line 10.
Execute the rest of the for loop, for i=3, 4, and 5.
You'll return nil. The call stack has 10 on it, so you'll return to line 10, and assign cont to nil.
Execute lines 13 and 15. Skip line 14 because cont is nil.
This continuation example shows you a few nice capabilities. You can take a snapshot of execution state at some point in time, like we did within the for loop. You can save that execution state in an object, as we did in the cont object. You can then return to the execution state stored in a continuation object at any point.
Why Would You Use Them?
You might first think that continuations are the most useful when you want to break logical control structures, as in implementing a break for our for loop, or processing exceptions. For the most part, though, you want to think "suspend and resume." Continuations are marvelous in these kinds of scenarios. Cooperative multitasking lets one program voluntarily relinquish control to another application, and resume at a later date. This problem is remarkably easy to solve using continuations. A subtler use involves communication. When you've got an application that spans multiple computers with synchronous request/response communication, you often want to suspend control until the remote system responds. When you need to scale this solution, suspending control while you wait frees the system to handle other requests. The system can conveniently resume your application without disruption when the remote system responds, simply by calling a continuation.
Continuation Servers
You can probably begin to see why continuations might be interesting for web servers. If you want to look at a web application as one continuous application with suspend/resume breaks in between to communicate with the user, it makes more sense.