Search code examples
haskellmonadsmonad-transformers

Is the monad transformer of a monad unique in Haskell?


There have been a couple of questions (e.g. this and this) asking whether every monad in Haskell (other than IO) has a corresponding monad transformer. Now I would like to ask a complementary question. Does every monad have exactly one transformer (or none as in the case of IO) or can it have more than one transformer?

A counterexample would be two monad transformers that would produce monads behaving identically when applied to the identity monad would but would produce differently behaving monads when applied to some other monad. If the answer is that a monad can have more than one transformer I would like to have a Haskell example which is as simple as possible. These don't have to be actually useful transformers (though that would be interesting).

Some of the answers in the linked question seemed to suggest that a monad could have more than one transformer. However, I don't know much category theory beyond the basic definition of a category so I wasn't sure whether they are an answer to this question.


Solution

  • The identity monad has at least two monad transformers: the identity monad transformer and the codensity monad transformer.

    newtype IdentityT m a = IdentityT (m a)
    newtype Codensity m a = Codensity (forall r. (a -> m r) -> m r)
    

    Indeed, considering Codensity Identity, forall r. (a -> r) -> r is isomorphic to a.

    These monad transformers are quite different. One example is that "bracket" can be defined as a monadic action in Codensity:

    bracket :: Monad m => m () -> m () -> Codensity m ()
    bracket before after = Codensity (\k -> before *> k () *> after)
    

    whereas transposing that signature to IdentityT doesn't make much sense

    bracket :: Monad m => m () -> m () -> IdentityT m ()  -- cannot implement the same functionality
    

    Other examples can be found similarly from variants of the continuation/codensity monad, though I don't see a general scheme yet.

    The state monad corresponds to the state monad transformer and to the composition of Codensity and ReaderT:

    newtype StateT s m a = StateT (s -> m (s, a))
    newtype CStateT s m a = CStateT (Codensity (ReaderT s m) a)
    

    The list monad corresponds to at least three monad transformers, not including the wrong one:

    newtype ListT m a = ListT (m (Maybe (a, ListT m a)))  -- list-t
    newtype LogicT m a = LogicT (forall r. (a -> m r -> m r) -> m r -> m r)  -- logict
    newtype MContT m a = MContT (forall r. Monoid r => (a -> m r) -> m r))
    

    The first two can be found respectively in the packages list-t (also in an equivalent form in pipes), and logict.