monad变压器的用途和例子?

时间:2017-02-13 09:38:35

标签: haskell monads

我正在讨论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类型?任何人都可以提供简单的解释和示例吗?

3 个答案:

答案 0 :(得分:3)

Monads是实现monad操作m areturn的任何多态数据类型>>=,并遵守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证明mMonad意味着t mMonad。 (The version of MonadTrans in transformers省略了transform成员,因为在撰写时,GHC并不支持:-运算符。)

重要的是,t m a(又名(t m) a)表示与t (m a)不同的内容。前者是应用于tm的双参数类型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的转换,它本身没什么有趣的,就像IdentityState s一样实现