I had written this application to speed up my hobby organization:
def say (stuff) puts stuff end def ask (question) say question gets end name = ask "What's your name?" place = ask "Where do you live?" say "OK, thanks." send_hitman (name, place)
It was easy to write. Even my boss could understand it. It’s just a list of simple instructions. We’re used to those.
Then my boss told me to make it internet-enabled. Our clients wanted to use their web browsers.
I didn’t know anything about internet-enabling, but I
figured that I could just change the say and
ask functions to do web I/O instead of terminal
I/O.
def say (stuff)
# What do I put here?
end
def ask (question)
say question
# And here?
end
I asked around, and the consensus was that I had to restructure
the whole thing. “You can’t write ask
to ask over HTTP,” they said.
Why not? How is a HTTP session different from a terminal? The root of the issue is that HTTP servers can serve several users at the same time. Abstracting that away requires some nifty language features.
A typical web application doesn’t get confused by the plurality of users, because it tags an identifier on every one. It remembers its conversations, and when someone says something, it look in the history to figure out what to do. If it remember that it’s had this conversation with Karekinian:
It: What’s your name?
Karekinian: Karekinian.
It: Where do you live?
Then the next time Karekinian says something, it looks at the history, and figures out that we’re supposed to send him an assassin. This is how most web applications work.
But programming like that isn’t very intuitive. It’s much easier to keep track of what we should do later, instead of having to deduce it from what we’ve already done. When Karekinian says his name, we think “OK, later, we’ll ask him for his address, and then send him a hitman.” Let’s use this technique to improve the hitman sender. It’ll be able to talk to several clients at the same time.
How do we store a future? The best representation is a function (oops, I mean method). To create a new function in Ruby, we do something like this:
my_function = lambda do do stuff end
We call those lambdas, or closures. Our table of futures can be neatly stored as a hash table of lambdas. Without further ado, click here to see the program. Try to understand it.
Here’s a sample of the output, by the way (the program speaks in emphasis):
Yes?
hello
OK, I’ll be calling you 0. I want your name. When you’re ready, give it to me by typing “0: John Doe”
Yes?
hello
OK, I’ll be calling you 1. I want your name. When you’re ready, give it to me by typing “1: John Doe”
Yes?
1: Foo
Thanks, 1. Now, I’d like your address, too. Give it to me in the same way.
Yes?
0: Bar
Thanks, 0. Now, I’d like your address, too. Give it to me in the same way.
Yes?
0: Milky Way 40
Thanks again, 0. I’ll be sending a hitman to Bar at Milky Way 40.
Yes?
1: Milky Way 41
Thanks again, 1. I’ll be sending a hitman to Foo at Milky Way 41.
Yes?
0: hello
I’m done with you. Get out of here.
But frankly, having to write $futures[id] = lambda do yada
yada end all the time is a little annoying. And
it’s easier to miswrite it if you have to write it a
zillion times. And, the most annoying thing: the code moves a
step to the right every time we want to ask something.
Fortunately, Ruby lets us shove that code under the rug. It has
a function that gives us the future, so we don’t have to
wrap it up in lambdas ourselves. The function is
callcc.
Actually, it doesn’t just give it to us. It passes the future to a block. Like this:
callcc do |future| $it = future end puts "Hello!"
After callcc, there’s a function in
$it that contains the future. If we called it, it
would say “Hello!” again. It’s just as
if we had written this:
$it = lambda do puts "Hello!" end puts "Hello!"
That’s cool and all, but we really don’t want to
continue to run the future right away. We want to stash it away
for a while, and return to it later. Right after we save the
future callcc gave us, we want to say
“Hey, stop! I’ve saved the future now. I want
out of here!” We want to disrupt the flow of time.
The easiest way to mess with Ruby’s flow of time is to use
throw and catch.
catch :done do puts "Hello, " throw :done puts "world!" end puts "dude!"
That’s an example of messing with time. It prints
“Hello,” and then it says “Hey,
stop! I’ve printed now. I want out of here!”
and Ruby gladly hops right to the end of the whole
catch block. At the end of the day, it’s a
dumb way to print “Hello, dude!”
Applying our new-found knowledge of disrupting time to the
callcc example, we do this:
catch :done do
callcc do |future|
$it = future
throw :done
end
puts "Hello!"
end
That’s the callcc way of
doing this:
$it = lambda do puts "Hello!" end
We’ve figured out a usable replacement for the mess of
procs. If you’re feeling brave, try
converting the previous example
to use this method.
Or if you’re not, I already did it for you (This one is so cool that I colored it gold).
OK, I guess it’s about time to tell you what all this has
to do with continuations and the web. Continuation is
a fancy name for a future. That’s how callcc
got its name. It used to be
call-with-current-continuation, but that was a pain
to write, so it got shortened.
Remember how my boss told me to implement the hitman program as
a web application? And how I was struggling to find a way to
rewrite ask and say as web functions?
Continuations (well, the ability to capture them with
callcc) gives me the ability to do that.
A web server is similar to the multi-user hitman program in many ways. Obviously, many clients can use it at once. But the application only cares about one at a time. That’s why we need this stuff.
In fact, the skeleton of a continuation-enabled web server would look very similar to the hitman program. The major difference would be that it can’t just print to the same screen all the time. It needs to send and receive from different devices depending on who sent the request. But that’s pretty easy.
Without further ado, here’s the punchline of this article:
with the right equipment, it’s easy to define
ask and say in a web application.
Fortunately, you don’t have to do it yourself. Avi Bryant and friends have already
implemented such a framework. It’s called Seaside, and it’s written in
Smalltalk.
And as luck would have it, there’s even a Ruby version! Eric Hodel took the initiative in porting Seaside to Ruby. The outcome of that was Borges.
Have fun.