If I have two monads m
and n
, and n
is traversable, do I necessarily have a composite m
-over-n
monad?
More formally, here's what I have in mind:
import Control.Monad
import Data.Functor.Compose
prebind :: (Monad m, Monad n) =>
m (n a) -> (a -> m (n b)) -> m (n (m (n b)))
mnx `prebind` f = do nx <- mnx
return $ do x <- nx
return $ f x
instance (Monad m, Monad n, Traversable n) => Monad (Compose m n) where
return = Compose . return . return
Compose mnmnx >>= f = Compose $ do nmnx <- mnmnx `prebind` (getCompose . f)
nnx <- sequence nmnx
return $ join nnx
Naturally, this type-checks, and I believe works for a few cases that I checked (Reader over List, State over List) -- as in, the composed 'monad' satisfies the monad laws -- but I'm unsure if this is a general recipe for layering any monad over a traversable one.
No, it's not always a monad. You need extra compatibility conditions relating the monad operations of the two monads and the distributive law
sequence :: n (m a) -> m (n a)
, as described for example on Wikipedia.Your previous question gives an example in which the compatibility conditions are not met, namely
S =
m = []
, with unit X -> SX sending x to [x];T =
n = (->) Bool
, or equivalently TX = X × X, with unit X -> TX sending x to (x,x).The bottom right diagram on the Wikipedia page does not commute, since the composition S -> TS -> ST sends
xs :: [a]
to(xs,xs)
and then to the Cartesian product of all pairs drawn fromxs
; while the right-hand map S -> ST sendsxs
to the "diagonal" consisting of only the pairs(x,x)
forx
inxs
. It is the same problem that caused your proposed monad to not satisfy one of the unit laws.A few additional remarks, to make the connection between Reid Barton's general answer and your concrete question more explicit.
In this case, it really pays off to work out your
Monad
instance in terms ofjoin
:By reintroducing
compose
/getCompose
in the appropriate places and usingm >>= f = join (fmap f m)
, you can verify that this is indeed equivalent to your definition (note that yourprebind
amounts to thefmap f
in that equation).This definition makes it comfortable to verify the laws with diagrams1. Here is one for
join . return = id
i.e.(fmap join . join . fmap sequence) . (return . return) = id
:The overall rectangle is the monad law:
Ignoring the parts that are necessarily the same both ways across the squares, we see that the two rightmost squares amount to the same law. (It is of course a little silly to call these "squares" and "rectangles", given all the
id
sides they have, but it fits better my limited ASCII art skills.) The first square, though, amounts tosequence . return = fmap return
, which is the lower right diagram in the Wikipedia page Reid Barton mentions...... and it is not a given that that holds, as Reid Barton's answer shows.
If we apply the same strategy to the
join . fmap return = id
law, the upper right diagram,sequence . fmap return = return
, shows up -- that, however, is not a problem in and of itself, as that is just (an immediate consequence of) the identity law ofTraversable
. Finally, doing the same thing with thejoin . fmap join = join . join
law makes the other two diagrams --sequence . fmap join = join . fmap sequence . sequence
andsequence . join = fmap join . sequence . fmap sequence
-- spring forth.Footnotes:
r
isreturn
,s
issequence
andj
isjoin
. The upper case letters and numbers after the function abbreviations disambiguate the involved monad and the position its introduced or changed layer ends up at -- in the case ofs
, that refers to what is initially the inner layer, as in this case we know that the outer layer is always aT
. The layers are numbered from bottom to top, starting from zero. Composition is indicated by writing the shorthand for the second function below the first one.