4
votes

I'm going over Monad Transformers and I understand their main role is to provide a monadic container to hold monads, of different types, which provides a common interface from which to manipulate the 'nested' monads within a computation.

I've attempted to implement my own transformer:

data CustomTransformer a = CustomTransformer

class TransformerClass m a where
  lift :: m a -> CustomTransformer (m a)

instance TransformerClass Maybe a where
 lift (Just a) = CustomerTransformer (Just a)

Going through this paper, I understand that this is incorrect. Their example shows:

class MonadTrans r where
  lift :: Monad m => m a -> (r m) a

This nests the action a in the monad transformer r m.

I don't understand how using a monad transformer aids in dealing with multiple monadic types within a computation? Can anyone provide a simple explanation and example?

3

3 Answers

4
votes

I find it helpful to understand the kinds that are at play here.

Firstly, as you know, a monad is a type constructor m :: * -> * paired with two operations, return :: a -> m a and (>>=) :: m a -> (a -> m b) -> m b.

class Monad (m :: * -> *) where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b

The idea of monad transformers is that they're a sort of type-level function that turns a monad into another monad. So given that a monad is a single-parameter type constructor * -> *, a monad transformer must be a type of kind (* -> *) -> (* -> *), or (* -> *) -> * -> * once you remove the parentheses. A monad transformer is a two-parameter type, whose first parameter is a monad and whose second parameter is a value.

More concretely, a monad transformer is a type t :: (* -> *) -> * -> *, such that whenever m is a monad t m is also a monad. We also require that t m be a larger monad than m, in the sense that any action in m can be embedded in t m.

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

