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