TL;DR call/cc
lets you tap into Schemes internal code so that you can use continuations without writing your code in continuation passing style. The best evaluation model is the substitution model given that you don't use set
and view it from CPS code
Imagine this little program.
(define (sum-list lst)
(cond ((null? lst) 0)
((number? (car lst)) (+ (car lst) (sum-list (cdr lsr))))
(else (sum-list (cdr lsr)))))
(display (sum-list '(1 2 3 4))) ; displays 10
Imagine you want the result to be 1337
if you hit the else
term. How would you go about that without rewriting the whole thing? You can't. However most Scheme implementation does CPS conversion to your code where changing it is trivial:
(define (sum-list& lst k)
(null?& lst
(lambda (nlst?)
(if& nlst?
(lambda ()
(k 0))
(lambda ()
(car& lst
(lambda (car-lst)
(number?& car-lst
(lambda (ncl?)
(if& ncl?
(lambda ()
(cdr& lst
(lambda (clst)
(sum-list& clst
(lambda (sclst)
(+& car-lst sclst k))))))
(lambda ()
(cdr& lst
(lambda (clst)
(sum-list& clst k))))))))))))))
(sum-list& '(1 2 3 4)
(lambda (sum)
(display& sum halt)))
cond
is a macro so it is the if&
calls you see. I know what you're thinking. Why not tell the programmers to do it like this in the first place? Just kidding!
If you change the second continuation in the second if&
to (lambda () (k 1337))
you're done. As beautiful as CPS is it is horrible to read and write, but since the compiler does this anyway couldn't there be a way to be able to write your code in the first way and still get to code control flow? The best of two worlds is enabled with call/cc
. call/cc
is this in CPS:
(define (call/cc& f& continuation)
(define (exit& value actual-continuation)
(continuation value))
(f& exit& continuation))
It doesn't do much at all. It passes exit&
which ignores the real continuation of the program when called and calls the value with the original continuation of the call/cc&
call. With the list '(1 2 3 #f)
you'd have 3 nested continuations waiting to add with the result but all of that needs to be cancelled. If you get to choose the value of the continuation before that calculation it automatically is cancelled.
And that is it. When you write your code with it you don't need to do full CPS, only the continuation you want thought call/cc
like this:
(define (sum-list lst)
(call/cc
(lambda (k)
(define (helper lst)
(cond ((null? lst) 0)
((number? (car lst))
(+ (car lst) (helper (cdr lst))))
(else (k 1337))))
(helper lst))))
This gets transformed to this in CPS:
(define (sum-list& lst k)
(call/cc&
(lambda (k& real-k)
(define (helper& lst k)
(null?& lst
(lambda (nlst?)
(if& nlst?
(lambda ()
(k 0))
(lambda ()
(car& lst
(lambda (car-lst)
(number?& car-lst
(lambda (ncl?)
(if& ncl?
(lambda ()
(cdr& lst
(lambda (clst)
(helper& clst
(lambda (sclst)
(+& car-lst sclst k))))))
(lambda ()
(k& 1337 real-k))))))))))))
(helper& lst real-k))
k))
(sum-list& '(1 2 3 4)
(lambda (sum)
(display& sum halt)))
call/cc
can always be avoided. Our example could have been rewritten to return 1337 like this:
(define (sum-list lst)
(define (helper lst sum)
(cond ((null? lst) sum)
((number? (car lst)) (helper (cdr lst) (+ sum (car lst))))
(else 1337)))
(helper lst 0))
This is tail recursive and instead of creating continuations it accumulates. For completeness here is the CPS version of it:
(define (helper& lst sum k)
(null?& lst
(lambda (nlst)
(if& nlst
(lambda () (k sum))
(lambda ()
(car& lst
(lambda (cl)
(number?& cl
(lambda (ncl?)
(if& ncl?
(lambda ()
(cdr& lst
(lambda (cdrl)
(+& sum
cl
(lambda (scl)
(helper& cdrl
scl
k))))))
(lambda () (k 1337))))))))))))
(define (sum-list& lst k)
(helper& lst 0 k))