A Newbie Guide to Call/cc
I haven't had much to write about in the last couple of weeks, but I've really wanted to do a small piece on the magic of call-with-current-continuation in Scheme, aka. call/cc.
For some reason I had a difficult time wrapping my head around this little statement, even though it isn't terribly complicated at all (from an end coder perspective that is; I don't want to imagine the headaches it causes for the compiler hackers ;)). For all intents and purposes, you can think of call/cc as a glorified return or a smart goto statement (and that is how it will sound when I try to explain it).
But first of all, what is this continuation thing?
You can think of a continuation as an object that represents the position of execution in a running program. If we call a continuation object as though it were a function, we will jump to that position in the program, and continue the flow from there.
If we want to store a continuation we call the function call-with-current-continuation
(shortened to call/cc
in many Scheme implementations).
This function takes a single argument, of a lambda to call.. with the current continuation..
An example is below:
(print
(call/cc (lambda (k) ; k is used as a convention for a continuation variable as far as I understand
; We can do whatever we want here, it's just a regular function
; Lets return some text
"Hello, World")))
This example will simply print "Hello, World" to the console. Now to show the mighty magic of continuations:
(print
(call/cc (lambda (k)
; Pretend we are doing some processing here,
; perhaps looping over a list looking for something.
; Now pretend that we've found what we're looking for.
; We are going to call the continuation with a value.
(k "We found the value man!")
; And now this is placed after the loop
"Didn't find what we were looking for."
)))
When (k "We found the value man!")
is called, we jump back to our continuation (ie. the place of (call/cc)
and return the value passed to k
back to print
.
So of course our example will print "We found the value man!" to the console.
This is pretty much all that there is to know about continuations. As you can see it is really a way to allow functional programs to terminate from a function early.
There are a wide variety of uses though, depending on how you dress up call/cc. A cool example comes from the in development sgrove/tehila Scheme game engine, which simplified looks a little like this:
;; Called when the Spacebar is hit
(define (break-to-repl)
(printf "Type (return) to resume gameplay~n")
(call/cc (lambda (k)
(set! return (lambda () (k #f))) ; So the user can call (return) instead of (k #f)
(repl)))) ; Call the built in REPL
This allows the user to hit Spacebar mid-game to get a REPL, which will pause the game. When they wish to continue, they simply enter (return)
into the console, and the game continues from where it left off.
Another good use of continuations is for implementing exceptions. For example if you take the SRFI-34 (Exception Handling) egg available for Chicken Scheme, you can think of the guard
construct as being call/cc, and the raise
statement as being (k 'myexception)
.
So that's it for my newbie guide to call-with-current-continuation
. I hope this cleared it up for someone, and I hope that you now understand call/cc enough to make some cool things happen with it!
Edit: DerGuteMoritz points out in the comments that you can also call continuations more than once, and gives an example:
(define foo #f)
(display (string-append (call/cc (lambda (k) (set! foo k) "foo")) "\n"))
(foo "baz")
(foo "qux")
Which will print:
foo
baz
qux