Continuations in ruby

8 minute read Published: 2011-03-27

In the weekends I like to learn lisp (my favorite lisp is scheme), and, whilst I do it in a chaotic fashion, I end up actually learning. Today I'm reading about continuations, a theme that is mentioned over and over in schemeland and which I hadn't the guts to try and understand. Until I found this chapter in the book "Teach yourself scheme in fixnum days". As eye-opening as it is, what I found most surprising is that ruby also has continuation! So I'll sum up what I learned about continuations using ruby for the examples. All the examples and content are either paraphrased or taken directly from the above book.

The first example in the book is simple enough:

1 + callcc {|k| 2 + k.call(3) }

So, what is k? Well, you might see it like this: the point in the program we're leaving to go into the continuation block. Thus, k above would be:

1 + []

Where the "hole" expressed by the brackets is where we left off to go into the definition of the continuation.

Therefore, if we call this continuation, we'd be filling the hole with whatever parameter we pass, so, to our amazement, the above code would be equivalent to:

1 + 3 

What we just did, fill the hole and forget what we where doing, is called an escaping continuation.

Not only can we escape from a continuation, but we can also store it and use it to our heart's content:

r = nil
1 + callcc do |k| 
       r = k 
       2 + k.call(3) 
     end
#=> 4
puts r.call(5)
#=>6
puts 3 + r.call(5)
#=>6

Note the last example, instead of 9, which could be expected, we get 6! Why? Calling a continuation always forgets what it was doing to fill the continuation's need. Kinda romantic, isn't it?

Now, let's define a recursive function which takes a list and returns the product of all of it's elements together:

def product(l)
    return 1 if l.empty?
    l.first * product(l.slice(1..-1))
end

Fair enough, but, what if one of the elements is a zero? Well, then we're in for lots of fruitless computations. Let's see how continuations could help here:

def product(l)
    callcc do |exit|
        return 1 if l.empty?
        unless l.first.zero?
            l.first * product(l.slice(1..-1))
        else
            exit.call 0
        end
    end
end

What's happening here? Well, remember how in school you learned about how a function is trapped when it makes recursive calls, waiting for them to end to finally die in peace? Well, here we stated that the whole function is a "hole" that wants fo be filled. In regular conditions, it just does the dreaded recursion. But, when it encounters a zero, it calls the continuation with zero and, what does that mean? Drum-doll... Exactly, the function forgets it was waiting for a recursion to end and just returns a zero, effectively avoiding useless calls! In more scientific terms, the whole call stack is sent to hell and the original function returns happily. So, again, we used an escaping continuation to leave a fruitless recursive call stack behind. Neat.

Now, to a more involved example.

Remember trees? You loved them, didn't you, with all those cool recursive algorithms all over the place? Well, let's simplify 'em a little and see them as nested lists. Ok, now, let's say that we have a function called same_fringe? that takes two nested lists (ahem, trees, remember?) and returns true if they have the same leaves in the same order, and false otherwise. Like this:

[1, [2,3]].same_fringe? [[1,2],3] #=> true
[1,2,3].same_fringe? [1, [3, 2]] #=>false

So let's hijack the Array class and put the method there. Remeber, we're being functional here, so let's flatten the lists and see if they match:

class Array
    def same_fringe?(o)
        self.flatten == o.flatten
    end
end

This definition is deceptively more succinct than the scheme version of the example, granted; but the computational complexity remains: Clever as it is, the implementation for flatten in the ruby language still has to visit the whole array to flatten it, which is a waste of resources if we could somehow just go leaf by leaf, without flattening, and leave as soon as we have an answer.

The more clever among you could recognize this as an occasion to use a generator, but, in ruby, the closest thing we have is yielding to a block, which is hardly a proper generator; that is, we want to call a function that returns the next element in the collection after the last time we called it, anywhere, and not just inside a block. (Python does have generators, though, and in java, you can have them if you implement the iterator interface).

A continuation, however, could come in handy in this situation:

class Array
    def rest
        self.slice 1..-1
    end
    
    def tree_generator
        caller = nil
        generate_leaves = lambda do
            loop = lambda do |tree|
               if tree.is_a?(Array) and tree.empty?
                nil #we reached the end of the array
               elsif tree.is_a? Array
                loop.call tree.first
                loop.call tree.rest
               else
                callcc do |rest_of_tree|
                    #I just don't get this :(
                    generate_leaves = lambda{rest_of_tree.call(:resume)}
                    caller.call tree
                end
               end
            end
            loop.call(self)
        end
        lambda do
            callcc do |k|
                caller = k
                generate_leaves.call
            end
        end
    end

end

The above procedure is rather convoluted, and I must confess that, although I had to understand it to translate it to ruby, I didn't get the last bit about sending :resume to the generate_leaves lambda. But the gist of the method is mind-blowing: it defines an internal method will get the leaves one by one (generate_leaves) and returns a method that sets itself as a continuation for that internal method; the internal generator, for it's part, does something amazing: when it is inside of an array, it makes recursive calls to go inside that array's head an tail. And when we reach a leaf, we return a continuation that thinks that the whole tree is where we just were.

To further illustrate this point, here's a generator that can go through an array yielding one element at a date =

class Array
   def each_one
        control_state = lambda do |ret|
            each do |elem|
                callcc do |resume|
                    control_state = lambda do |r|
                        resume.call r
                    end
                    ret.call elem
                end
            end
            ret.call :the_end
        end
        lambda do
            callcc {|k| control_state.call(k)}  }
        end
    end
end

This one is clearer: the method returns a procedure that will call, the first time, the control_state procedure, which is defined first as something that goes through each element of the original array, but it stores where it is the computation for the next time it is called. So each time we call whatever was returned to us in a call to each_one, we'll get the next element from where we left off!. That's more powerful that the each method, because we can obtain this procedure and call it wherever we want, not only inside a single block. Color me thunderstruck.

Well, let's redefine same_fringe? using this amazing new knowledge:

class Array
    def same_fringe?(o)
        gen1 = self.tree_generator
        gen2 = o.tree_generator
        loop do
            leaf1 = gen1.call
            leaf2 = gen2.call
            if leaf1 == leaf2 
                if leaf1.nil?
                    return true
                end
            else
                return false
            end
        end
    end
end

Now, we could go a little further and generalize this generator thing with a method that creates coroutines, but this would mean being able to create a binding and then making it available for a method, but that seems impossible in ruby. To illustrate this, we could see the scheme macro that does this (in racket, remember to (require (lib "defmacro.ss")):

(define-macro coroutine
  (lambda (x . body)
    `(letrec ((+local-control-state
               (lambda (,x) ,@body))
              (resume
               (lambda (c v)
                 (call/cc
                  (lambda (k)
                    (set! +local-control-state k)
                    (c v))))))
       (lambda (v)
         (+local-control-state v)))))

And we can do awesome things with it, like defining a coroutine that fetches a leaf and sends its leaves to another coroutine that can then match this leaf to another leaf, fetched by yet another instance of the same coroutine acting on the other tree. Hence, we could create a procedure that uses two instances of a fetcher coroutine and a matcher coroutine and puts all three to work to fetch leaf by leaf and compare them, all in different procedures that remember their interaction with each other. To see these little guys, see the original article, because in ruby, I can't find a way to simulate a way to mimic the letrec form to call the above macro on a function definition and give it the binding with the definition of resume to the function; the more we could do I guess could be a function that takes a block (the body in the above macro), but making that block recognize the resume method as part of its environment, well, there is when my ruby falls short (maybe we could use the rubinius ruby internals?).