I'm implementing a very simple poor mans concurrency structure with the following data type:
data C m a = Atomic (m (C m a)) | Done a
I'm creating an instance of monad for this:
instance Monad m => Monad (C m) where
(>>=) (Atomic m) f = Atomic $ (liftM (>>= f) m)
(>>=) (Done v) f = f v
return = Done
Q1. Am I right in saying Atomic $ (liftM (>>= f) m)
is creating a new Atomic
monad which contains the result of f
(* -> *
) applied to the value inside of m
?
Q2. Am I right in saying the superclass Monad m
is used here to enable the use of liftM
? If so, as this is an instance of the Monad
class, why can't this access liftM
directly?
Q1. It is creating Atomic
value. Monad is a mapping at type level. Since f
is the second argument of >>=
of C m a
we know its type
f :: Monad m => a -> C m b
hence
(>>= f) :: Monad m => C m a -> C m b
is f
extended to unwrap its argument, and
liftM (>>= f) :: (Monad m1, Monad m) => m1 (C m a) -> m1 (C m b)
simply lifts the transformation into m1
which in your setting is unified with m
. When you extract the value m
from Atomic
and pass it to liftM
you use the monad m
's bind (via liftM
) to extract the inner C m a
to be passed to f
. The second part of liftM
's definition re-wraps the result as a m (C m b)
which you wrap in Atomic
. So yes.
Q2. Yes. But it is a liftM
of the underlying monad m
. The liftM
for C m a
is defined in terms of the instance (its >>=
and return
). By using C m a
's liftM
(if you managed to define >>=
in terms of liftM
) you would get a cyclic definition. You need the constraint Monad m
to create the plumbing for m (C m a)
to travel through the >>=
and f
. In fact as pointed by Benjamin Hodgson, Monad m
is an unnecessarily strong constaint, Functor m
would suffice. The fact that C
is in fact Free
together with the relevant implementations give the most insight in the matter.