Delimited Continuations in Ruby Part 1
Jul 12, 2014
4 minute read

For the past few days at Hacker School, I’ve been exploring continuations. Continuations are hard to describe. Basically, a continuation represents the execution state of a program at a point. Capturing the continuation and invoking it later allows you to come back to that point in the programs execution. Continuations can be used to implement complicated control flow constructs.

If that was complicated, here’s a sandwich metaphor from Luke Palmer:

Say you’re in the kitchen in front of the refrigerator, thinking about a sandwich. You take a continuation right there and stick it in your pocket. Then you get some turkey and bread out of the refrigerator and make yourself a sandwich, which is now sitting on the counter. You invoke the continuation in your pocket, and you find yourself standing in front of the refrigerator again, thinking about a sandwich. But fortunately, there’s a sandwich on the counter, and all the materials used to make it are gone. So you eat it. :-)

Another way to put it: a captured continuation is a label, and to invoke it is to GOTO.

The Basics

On that note, let’s start by building a simple GOTO.

In Ruby, you need to require 'continuation' to enable continuations. callcc takes a block, and passes it the current continuation as parameter. That continuation can be invoked later.

require 'continuation'
$labels = {}

def label(label_name)
  callcc { |continuation|   $labels[label_name] = continuation }
end

def goto(label_name)
  unless $labels.has_key? label_name
    raise "No label #{label_name}"
  end
  $labels[label_name].call
end

We can then build a for loop

i = 1
puts "entering loop"
label "loop"
if i < 10
  puts i
  i += 1
  goto "loop"
end
puts "loop done"
puts "i is #{i}"

We can even build a reverse GOTO (known as the COMEFROM, implementation courtesy of Wikipedia).

$come_from_labels = {}
 
def label(l)
    if $come_from_labels[l]
        $come_from_labels[l].call
    end
end
 
def come_from(l)
    callcc do |block|
        $come_from_labels[l] = block
    end
end

Problems Abound

You can implement pretty much any control flow you want with continuations, but it may be hairy.

To give an example of the kind of problems you face when working with traditional continuations, here’s a bug I ran into when writing the GOTO example above. My orginal code for label looked like this:

def label(label_name)
   $labels[label_name] = callcc { |continuation| continuation }
end

Running it, I got the following output:

entering loop
1
2
goto.rb:15:in `goto': undefined method `call' for nil:NilClass (NoMethodError)
	from goto.rb:27:in `<main>'

Can you guess the issue? When goto was called, it returned to the place where callcc was called. That means that whatever was passed to the continuation would now be assigned to $labels[label_name]. We didn’t pass anything to the continuation, and so $labels[label_name] becomes nil.

To further illustrate the point:

$continuation = callcc { |continuation| continuation }
puts "$continuation is #{$continuation}"
# "$continuation is #<Continuation:0x007fbbaa0193f8>"
$continuation.call("hello world")
puts "$continuation is #{$continuation}"
# "$continuation is hello world"

It seems like it would be convent to separate the concept of shifting control from the place we want to return to.

Enter delmited continuations

Delimited continuations are way of capturing a fixed slice of computation instead of the unlimited computation captured by a regular continuation.

I wrote a little library called DelimR that implemnts delimited continuations for ruby by using the native callcc construct. I made a direct port of Oleg Kiselyov’s scheme implementation. In an inversion of Knuth’s famous line, the code seems to work, but I don’t understand it.

Delimited continuations have two operations. prompt marks a continuation, when the continuation is invoked, it will return to where prompt was called. control allows us to capture the continuation.

It’s best to demonstrate this with some examples. prompt on it’s own is a no-op.

DelimR.prompt { 123 }
# => 123

Inside of prompt, you can call control, and pass it a block that receives a continuation as an argument

DelimR.prompt { 1 + DelimR.control { |k| k.call(2) }}
# => 3

k captures the computation that surrounds control inside of prompt. If we don’t call k, that computation is lost

DelimR.prompt { 1 + DelimR.control { |k| 2 }}
# => 2

And finally, the most exciting part,

DelimR.prompt { 1 + DelimR.control { |k| k.class }}
# => Proc

This is really cool! In delimited continuations, k is a Proc, which is Ruby-speak for a function. Delimited continuations are also called composable continuations, because you can compose k like any other function.

Here’s an example:

DelimR.prompt { 1 + DelimR.control { |k| k.call(k.call(k.call(2))) } + 7 }
# => 26

How did 26 get there? The way to think about this is to realize that k captures the execution around control. In this case it’s k = lambda { |x| 1 + x + 7}. So, in the above statement

k.call(k.call(k.call(2))) = 1 + (1 + (1 + (2) + 7) + 7) + 7 = 26

Delimited/composable continuations are really cool and powerful abstractions. Next time, I’m going to show you how to implement generators and co-routines with them!