Continuations
As I said before, this section is only for the adventurous. If you think you are, it's going to be fun. It's also long, so you may want to skip it if you're pressed for time and want to get to the next chapter to get ready for your Haskell project. You can always come back here if you have a weekend or two to spare to try to wrap your head around continuations.
Let's start by reviewing what a continuation is: As the name suggests, it's a way to continue a computation from some point forward. We can take a snapshot of our program at any point and ask what it will do if we continue running it from here. That's the continuation at the current point.
Scheme was the first language to elevate continuations to first-class values. We
can pass continuations to other functions, return them as results of function
calls, and even store them in data structures. We can do everything with them
that we can do with "ordinary" values. The other thing we can do with a
continuation is to "call" it. Except that's not really a function call; it's
effectively a goto
—hence the quotes.
We'll start by looking at continuations in Scheme and how they can be used to implement various abstractions such as exception handling, loops, and coroutines. You don't need to know Scheme to follow the discussion. Use your intution, and you should be able to follow the discussion of the various pieces of Scheme code. It is not important to understand every little piece of Scheme syntax in those examples. It is important that you understand the main ideas, because we will build on them. The reason why we look at Scheme first is because this allows us to focus on working with continuations. When translating this into Haskell, we have to deal with the added challenge of figuring out how we can represent continuations as pure functions. In contrast to Scheme, where continuations are built right into the language, they're plain old functions in Haskell. We're building the ability to program with continuations ourselves.
Next we will discuss how we can think about continuations as nothing but
functions. This will lead us to how we model continuations in Haskell. I will
show you how to translate the implementations of exception handling, loops, and
coroutines from Scheme into Haskell. We'll do this twice, first using ordinary
functions and then using the Cont
monad, which allows us to program with
continuations much more conveniently. Without this monad, programming using
continuations isn't for the faint of heart, in any language.
We'll finish the discussion of continuations by explaining how the Cont
monad
can be used to simulate many of the other monads we have discussed in this
chapter.
Before we start, let me emphasize an important point: Even though learning how to use continuations is a bit of a rite of passage for Scheme programmers, they nevertheless use them sparingly. That's because overusing continuations can lead to code that is virtually impossible to understand and maintain. Haskell programmers use continuations even less because many things that do require continuations in Scheme can be done without them in Haskell thanks to lazy evaluation. Thus, the examples in this section only demonstrate the things we can do using continuations. You should not interpret them as showing what you should use continuations to do.