views:

407

answers:

4

This is related to http://stackoverflow.com/questions/612761/what-is-call-cc, but I didn't want to hijack this question for my own purposes, and some of its arguments like the analogy to setjmp/longjmp evade me.

I think I have a sufficient idea about what a continuation is, I think of it as a snapshot of the current call stack. I don't want to go into the discussion why this might be interesting or what you can do with continuations. My question is more specifically, why do I have to provide a function argument to call/cc? Why doesn't call/cc just return the current continuation, so I could do whatever I please with it (store it, call it, you name it)? In a link from this other question (http://community.schemewiki.org/?call-with-current-continuation-for-C-programmers), it talks about "Essentially it's just a clean way to get the continuation to you and keep out of the way of subsequent jumps back to the saved point.", but I'm not getting it. It seems unnecessarily complicated.

+2  A: 

That would be less versatile. If you want that behavior, you can just do:

(call/cc (lambda (x) x))

You could take a look at the example usages of continuations in "Darrell Ferguson and Dwight Deugo. "Call with Current Continuation Patterns". 8th Conference on Pattern Languages of Programs. September 2001." (http://library.readscheme.org/page6.html) and try to rewrite them using a call/cc-return, defined as above.

Jay Kominek
Thanks for showing how I could degrade to this corner case. But I wanted an explanation, not a solution.
ThomasH
+8  A: 

If you use a construct like Jay shows, then you can grab the continuation, but in a way, the value that is grabbed is already spoiled because you're already inside that continuation. In contrast, call/cc can be used to grab the continuation that is still pending outside of the current expression. For example, one of the simplest uses of continuations is to implement a kind of an abort:

(call/cc (lambda (abort)
           (+ 1 2 (abort 9))))

You cannot do that with the operation you describe. If you try it:

(define (get-cc) (call/cc values))
(let ([abort (get-cc)]) (+ 1 2 (abort 9)))

then you get an error about applying 9 as a procedure. This happens because abort jumps back to the let with the new value of 9 -- which means that you're now doing a second round of the same addition expression, except that now abort is bound to 9...

Two additional related notes:

  1. For a nice an practical introduction to continuations, see PLAI.
  2. call/cc is a little complex in that it takes in a function -- a conceptually easier to use construct is let/cc which you can find in some implementations like PLT Scheme. The above example becomes (let/cc abort (+ 1 2 (abort 9))).
Eli Barzilay
So, what you say in terms of my question, is (a) if the continuation is immediately returned it is "spoiled" because I'm inside the continuation. But I can't be inside a continuation unless I call it. If continuations are first-class values they cannot be spoiled by passing them around, can they?!Then, you say (b) call/cc would grab a "pending" continuation. How can a value be pending? You also say (c) that the continuation is "outside" the current expression. I don't quite get that. How much does the continuation capture? Everything up to but not including the call to call/cc?!
ThomasH
Yes, you're already inside the continuation. The thing about continuations is that they represent the "future" computation after the current call/cc expression is done -- so after you're done with (call/cc (lambda (k) k)) you immediately proceed to the continuation that you've just captured.
Eli Barzilay
Note, BTW, that the view of "a continuation represents the current stack" is helpful to implementing it, but not to understand it. A better way of looking at it if you want to understand it is to view it as the context. For example, the continuation that gets bound to k in (list 1 2 (call/cc (lambda (k) k)) 3 4) is the context that is waiting for an answer, and will then proceed to evaluate 3 and 4, then apply list on the 5 values. In other words, you view it as: (list 1 2 [...] 3 4).This can be hard to do, because in most cases this context is global. See those references I pointed at.
Eli Barzilay
+1  A: 

I suggest starting by asking yourself: what does it mean to be a first-class continuation?

The continuation of an expression essentially consists of two pieces of data: first, the closure (i.e., environment) of that expression; and second, a representation of what should be done with the result of the expression. A language with first-class continuations, then, is one which has data structures encapsulating these parts, and which treats these data structures just as it would any other.

call/cc is a particularly elegant way to realise this idea: the current continuation is packaged up as a procedure which encapsulates what-is-to-be-done-with-the-expression as what the procedure does when applied to the expression; to represent the continuation this way simply means that the closure of this procedure contains the environment at the site it was invoked.

You could imagine realising the idea of first-class continuations in other ways. They wouldn't be call/cc, and it's hard for me to imagine how such a representation could be simpler.

On a parting note, consider the implementation of let/cc that Eli mentioned, which I prefer to call bind/cc:

(define-syntax bind/cc
    (syntax-rules ()
        ((bind/cc var . body)
             (call/cc (lambda (var) . body)))))

And as an exercise, how would you implement call/cc based on bind/cc?

Charles Stewart
Thanks for the post, but it is, again, too far off of my question to leave me with an answer.
ThomasH
+1  A: 

Against common SO netiquette I'm answering my own question, but more as the editor than the provider of the answer.

After a while I started a similar question over at LtU. After all, these are the guys that ponder language design all day long, aren't they, and one of the answers finally kicked in with me. Now things mentioned here, e.g. by Eli or in the original question, make much more sense to me. It's all about what gets included in the continuation, and where the applied continuation sets in.

One of the posters at LtU wrote:

"You can see exactly how call/cc allows you to "keep out of the way." With em or get/cc you need to do some kind of test to determine if you have a back-jump or just the initial call. Basically, call/cc keeps the use of the continuation out of the continuation, whereas with get/cc or em, the continuation contains its use and so (usually) you need to add a test to the beginning of the continuation (i.e. immediately following get/cc / em) to separate the "using the continuation parts" from the "rest of the continuation" parts."

That drove it home for me.

Thank you guys anyway!

ThomasH
I think Derek's em (excluded middle) continuation provider can be implemented using get/cc. If I find time, I'll check this.
Charles Stewart