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 :: [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:
>>> :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.