Why does Ruby have callcc?

As I understand it, continuations are, in a way, a
superset of closures. A continuation creates a
closure that also has information on where it was
executing at the time, so when you execute the
continuation, it goes back to that place, in addition
to having the appropriate context. Closures don’t
simply make a copy of the stack and restore it, it’s
more like they make a reference to a persistant
lexical storage area (read about closures on Dan’s
blog about parrot for a better explanation of this).
So for example:

def foo
x = 5
q = proc { x }
x = 3
end

fun = foo
puts fun

Would, I think print 3, rather than 5, since q
would store a reference to the lexical context of
that call to foo, rather than just making a copy of
the stack up to when proc was called. Likewise,
callcc would make a reference to that lexical
scratch-pad, and values wouldn’t be replaced with
their previous values by resuming the continuation.

I think this makes callcc a bit different than
setjmp and longjmp from C (although I haven’t used
them, only read about them), as I believe they make
a copy of, and restore the stack.

Note, though, that I could be way off base, since
I’m not able to test the closure example right now.
It’s based on my understanding of them, though.

You should be able to implement closures with
continuations, though, and if continuations changed
the values back to what were when the continuation
was taken, then you couldn’t implement something
like:

def foo
x = 1
return proc { x += 1; x }
end

With continuations without some major headaches, I think.

Hope this all makes sense.

  • Dan
···

----- Original Message -----
From: Brian Candler B.Candler@pobox.com
Date: Friday, August 8, 2003 5:40 am
Subject: Re: Why does Ruby have callcc?

Here’s my entry for the ‘writing BASIC in Ruby’ competition:

$lines = {}
$running = false
$tron = false
def line(n,&blk)
callcc { |$lines[n]| }
puts “Line #{n}” if $tron and $running
yield if $running
end
def goto n
$lines[n].call
end
def run
unless $running
$running = true
$lines.sort[0][1].call
$end
end

line(100) { puts “Hello World” }
line(110) { $i = 0 }
line(120) { if $i > 10 then goto 160 end }
line(130) { puts “Current $i: %d” % [$i] }
line(140) { $i = $i + 1 }
line(150) { goto 120 }
line(160) { puts “Done!” }
line(170) { puts “Stopping now” }

$tron = true

run

One thing worth mentioning about continuations is that as far as I
can tell,
Continuation#call NEVER RETURNS. You are not pushing the current
executionstate onto a stack like Method#call or Proc#call; you are
simply replacing
the current execution context, like a longjmp in C. The current
executionthread is lot. In that case perhaps ‘call’ is not the
ideal name for this
method.

The thing I am not sure of is what values of variables, if any,
are bound up
in the continuation. Take the following example:

i = 0
here = nil
callcc {|here|}
puts i
i = i + 1
here.call unless i == 10 # here.call is just “goto here”
puts “Done!”

This is not recursive - as mentioned before, here.call just
rewinds to the
point after the callcc.

Now, clearly the value of variable ‘i’ at callcc time is not
included in
the continuation object ‘here’, because when I do ‘here.call’, the
subsequent ‘puts i’ shows ‘i’ with its new value.

I think what confuses me is the discussion on the Wiki about
coming to a
crossroads, taking the left path, finding that you get bitten by a
dog, and
then deciding that was a bad idea so you ‘rewind back in time’ to the
crossroads. But if you did this using a Ruby continuation, surely
your leg
would still be bleeding?

preferred_path = “LEFT”
bleeding = false

crossroads = nil
callcc{ |crossroads| }

puts “At the crossroads”
puts bleeding ? “I am bleeding!” : “I am OK”

puts “I am going to go #{preferred_path}”
if preferred_path == “LEFT”
sleep 2
puts “Bitten by dog!”
bleeding = true
preferred_path = “RIGHT”
crossroads.call
else
sleep 2
puts “Hit by train!”
exit
end

As I understand it, continuations are, in a way, a
superset of closures. A continuation creates a
closure that also has information on where it was
executing at the time, so when you execute the
continuation, it goes back to that place, in addition
to having the appropriate context.

OK. So if you had object or variable x in scope at that time, you still have
access to the same object/variable, but with whatever its current value
is. That makes sense.

I think this makes callcc a bit different than
setjmp and longjmp from C (although I haven’t used
them, only read about them), as I believe they make
a copy of, and restore the stack.

I don’t think so; AFAIK setjmp just stores the stack pointer and instruction
pointer (and other registers, I guess), and longjmp just restores them. C is
only portable machine-code, after all :slight_smile:

As a result, at longjmp the stack is “wound back” to exactly where it was
before. Any local variables declared within nested function calls are
therefore lost. But any variables declared within the current function, or
in functions which call it, or global variables, keep their current values.

Cheers,

Brian.

···

On Fri, Aug 08, 2003 at 10:26:26PM +0900, djd15@cwru.edu wrote:

More exactly, after a longjmp the stack pointer is put back to
where it was before. (As are all the registers, generally) This is
why longjmp is only OK to do upwards–once you exit the function that
did the setjmp, parts of the stack that longjmp will expose will have
been reused. Which, while occasionally useful for very evil code,
is normally a really fatal thing.

···

At 11:10 PM +0900 8/8/03, Brian Candler wrote:

On Fri, Aug 08, 2003 at 10:26:26PM +0900, djd15@cwru.edu wrote:

I think this makes callcc a bit different than
setjmp and longjmp from C (although I haven’t used
them, only read about them), as I believe they make
a copy of, and restore the stack.

I don’t think so; AFAIK setjmp just stores the stack pointer and instruction
pointer (and other registers, I guess), and longjmp just restores them. C is
only portable machine-code, after all :slight_smile:

As a result, at longjmp the stack is “wound back” to exactly where it was
before. Any local variables declared within nested function calls are
therefore lost. But any variables declared within the current function, or
in functions which call it, or global variables, keep their current values.


Dan

--------------------------------------“it’s like this”-------------------
Dan Sugalski even samurai
dan@sidhe.org have teddy bears and even
teddy bears get drunk