Even for monad transformers it's not necessarily the case that direct composition leads to a monad. For instance, ContT looks like
newtype ContT r m a = ContT ((a -> m r) -> m r)
so ContT M A is not the same as Cont (M A).
Monads also compose under products as two parallel monadic computations
data (f * g) a = Pair (f a) (g a)
p1 :: (f * g) a -> f a
p1 (Pair fa _) = fa
p2 :: (f * g) a -> g a
p2 (Pair _ ga) = ga
instance (Monad f, Monad g) => Monad (f * g) where
return a = Pair (return a) (return a)
Pair fa ga >>= k = Pair (fa >>= p1 . k) (ga >>= p2 . k)
You can also talk about composition under sums
data (f + g) a = Inl (f a) | Inr (g a)
and you'll get compositions of applicatives here so long as you have a notion of natural transformation
class Natural f g where
phi :: f a -> g a
instance (Applicative f, Applicative g, Natural f g) => Applicative (f + g) where
pure a = Inl (pure a)
Inl ff <*> Inl fa = Inl ( ff <*> fa)
Inr gf <*> Inr ga = Inr ( gf <*> ga)
Inl ff <*> Inr ga = Inr (phi ff <*> ga)
Inr gf <*> Inl fa = Inr ( gf <*> phi fa)
but there's no way to get a similar monad. It turns out that you have to "know what branch to go down before you start" in exactly the kind of way applicatives allow and monad preclude.
Monads also compose under products as two parallel monadic computations
You can also talk about composition under sums and you'll get compositions of applicatives here so long as you have a notion of natural transformation but there's no way to get a similar monad. It turns out that you have to "know what branch to go down before you start" in exactly the kind of way applicatives allow and monad preclude.