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.