I'm using the "entailment" operator :- from Kmett's constraints package in the definition of transform; transform is a proof that m being a Monad implies that t m is a Monad. (The version of MonadTrans in transformers omits the transform member because when it was written GHC didn't support the :- operator.)

Importantly, t m a (aka (t m) a) means something different than t (m a). The former is a two-parameter type t applied to m and a. The latter is a one-parameter type t applied to m a.

A very simple - because I'm on my phone - example of what a definition of a monad transformer looks like:

newtype IdentityT m a = IdentityT { runIdentityT :: m a }

instance Monad m => Monad (IdentityT m) where
    return = IdentityT . return
    IdentityT m >>= f = IdentityT $ m >>= (runIdentityT . f)

instance MonadTrans IdentityT where
    transform = Sub Dict
    lift = IdentityT

Note that IdentityT is a two-parameter datatype; the first parameter m :: * -> * is a monad and the second parameter a :: * is a regular type.

ghci> :k IdentityT
IdentityT :: (* -> *) -> * -> *
3
votes

Monads, are any of the polymorphic data types m a, that implement the monad operations, return and >>=, and obey the monad laws.

Some monads have a special form, in that m can be written as a polymorphic mT m', and will be a monad as long as the parameter m' is a monad. Monads that we can split like this are monad transformers. The outer monad mT, adds a monadic effect to the inner monad. We can nest unlimited amounts of monads, since the inner m', may itself be a monad transformer.

Since Maybe is one of the simplest monads, I'll repost code from Transformers.

The definition shows that the monad, MaybeT m, is mostly a wrapper around the monad m. However, m is no longer a "pure" monad, but with a type parameter tainted by the Maybe effect.

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

Now the definition of the Monad instance.

instance (Monad m) => Monad (MaybeT m) where
    return = lift . return
    x >>= f = MaybeT $ do
        v <- runMaybeT x
        case v of
            Nothing -> return Nothing
            Just y  -> runMaybeT (f y)

Looking at the bind operation >>=, it's important to note that the do notation following the $ takes place in the m monad. The inner monad is recovered via runMaybeT x, and the monadic value is bound to v, triggering the m effect. Then, the Maybe state is evaluated, f is applied to a value (if existing) and wrapped appropriately.

One source of confusion for me is the terminology for inner and outer monad--and I wouldn't be surprised if I had it backwards. The transformed monad mT is actually projecting itself inside the inner monad, m.

The question asks about lift, which corresponds to the ability to run the inner monad in the pure context of the outer monad. Note that lift or MonadTrans doesn't define the transformer, but says that if a monad (t m) is a transformer, then you should be able to lift m a into a pure (t m) a

For my example, the below is a mockup of some program where users ask for some resource. The function, userGetResource asks for the user's name, then queries that name against some registry, if the name is found, it will attempt to get permission for the user, if permission is given for the user it will return the resource. There are a series of IO actions which may fail with Nothing. MaybeT helps write the function so that it is a little more friendly to read and maintain. Note especially the use of lift in the userGetResource function. Since it will always return a string (baring catastrophe), this function is lifted into the pure Just form of MaybeT.

import Data.List(find)
import Control.Monad (liftM)
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Class(lift)

data User = User { userName :: String, hasCredentials :: Credentials }
type Credentials = Bool
type Token = ()
type UserReg = [User]
data Resource = Resource deriving Show

userGetResource :: IO (Maybe Resource)
userGetResource = runMaybeT $ do
  str <- lift $ do putStrLn "Who are you"
                   getLine
  usr <- MaybeT $ getUser str
  tok <- MaybeT $ getPermission usr
  MaybeT $ getResource tok


getResource :: Token -> IO (Maybe Resource)
getResource _ = return (Just Resource)

userRegistry :: IO UserReg
userRegistry = return [User "Alice" True, User "Bob" False]

lookupUser :: String -> UserReg -> Maybe User
lookupUser name = find ((name==) . userName)

getUser :: String -> IO (Maybe User)
getUser str = do
  reg <- userRegistry
  return $ lookupUser str reg

getPermission :: User -> IO (Maybe Token)
getPermission usr
  | hasCredentials usr = do
     tok <- generateToken
     return (Just tok)
  | otherwise          = return Nothing

generateToken :: IO Token
generateToken = doSomeUsefulIO
  where
    doSomeUsefulIO = return () 

And here is the output of several calls to userGetResource

Fails, "Sam" not in userReg

*MaybeTrans> userGetResource
Who are you
Sam
Nothing

Succeeds, "Alice" in registry and has permission.

*MaybeTrans> userGetResource 
Who are you
Alice
Just Resource

Fails. "Bob" is in registry, but no permission.

*MaybeTrans> userGetResource
Who are you
Bob
Nothing
1
votes

Different monads give different "effects" such as state or nondeterminism. But if you want more of these in a monad you need to either implement such a monad from scratch (which would be tedious) or stack the monads somehow. Transformers are there to allow stacking the effects of various monads. Transformers are often obtained by generalisation from a monad that provides the effect you are interested in, extracting the effect while making it possible to plug another monad inside. It is sort of a decoration for other monads that adds your desired state/nondet/... effect to them. Look at hackage at the package transformers to see some common examples.

Transformer captures the effect of the associated monad but leaves room for you to put another monad in, as in the definition of bind (>>=) for StateT:

(>>=) :: StateT s m a -> (a -> StateT s m b) -> StateT s m b
m >>= k = StateT $ \ s -> do
    (a, s') <- runStateT m s
    runStateT (k a) s'

the do block "runs" in the underlying monad (m, the lines are chained via the bind operator >>= of m), and thus "does its own thing", while StateT maintains the state on the side.

This way you can stack more effects, each transformer taking care of one, and get a monad that has all these effects.

ExceptT e (StateT s (ListT IO))

Here you express the effects of "throwing exception" in a "stateful" and "nondeterministic" computation in the "realworld" environment provided by IO. Please take this just as an example, IO itself is stateful so I do not deny the example is a bit too much.

Just a note: concrete monads can then often be expressed as transformations of the simplest monad Identity which does nothing interesting on its own, just as State s is implemented in terms of StateT s Identity.