Maybe via Cont
The essence of the Maybe
monad was that we have a computation we can abort at
any time by returning Nothing
. The result of the whole computation is then also
Nothing
. This is captured in the following equivalent of the Maybe
monad:
type MaybeC r a = Cont (Maybe r) a
runAbortable :: MaybeC r r -> Maybe r
runAbortable f = runCont f Just
You may wonder why Maybe r
is part of the definition of MaybeC
. Didn't we
want to make this all work without Maybe
? Not exactly. We only said that we
didn't want to make Maybe
itself a monad. We want to use Cont
to simulate
the behaviour of the Maybe
monad.
The MaybeC
values corresponding to Nothing
and Just x
are
nothing :: MaybeC r a
nothing = Cont (\_ -> Nothing)
just :: a -> MaybeC r a
just x = Cont (\cc -> cc x)
Together with the (>>=)
operator of the Cont
monad (!), these two values
achieve exactly the same behaviour as plain old Nothing
and Just
together
with the (>>=)
operator of the Maybe
monad. In particular, just x >>= f
applies f
to x
, and nothing >>= f
just produces Nothing
without
evaluating f
.
Let's try it out. Here is how we implemented inverses
using the Maybe
monad:
inverses :: [Double] -> Maybe [Double]
inverses = mapM invert
where
invert 0 = Nothing
invert x = Just (1 / x)
>>> :{
| inverses = mapM invert
| where
| invert 0 = Nothing
| invert x = Just (1 / x)
| :}
>>> inverses [1,2,3,4,5]
Just [1.0,0.5,0.3333333333333333,0.25,0.2]
>>> inverses [1,3,0,4,5]
Nothing
Using the MaybeC
monad, we have
inverses :: [Double] -> Maybe [Double]
inverses xs = runAbortable (mapM invert xs)
where
invert 0 = nothing
invert x = just (1 / x)
>>> :{
| type MaybeC r a = Cont (Maybe r) a
|
| runAbortable :: MaybeC r r -> Maybe r
| runAbortable f = runCont f Just
| :}
>>> :{
| nothing :: MaybeC r a
| nothing = cont (\_ -> Nothing)
|
| just :: a -> MaybeC r a
| just x = cont (\cc -> cc x)
| :}
>>> :{
| inverses :: [Double] -> Maybe [Double]
| inverses xs = runAbortable (mapM invert xs)
| where
| invert 0 = nothing
| invert x = just (1 / x)
| :}
>>> inverses [1,2,3,4,5]
Just [1.0,0.5,0.3333333333333333,0.25,0.2]
>>> inverses [1,2,0,4,5]
Nothing
(Note the use of cont
instead of Cont
in the definitions of nothing
and
just
. That's because in the standard library, Cont r = ContT r Identity
,
that is, there is no Cont
type constructor. The standard library offers the
function cont
though, which has the type
cont :: ((a -> r) -> r) -> Cont r a
.)
So how does this work? If the definitions of nothing
and just
do what we
want, then we should have
nothing >>= f = nothing
just x >>= f = f x
Let's see whether that's true. To do this, we need to remember that
x >>= f = Cont $ \c -> runCont x (\a -> runCont (f a) c)
This gives
nothing >>= f
= Cont $ \c -> runCont nothing (\a -> runCont (f a) c) -- Definition of (>>=)
= Cont $ \c -> (\_ -> Nothing) (\a -> runCont (f a) c) -- Definition of nothing
= Cont $ \c -> Nothing -- Function application
= nothing -- Definition of nothing
and
just x >>= f
= Cont $ \c -> runCont (just x) (\a -> runCont (f a) c) -- Definition of (>>=)
= Cont $ \c -> (\cc -> cc x) (\a -> runCont (f a) c) -- Definition of just
= Cont $ \c -> (\a -> runCont (f a) c) x -- Function application
= Cont $ \c -> runCont (f x) c -- Function application
= Cont $ runCont (f x) -- eta-Reduction
= f x -- Cont . runCont = id
We have ourselves a Maybe
monad built entirely from Cont
.