我正在讨论Monad Transformers,我理解他们的主要作用是提供一个monadic容器来容纳不同类型的monad,它提供了一个通用接口,可以在计算中操作'嵌套'monad。
我试图实现自己的变压器:
$random = mt_rand (0,1);
$randomClass = '';
if($random == 0){
$randomClass = 'class1';
} else {
$randomClass = 'class2';
}
$someVariable = '<div class="someClass '.$randomClass.'" style="someStyle" ...>...</div>';
通过this论文,我知道这是不正确的。他们的例子显示:
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)
这会将行动class MonadTrans r where
lift :: Monad m => m a -> (r m) a
嵌套在monad变换器a
中。
我不明白使用monad变换器的如何有助于在计算中处理多个monadic类型?任何人都可以提供简单的解释和示例吗?
答案 0 :(得分:3)
Monads是实现monad操作m a
和return
的任何多态数据类型>>=
,并遵守monad定律。
有些monad有一个特殊形式,因为m
可以写成多态mT m'
,只要参数m'
是monad,它就是monad。我们可以像这样拆分的Monads是monad变换器。外部monad mT
为内部monad添加了monadic效果。我们可以嵌套无限量的monad,因为内部m'
本身可能是monad变换器。
由于Maybe是最简单的monad之一,我会从Transformers重新发布代码。
定义显示monad MaybeT m
主要是monad m
的包装器。但是,m
不再是纯粹的&#34; 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)
查看绑定操作>>=
,重要的是要注意$
之后的符号发生在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
1}}
对于我的例子,下面是一些用户的程序的模型
要求一些资源。函数userGetResource
询问用户的名称,然后根据某个注册表查询该名称,如果找到该名称,它将尝试获得该用户的权限,如果为该用户提供了权限将返回资源。有一系列IO操作可能会因Nothing
而失败。 MaybeT有助于编写函数,以便阅读和维护更友好。请特别注意lift
函数中userGetResource
的使用。因为它总会返回一个字符串(baring灾难),所以这个函数被提升为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
失败,&#34; Sam&#34;不在userReg
*MaybeTrans> userGetResource
Who are you
Sam
Nothing
成功,&#34;爱丽丝&#34;在注册表中并获得许可。
*MaybeTrans> userGetResource
Who are you
Alice
Just Resource
失败。 &#34;鲍勃&#34;是在注册表中,但没有权限。
*MaybeTrans> userGetResource
Who are you
Bob
Nothing
答案 1 :(得分:3)
我发现了解这里发挥作用的种类很有帮助。
首先,如您所知, monad 是类型构造函数 m :: * -> *
,与两个操作return :: a -> m a
和(>>=) :: m a -> (a -> m b) -> m b
配对
class Monad (m :: * -> *) where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
monad变形金刚的想法是,它们是一种类型级别的函数,可以将monad转换为另一个monad。因此,假设monad是单参数类型构造函数* -> *
,monad转换器必须是类型(* -> *) -> (* -> *)
,或者(* -> *) -> * -> *
一旦删除括号。 monad变换器是双参数类型,其第一个参数是monad,第二个参数是一个值。
更具体地说,monad变换器是t :: (* -> *) -> * -> *
类型,因此每当m
是monad t m
时,它也是monad。我们还要求t m
是更大的 monad而不是m
,因为m
中的任何操作都可以嵌入t m
。< / p>
class MonadTrans t where
transform :: Monad m :- Monad (t m)
lift :: Monad m => m a -> t m a
我在transform
的定义中使用the "entailment" operator :-
中的Kmett's constraints
package; transform
证明m
是Monad
意味着t m
是Monad
。 (The version of MonadTrans
in transformers
省略了transform
成员,因为在撰写时,GHC并不支持:-
运算符。)
重要的是,t m a
(又名(t m) a
)表示与t (m a)
不同的内容。前者是应用于t
和m
的双参数类型a
。后者是应用于t
的单参数类型m a
。
非常简单 - 因为我在手机上 - 例如monad变压器的定义:
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
请注意IdentityT
是一个双参数数据类型;第一个参数m :: * -> *
是monad,第二个参数a :: *
是常规类型。
ghci> :k IdentityT
IdentityT :: (* -> *) -> * -> *
答案 2 :(得分:1)
不同的monad会产生不同的效果&#34;如国家或非确定性。但是如果你想在monad中想要更多这些,你需要从头开始实现这样的monad(这将是乏味的)或以某种方式堆叠monad。变形金刚可以叠加各种单子的效果。变形金刚通常是通过一个monad来获得的,它提供你感兴趣的效果,提取效果,同时可以插入另一个monad。它是一种其他monad的装饰,可以为它们添加所需的状态/非det / ...效果。查看包transformers
的hackage,看看常见的examples。
Transformer捕获相关monad的效果,但为您放置另一个monad的空间,如>>=
的bind(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'
do
块&#34;运行&#34;在底层monad(m
中,这些行通过>>=
的绑定运算符m
链接,因此&#34;做自己的事情&#34;,而{{1保持一边的状态。
通过这种方式,您可以叠加更多效果,每个变换器都可以处理一个,并获得具有所有这些效果的monad。
StateT
在这里你表达了&#34;抛出异常的影响&#34;在一个有状态的&#34;和&#34;非确定性的&#34;计算在&#34; realworld&#34;环境由ExceptT e (StateT s (ListT IO))
提供。请以此为例,IO
本身是有状态的,所以我不否认这个例子有点过分。
只需注意:具体的monad通常可以表示为最简单的monad IO
的转换,它本身没什么有趣的,就像Identity
以State s
一样实现