What's a functor on the category of monads?
Asked Answered
K

1

8

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?
Kareykari answered 14/9, 2020 at 9:55 Comment(3)
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 StringAmadus
Since a monad is a functor, the arrows would be natural transformations.Albania
For some of those subquestions, you might find it useful to have a look at the Control.Monad.Morph docs.Immure
I
5
  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.

Inconsonant answered 14/9, 2020 at 23:17 Comment(3)
So it's actually just the functor category, restricted to those functors that are monads? I suspected this, but wasn't sure.Diocesan
@leftaroundabout: That seems to be the way folks generally use the term, yeah. I could certainly imagine more exotic things by that name.Inconsonant
If a monad transformer is not functorial then the monad cannot be used at an arbitrary place in the monad stack because you can't lift its operations into the stack. You are then restricted to putting that monad at the deep end of the stack. Witness how all monad stacks involving IO always put IO at the deep end of the stack; this is because IO doesn't actually have a transformer. The same restriction applies to Cont, Select, and the codensity monads, because their transformers don't have hoist and are not functorial.Judaica

© 2022 - 2025 — McMap. All rights reserved.