Skip to content

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)
GHCi
>>> 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.