Loops
Let's revisit the three examples we used to illustrate the use of continuations
in Scheme. We'll start with loops because there is really nothing about them,
given that continuations in Haskell are just plain functions. Exception handling
will throw us a curve ball. We won't be able to translate our scheme
implementation of the inverses
function into Haskell without first learning
about the encoding values in continuation passing style (CPS). We'll discuss
that in the next subsection and then return to discussion exception handling and
coroutines in Haskell.
Here's our implementation of print-thrice
in Scheme, which we want to
translate into Haskell now:
(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))))
The key observation is that the line (repeat (call/cc (lambda (c) c)))
assigns
to repeat
the current continuation. This continuation is everything that
follows after this line. In Haskell, we represent this continuation as a function:
repeat i = when (i < 3) $ do
putStrLn "Hello world!"
repeat (i + 1)
The one thing we simply cannot simulate in Haskell is the destructive update
of the variable i
via (set! (+ i 1))
. That's one of the imperative features
of Scheme, and we know that variables are immutable in Haskell. So we did the
next best thing and passed the current value of i
as a function argument to
repeat
.
Now you may have to squint really hard to see the continuation here because this
really is just a standard tail-recursive function. Remember what we said: A
function becomes a continuation by virtue of being called in tail position.
Here, repeat
calls itself in tail position at the end; it uses itself as a
continuation.
After creating a continuation equivalent to our repeat
function in Haskell,
the Scheme code goes on to executing the (when ...)
block, that is, it
executes the same code captured by the continuation. Since our Haskell code
captures this continuation in the form of the repeat
function, our Haskell
version of print-thrice
needs to call repeat
, with i
set to its initial
value, 0
:
printThrice :: IO ()
printThrice = repeat 0
where
repeat i = when (i < 3) $ do
putStrLn "Hello world!"
repeat (i + 1)
>>> import Control.Monad
>>> :{
| printThrice = repeat 0
| where
| repeat i = when (i < 3) $ do
| putStrLn "Hello world!"
| repeat (i + 1)
| :}
>>> printThrice
Hello world!
Hello world!
Hello world!
Well, this was a gentle warm-up. However, given that this is really just a familiar tail-recursive function, it may also be hard to appreciate where the continuation is in this code. Rememember, tail calls are continuations.