Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't lift's return value constrained to be a monad?

Why isn't MonadTrans defined as

class MonadTrans t where
    lift :: (Monad m, Monad (t m)) => m a -> t m a
--                    ^^^^^^^^^^^

instead of current

class MonadTrans t where
    lift :: Monad m => m a -> t m a

This is Haskell 98 (unlike the suggestion in Why aren't monad transformers constrained to yield monads?) and ensures that the result will always be a monad. Is there a reason why monad transformers are allowed to produce something that isn't a monad?

like image 996
Petr Avatar asked Aug 28 '13 17:08

Petr


2 Answers

bheklilr's answer gave me idea of an example where a monad transformer produces something that isn't a monad. A well-known example of something that isn't a monad is ZipList. And we can make a variant that runs a monadic action at each level:

import Control.Applicative
import Control.Arrow ((***))
import Control.Monad
import Control.Monad.Trans

-- | A list where each step is produced by a monadic action.
data ListT m a = Nil | Cons (m (a, ListT m a))

This is actually a monad stream. And it can be easily made into a Functor and an Applicative

instance Monad m => Functor (ListT m) where
    fmap f Nil      = Nil
    fmap f (Cons k) = Cons $ (f *** fmap f) `liftM` k
instance Monad m => Applicative (ListT m) where
    pure x = Cons $ return (x, pure x)
    Cons mf <*> Cons mx = Cons $ do
        (f, fs) <- mf
        (x, xs) <- mx
        return (f x, fs <*> xs)
    _       <*> _       = Nil

but obviously not a monad. So we have a MonadTrans instance that converts a monad into something that's only an Applicative.

instance MonadTrans ListT where
    lift mx = Cons $ (\x -> (x, lift mx)) `liftM` mx

(This whole thing made me realize that an experimental ZipSink in conduit-extra is also a nice such example.)


However, this raises another question: If we want such transformers, what laws should they adhere to? The laws for MonadTrans are defined as

lift . return = return
lift (m >>= f) = lift m >>= (lift . f)

So in our case we could wish for something like

lift (f `liftM` x)  = fmap f (lift x)

lift . return       = pure
lift (m `ap` f)     = lift m <*> lift f
like image 74
Petr Avatar answered Oct 28 '22 05:10

Petr


My guess is that a MonadTrans transforms a Monad into something else, instead of transforming a Monad into a Monad. It's more generalized, since you might write something that transforms a Monad and you can define lift, but you can't define >>= and return. Since most (if not all) MonadTrans instances end up being Monads, it doesn't really present a problem as the compiler still handles it just fine.

like image 32
bheklilr Avatar answered Oct 28 '22 04:10

bheklilr