Loops
Here's the essence of a loop: We have some loop variable(s) that we test using
some loop condition. If the loop condition is true, then we execute the body of
the loop and then—this is the important part—jump back up to the start of the
loop. That jump is a goto
, and since calling a continuation is a glorified
goto
, we should be able to implement a loop using continuations. Let's try:
(define (print-thrice)
(let* ((i 0)
(repeat (call/cc (lambda (c) c))))
(when (< i 3)
(display "Hello world!")
(newline)
(set! i (+ i 1))
(repeat repeat))))
(print-thrice)
prints the string Hello world!
three times, using a loop.
let*
introduces a scope. The list of pairs after let*
define a list of local
variables and initializes them. Here, we create a variable i
and set it to
0
. We also create a variable repeat
. The way we calculate the initial value
of this variable is where the magic happens. We call/cc
an anonymous function,
defined using lambda
, that takes one argument c
and simply returns it. Thus,
the expression (call/cc (lambda (c) c))
simply returns the current
continuation, and this is what we store in repeat
. In other words, repeat
stores the future computation starting from the point when call/cc
returns.
This computation tests whether i < 3
. If so, we print Hello world!
and a
newline, increase i
by one using (set! i (+ i 1))
, and then call repeat
.
What this does is jump to the continuation stored in repeat
, that is, we're
going back to the line where we defined repeat
. To be precise, when calling
repeat
, we make call/cc
return a second time. The return value of call/cc
gets assigned to repeat
once again, and then the whole show starts over. We
test whether i < 3
, print Hello world!
, increment i
, and then we jump back
up to the call/cc
yet again by calling repeat
.
Here's a subtle but important point: To make call/cc
return a second time,
that is, to repeat the loop, we call (repeat repeat)
, not just (repeat)
. In
other words, we call repeat
with itself as argument. Why do we do this?
Remember, when invoking a continuation, this makes the call/cc
return a second
time, and the return value of call/cc
is the argument with which we invoked
the continuation. In other words, whatever we pass as argument to repeat
gets
stored in our repeat
variable the second time around. In order to be able to
repeat the loop a third, fourth or 10,000th time, we need to make sure that
every time we invoke repeat
, this stores the exact same continuation in
repeat
again. Thus, we call (repeat repeat)
. This makes call/cc
return the
continuation stored in repeat
, so this continuation gets assigned to repeat
again, and we're ready for the next iteration.
As I said before, only because you can program loops using continuations doesn't
mean you should. Here's a much better way to implement print-thrice
, without
continuations:
(define (print-thrice)
(void (map (lambda (x)
(display "Hello world!")
(newline))
'(1 2 3))))
This maps an anonymous function that prints Hello world!
over the list
(1 2 3)
. Thus, this prints Hello world!
three times. map
returns the list
of results of these function applications. We don't care about these results
here, only about the effect of seeing three Hello world!
s on our screen. Thus,
we throw away the result of map
by wrapping the whole expression in
(void ...)
.