Monad,是任何多态数据类型m a,它实现了 monad 操作,return 和 >>=,并遵守 monad 法则。
有些 monad 有一种特殊的形式,m 可以写成多态的 mT m',只要参数 m' 是一个 monad,它就会是一个 monad。我们可以像这样拆分的 Monad 是 monad 转换器。外部 monad mT,为内部 monad 添加了 monadic 效果。我们可以嵌套无限数量的 monad,因为内部的 m' 本身可能是一个 monad 转换器。
由于 Maybe 是最简单的 monad 之一,我将重新发布来自 Transformers 的代码。
定义表明 monad MaybeT m 主要是 monad m 的包装器。然而,m 不再是一个“纯粹的”monad,而是带有一个被 Maybe 效应污染的类型参数。
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
现在是 Monad 实例的定义。
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)
查看绑定操作>>=,重要的是要注意$ 后面的do 符号发生在m monad 中。内部monad通过runMaybeT x回收,monadic值绑定到v,触发m效果。然后,评估Maybe 状态,将f 应用于一个值(如果存在)并适当地包装。
对我来说,混淆的一个来源是内部和外部 monad 的术语——如果我把它倒过来,我不会感到惊讶。转换后的 monad mT 实际上将自己投射到内部 monad m。
问题是关于lift,它对应于在外部monad 的纯上下文中运行内部monad 的能力。请注意,lift 或 MonadTrans 没有定义转换器,但表示如果 monad (t m) 是转换器,那么您应该能够将 m a 提升为纯 (t m) a
对于我的示例,下面是一些程序的模型,其中用户
请求一些资源。函数userGetResource 询问用户名,然后针对某个注册表查询该名称,如果找到该名称,它将尝试获取用户的权限,如果授予用户权限,它将返回资源。有一系列 IO 操作可能会因Nothing 而失败。 MaybeT 帮助编写函数,使其更易于阅读和维护。特别注意lift 在userGetResource 函数中的使用。因为它总是会返回一个字符串(baring catastrophe),所以这个函数被提升为MaybeT的纯Just形式。
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 ()
这是对userGetResource的多次调用的输出
失败,“Sam”不在 userReg 中
*MaybeTrans> userGetResource
Who are you
Sam
Nothing
成功,“爱丽丝”在注册表中并获得许可。
*MaybeTrans> userGetResource
Who are you
Alice
Just Resource
失败。 “Bob”在注册表中,但没有权限。
*MaybeTrans> userGetResource
Who are you
Bob
Nothing