8
votes

Note, this question is not about "monoids in the category of endofunctors". Nor is it directly about Functors (a Monad is always a Functor, but this question is concerned mainly about monad transformers)


The docs on Haskell's SelectT monad transformer states that

SelectT is not a functor on the category of monads, and many operations cannot be lifted through it.

  1. What's the category of monads? What are the arrows in that category?
  2. Why are some monad transformers functors on the category of monads (MaybeT, RWST, etc), but some not (ContT, SelectT)?
  3. What good does it do, from a programming perspective, to be a functor on the category of monads? Why should I care as a consumer of the library?
1
I am not versed in category theory, but perhaps it means that SelectT monads aren't just "wrappers" for their base monad? It's easy to convert a IO String into a MaybeT IO String, but it's not possible to convert an IO String into a SelectT <whatever type> IO Stringuser253751
Since a monad is a functor, the arrows would be natural transformations.chepner
For some of those subquestions, you might find it useful to have a look at the Control.Monad.Morph docs.duplode

1 Answers

4
votes
  1. What's the category of monads? What are the arrows in that category?

The category where the objects are monads, i.e., types T of kind Type -> Type with Monad instances, and the arrows A -> B are natural transformations between their underlying functors, conventionally represented in Haskell by functions of type forall x. A x -> B x (although strictly speaking parametricity is a stronger condition than naturality).

There’s an implementation of this in the mmorph package.

The initial object in this category is Identity, since for any monad T there’s exactly one natural transformation forall x. Identity x -> T x. Dually, I think the final object is Const ().

  1. Why are some monad transformers functors on the category of monads (MaybeT, RWST, etc), but some not (ContT, SelectT)?

A functor in this category would need a lifted fmap:

fmap'
  :: forall m n. (Monad m, Monad n)
  => (forall x. m x -> n x) -> forall x. T m x -> T n x

And you can’t implement this in general for ContT and SelectT. I’m not sure precisely why, but it seems to depend on variance: we’re trying to implement a covariant functor, but ContT and SelectT are invariant in their underlying monads, e.g., m occurs both positively and negatively in the (a -> m r) -> m r inside a ContT r m a.

  1. What good does it do, from a programming perspective, to be a functor on the category of monads? Why should I care as a consumer of the library?

If you have a general way to “run” a monad m in a monad n, you can’t necessarily lift that into ContT or SelectT; you’re stuck with the more restricted mapping operations like these:

mapSelectT :: (m a -> m a) -> SelectT r m a -> SelectT r m a
mapContT   :: (m r -> m r) -> ContT   r m a -> ContT   r m a

Where the underlying monad and result type are fixed. So you can’t always freely hoist actions within a stack that uses these transformers.