Why are monads not closed under composition?

I think this is easiest to understand by looking at the join operator:

join :: Monad m => m (m a) -> m a

join is an alternative to >>= for defining a Monad, and is a little easier to reason about. (But now you have an exercise to do: show how to implement >>= from join, and how to implement join from >>=!)

Let's try to make a join operation for Composed f g and see what goes wrong. Our input is essentially a value of type f (g (f (g a))), and we want to produce a value of type f (g a). We also know that we have join for f and g individually, so if we could get a value of type f (f (g (g a))), then we could hit it with fmap join . join to get the f (g a) we wanted.

Now, f (f (g (g a))) isn't so far from f (g (f (g a))). All we really need is a function like this: distribute :: g (f a) -> f (g a). Then we could implement join like this:

join = Compose . fmap join . join . fmap (distribute . fmap getCompose) . getCompose

Note: there are some laws that we would want distribute to satisfy, in order to make sure that the join we get here is lawful.


Ok, so that shows how we can compose two monads if we have a distributive law distribute :: (Monad f, Monad g) => g (f a) -> f (g a). Now, it could be true that every pair of monads has a distributive law. Maybe we just have to think really hard about how to write one down?

Unfortunately there are pairs of monads that don't have a distributive law. So we can answer your original question by producing two monads that definitely don't have a way of turning a g (f a) into an f (g a). These two monads will witness to the fact that monads don't compose in general.

I claim that g = IO and f = Maybe do not have a distributive law

-- Impossible!
distribute :: IO (Maybe a) -> Maybe (IO a)

Let's think about why such a thing should be impossible. The input to this function is an IO action that goes out into the real world and eventually produces Nothing or a Just x. The output of this function is either Nothing, or Just an IO action that, when run, eventually produces x. To produce the Maybe (IO a), we would have to peek into the future and predict what the IO (Maybe a) action is going to do!


In summary:

  • Monads can compose if there is a distributive law g (f a) -> f (g a). (but see the addendum below)
  • There are some monads that don't have such a distributive law.
  • Some monads can compose with each other, but not every pair of monads can compose.

Addendum: "if", but what about "only if"? If all three of F, G, and FG are monads, then you can construct a natural transformation δ : ∀X. GFX -> FGX as the composition of GFη_X : GFX -> GFGX followed by η_{GFGX} : GFGX -> FGFGX and then by μ_X : FGFGX -> FGX. In Haskellese (with explicit type applications for clarity), that would be

delta :: forall f g x. (Monad f, Monad g, Monad (Compose f g))
      => g (f x) -> f (g x)
delta = join' . pure @f . fmap @g (fmap @f (pure @g)) 
  where
    -- join for (f . g), via the `Monad (Compose f g)` instance
    join' :: f (g (f (g x))) -> f (g x)
    join' = getCompose . join @(Compose f g) . fmap Compose . Compose

So if the composition FG is a monad, then you can get a natural transformation with the right shape to be a distributive law. However, there are some extra constraints that fall out of making sure your distributive law satisfies the correct properties, vaguely alluded to above. As always, the n-Category Cafe has the gory details.