Skip to content

One Monad to Rule Them All

Let's recap what we have learned about continuations so far: Programming in continuation passing style turns functions "inside out". Instead of returning a value, a function in CPS expects a function as argument, which it calls in tail position using the value a normal function would have returned as argument. By calling this function in tail position, it uses it as a continuation. The only thing a normal function can do is to return a value. It has no control over what happens to this value after it returns. When programming in CPS, we can provide a function with multiple possible continuations, and the function can choose between them, thereby exerting explicit control over what happens once it "returns". That's pretty much exactly what the yield function constructed by callCC did. One of its arguments is the continuation that represents the computation after yield returns. But yield also has access to the continuation that represents the computation after callCC returns. yield never calls the "normal" continuation and always calls the continuation after callCC. By doing so, yield effectively never returns, and it makes callCC return immediately even if we call yield from inside some deeply nested context.

This explicit control over the future of a computation after a function returns is what makes CPS an extremely powerful tool. Unfortunately, even doing easy things in CPS feels much less natural than working with normal functions, and if we do really complex things in CPS, we are almost guaranteed to write code that nobody, even our future self, has no way to understand. The Cont monad, and its transformer version ContT, brought some sanity back, by doing all the passing around of continuations for us, behind the scenes of do-notation. Thus, we can mostly forget that we're programming in CPS, but we do gain the power of programming in CPS when we need to by using callCC.

To further demonstrate the power of the Cont monad, let's ask a provocative question. Assume that Haskell didn't give us the ability to define our own monads. The only thing the implementers of Haskell are willing to do is to give us one monad they're willing to build right into the language. Which one would we choose?

Actually, the answer should immediately be, "the IO monad," because we cannot do anything useful if we cannot perform I/O. Okay, then let's assume that we're allowed to have two monads. One of them must be IO because we cannot do anything without it. What should the other one be? Clearly, State, Reader, Writer, Maybe, the list monad, and Cont are all useful for certain types of programs. Can we really choose one over all the others?

Yes. We should choose Cont over all the others, because many other monads can be simulated using Cont, simply by choosing the return type r in Cont r appropriately. Let's look at a few examples.