Closing in on Closures and Jumping into Continuations
Posted by Rick DeNatale Wed, 06 Jun 2007 01:41:00 GMT
Recently on ruby-talk someone asked if continuations and closures were the same thing.They are related in that they both are tied to a particular point in time in the execution of a program. The difference is that closures are like a souvenir of a trip, while continuations are like a kind of time-machine.
Closures
The general meaning of the term closure in computer science is a function which captures one or more variable bindings in effect at the time it was created. Closures are created in Ruby using lambda or its alias proc:irb(main):001:0> def closure(a)
irb(main):002:1> lambda {a}
irb(main):003:1> end
=> nil
irb(main):004:0> cl1 = closure(1)
=> #<Proc:0xb7b3cf10@(irb):2>
irb(main):005:0> cl2 = closure(2)
=> #<Proc:0xb7b3cf10@(irb):2>
irb(main):006:0> cl1.call
=> 1
irb(main):007:0> cl2.call
=> 2Notice that the block in line 2 refers to the method parameter a, and returns the value of a as the blocks value. The Kernel#lamba method turns the block into a Proc object which is returned as the value of the call.
in lines 4 and 5 we make two calls to the method closure with different arguments, and save the resultant closures in cl1 and cl2 respectively.
Notice that when we call each closure in lines 6 and 7, each one returns the value passed to cont when that closure was created.
Continuations
A continuation, on the other hand, represents a particular point of execution, not [just] variable bindings. When a continuation is called, it acts like (the dreaded) goto, we jump to the point right after the creation of the continuation. In Ruby we create a continuation with the Kernel#callcc method. This method takes a block and yields it a new continuation object.The mystical aspect of continuations is that they allow us to jump back into the middle of the execution of a method which has already returned:
irb(main):008:0> def continuation
irb(main):009:1> i = 0
irb(main):010:1> lola = nil
irb(main):011:1> callcc {|cc| puts "In callcc";lola = cc}
irb(main):012:1> puts "i is now #{i}"
irb(main):013:1> i += 1
irb(main):014:1> lola
irb(main):015:1> end
=> nil
irb(main):016:0> cont = continuation
In callcc
i is now 0
=> #<Continuation:0xb7b727a0>
irb(main):017:0> cont.call
i is now 1
=> #<Continuation:0xb7b727a0>
irb(main):018:0> cont.call
i is now 2
=> #<Continuation:0xb7b727a0> Line 10 makes the ruby interpreter see that lola is a local variable of the method and so the block can set it and the method can return it.
Note that the call to the continuation method in line 16 is the only time we actually evaluate callcc and the block. The call to callcc returns and the rest of the continuation method is excuted and the method returns.
Each time we call the continuation referenced by the variable cont, we execute the code afer the callcc which created it. It acts like a closure in a sense since it captures the last state of the variables i and lola each time. But this is because the continuation represents a particular execution state which includes not only the variebles in scope at that time, but also the program counter.
FLASH Breaking News: Rick has a Ruby Red Face
Robert Dober pointed out that if you run the above code in ruby rather than in irb it loops on the first cont.call, printing out:
i is now 1
i is now 1
i is now 1
ad infinitum (or at least ad ctrl-C)
I was actually abusing continuations, since in general they aren’t meant to be reused. I’ll write a new posting in the near future on how continuations are used in the Ruby standard library Generator class, In the meantime, here’s a simpler example which runs in ruby without trying to reuse the continuation:
def continuation
puts "Before callcc"
callcc {|lola| puts "In callcc";return lola}
puts "After callcc"
end
puts "Begin"
cont = continuation
puts "Got cont #{cont.inspect}"
if cont
cont.call
puts "After call"
end
puts "All done"If we run this using ruby we see:
$ ruby testcontin.rb
Begin
Before callcc
In callcc
Got cont #<Continuation:0xb7de3840>
After callcc
Got cont nil
All done








