Skip to content

Exceptions

If you want, you can breathe a sigh of relief and completely forget about the details of the implementation of the Cont r monad and of its callCC function. The only thing you need to remember is that callCC calls its argument, a function, with a function yield as argument that can be used to make callCC return immediately. With this in hand, we can implement the Scheme version of inverses more or less verbatim:

Inverses.hs
inverses :: [Double] -> Maybe [Double]
inverses xs = runCont inv id
  where
    inv = callCC (\abort -> Just <$> mapM (invert abort) xs)
    invert abort x | x == 0    = abort Nothing
                   | otherwise = return (1 / x)

inverses xs is supposed to be a plain old function. It only uses the Cont monad in its implementation. The real implementation is provided by the inv function. We run this function using id as its continuation. That is, inverses xs simply extracts the value returned by inv.

inv uses callCC to call the function (\abort -> Just <$> mapM (invert abort) xs). This function maps (invert abort) over the elements in xs. If this exits normally, we have a list of inverses of the elements in xs, and we need to wrap the result in Just. Since invert abort is a function that uses the Cont r monad, we need to use mapM, not map, to apply it to every element in xs. The result has the type Cont r [Double], not [Double], and we want the result of the whole function (\abort -> Just <$> mapM (invert abort) xs) to have the type Cont r (Maybe [Double]). Thus, we need to fmap Just over the result of mapM (invert abort) xs, which we do using the infix version of fmap, (<$>). invert simply returns 1 / x if x is not zero. Otherwise, it calls abort Nothing. This makes callCC return immediately, with Nothing as its result. Thus, inv returns Nothing if there's as 0 in xs, and Just the list of inverses of the elements of xs otherwise.

This works as expected:

GHCi
>>> :l Inverses.hs
[1 of 1] Compiling Main             ( Inverses.hs, interpreted )
Ok, one module loaded.
>>> inverses [1,2,3,4,5]
Just [1.0,0.5,0.3333333333333333,0.25,0.2]
>>> inverses [1,2,0,4,5]
Nothing

We could have achieved the result above much more succinctly using the implementation

inverses :: [Double] -> Maybe [Double]
inverses = mapM invert
  where
    invert 0 = Nothing
    invert x = Just (1 / x)

which uses the fact that Maybe is itself a monad. This implements the same abortion logic as our implementation above. As soon as invert encounters a 0 and returns Nothing, the entire computation turns to Nothing, and the remaining list elements aren't inspected at all anymore. Unfortunately, as we discussed before, this version using the Maybe monad is less efficient than the version using the Cont r monad, as this Nothing traverses a sequence of (>>=) operators whose length corresponds to the number of non-zero elements before the first 0. Calling abort when using the Cont r monad does not do that.