I am studying the language racket and trying to grasp what call/cc is actually for. Could someone please explain it in simple words and give an example or two? Thanks.
If you have an expression (+ (* 2 3) (/ 10 2))
a Scheme system will not evaluate everything at the same time but in parts. The order is not specified in Scheme but lets imagine it's from left to right (I think Racket always do left to right):
You do (* 2 3)
, the continuation to that would be to compute (/ 10 2)
, then (+ result1 result2)
. The way a Scheme system can do this is by transforming your code to Continuation passing style before execution. The expression above turns into something like this:
(lambda (k)
(*& 2
3
(lambda (result1)
(/& 10
2
(lambda (result2)
(+& result1 result2 k))))))
Now, the procedures with &
in the end are the same as in Scheme except it takes a continuation as it's last argument. An example of one of these: (define (+& a b k) (k (+ a b)))
. All the others are done just like that and are considered primitives.
if you apply that and pass display
or values
it will either display or evaluate to 11.
However, if you used call/cc
you could override that. Imagine this variant:
(call/cc (lambda (k) (+ (* 2 3) (/ 10 (if (zero? a) (k +inf.0) a))
In Scheme you get an error when dividing with zero and if that happens you want to call off the rest of the calculations and say the result is infinite. The code above does that and if a
is zero it won't sum the results from the two previous calculations.. It actually acts as a GOTO. A CPS version of that code would look something like this:
(lambda (k)
(*& 2
3
(lambda (result1)
(zero?& a
(lambda (azero?)
(if azero?
(k +inf.0) ; continuation used here
(/& 10
a
(lambda (result2)
(+& result1 result2 k))))))))) ; and here
So what does call/cc
do? Well it lets you code your usual way and not CPS like how the computer does the actual computation but you get the best of two worlds by getting hold of the continuation so that you can do the same as if you had written it in CPS.
Now, imagine this code block:
(let* ((c 10)
(r (call/cc (lambda (exit) exit))))
(display "Hello\n")
(cond ((zero? c) 'done)
(else (set! c (- c 1))
(r r))))
Here you see I return the continuation as r
. The continuation is the rest of the calculations starting with setting r
, then doing display
... This actually displays "hello\n" 11 times since when you call the continuation in the bottom it does the same all over again.
Just like eval
I do try to keep this at an absolute minimum and when I do I usually do it to get an abort from a on going computation. Eg.
(define (lstadd1 lst)
(call/cc (lambda (exit)
(let loop ((lst lst))
(cond ((pair? lst) (cons (add1 (car lst)) (loop (cdr lst))))
((null? lst) '())
(else (exit #f))))))) ; not proper
(lstadd1 '(1 2 3)) ; ==> (2 3 4)
(lstadd1 '(1 2 . 3)) ; ==> #f
For more examples I love Matt Mights page with lots of examples on how to use continuations.