monad变换器的用法

时间:2017-10-19 14:13:05

标签: haskell

我正在阅读Haskell书中的monad变形金刚。

提交人提到了以下内容:

  

Monad怎么样?组成两个任意数据类型没有问题   有Monad实例。当我们使用Compose时,我们已经看到了这一点   使用Maybe和list,它们都定义了Monad实例。然而,   这样做的结果并没有给你一个Monad。

     

问题归结为缺乏信息。这两种类型都是   使用是多态的,所以当你尝试编写绑定   Monad,你试图将两个多态绑定组合成一个   结合绑定。事实证明,这是不可能的:

{-# LANGUAGE InstanceSigs #-}
-- impossible.
instance (Monad f, Monad g) => Monad (Compose f g) where
  return = pure
  (>>=) :: Compose f g a
  -> (a -> Compose f g b)
  -> Compose f g b
  (>>=) = ???
     

这些是我们试图结合的类型,因为它们是   必然两个monad都有他们自己的Monad实例:

Monad f => f a -> (a -> f b) -> f b
Monad g => g a -> (a -> g b) -> g b
     

从那些,我们正在尝试编写这个绑定:

(Monad f, Monad g) => f (g a) -> (a -> f (g b)) -> f (g b)
     

或者以不同的方式表达:

(Monad f, Monad g) => f (g (f (g a))) -> f (g a)
     

这是不可能的。加入决赛并不是一个好方法   和。尝试使其工作是一个很好的练习,因为   你遇到的障碍本身就具有指导意义。您可以   还阅读了Mark P. Jones和Luc Duponcheel撰写的“组合monads1”   为什么不可能。

我无法理解。他什么意思?究竟是什么是monad变压器,它有什么用呢?

2 个答案:

答案 0 :(得分:6)

作者试图说任何两个monad的构成都是不可能的。这不是因为语言不好,而是因为有些monad的成分不是monad。

例如,IsntMonad不是monad:

newtype IsntMonad a = IsntMonad (Maybe (IO a))

instance Monad IsntMonad where
    m >>= k = ???

但是,IsMonad是一个monad:

newtype IsMonad a = IsMonad { runIsMonad :: IO (Maybe a) }

instance Monad IsMonad where
    (IsMonad ioma) >>= k = IsMonad $ do
        ma <- ioma
        case ma of
          Just a -> runIsMonad $ k a
          Nothing -> return Nothing

因此,monad变形金刚只是一种提高构图可能性的方法。但是,如果我们知道在一般情况下它是不可能的,它是如何工作的?是的,任何两个monad都不可能,但对于一些具体的monad和任何其他monad来说都是可能的。

所以,有些monad可以与任何其他monad组合。第一个近似值中的这些单子是单子变换器。

例如,monad Maybe可以与任何其他monad组合:Monad m => m (Maybe a),因此m (Maybe a)是monad。但是我们怎么能为它做monad的实例呢?

instance Monad m => Monad ??? where ...

然后MaybeT显示为帮助器,它被称为monad变换器(后缀为T提示)。

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

instance Monad m => Monad (MaybeT m) where
    m >>= k = MaybeT $ do
        ma <- runMaybeT m
        case ma of
          Just a -> runMaybeT $ k a
          Nothing -> return Nothing

之后,自动MaybeT IOMaybeT []MaybeT STM ...是monad。您不需要为它们编写实例。这是他们的好处。

我们明白monad变形金刚只是monad组成的方式。但它不是一个答案:monad的组成是什么?为什么我们花时间和精力去寻找组合monad的方法?

嗯,正如你所知,任何与某种效果有关的单子。如果我们组成两个monad,我们也会组合它们的效果,这个结果我们将自动获得。

例如:

StateT s Maybe a -- a computation with a state `s` which can fail
ReaderT e Maybe a -- a computation with access to an environment `e` and option to fail
ReaderT e (StateT s Maybe) a -- a computation with state `s`, access to an environment `e` and option to fail
...

monads的组成不是可交换的。例如,MaybeT (State s)StateT s Maybe不同。理解构图将产生什么效果的简单方法是记住效果是从内部monad到外部monad产生的。 MaybeT (State s)第一个效果将由State s monad然后Maybe生成。但在StateT s Maybe中,第一个效果将由Maybe然后由State s产生。这意味着在StateT s Maybe失败的情况下,我们会丢失状态,但在MaybeT (State s)中我们会保存错误发生的状态。

因此,monad的组合是一种在类型级别上构建更复杂的monad的简单方法。

如何在实践中应用?例如,让我们的图像情况。你有一些东西和选择来改变它。命令式代码可能如下所示:

thing = defaultValue;
if (transformByOption0)
    doTransformationByOption0(thing);
if (transformByOption1)
    doTransformationByOption1(thing);
...

在Haskell中我们可以写:

thing <- flip execStateT defaultValue $ do
    when transformByOption0 $
        modify doTransformationByOption0
    when transformByOption1 $
        modify doTransformationByOption1
    when transformByOption2 $
        put =<< doMonadicTransformationByOption2 =<< get
    ...

这里发生了什么?我在本地包装了一些monad(可能是任何一个)monad State MyThing,因此解决这个问题简单易行。它只是十亿个例子中的一个,monad的组合可以很容易地解决你的问题。

答案 1 :(得分:0)

monad变换器是一个monad,它允许自身和其他monad之间的效果组合。

Reader的效果将是函数ask

在其上下文中可访问的环境

State的效果是get可以访问的状态,put可以修改。

monad变换器StateT a m s将是一个monad变换器,能够携带它的状态,并且还提供monad m的一些效果。

monad变换器堆栈的实例可以是: StateT Int (Reader String) Int,表示使用类型为String的Reader环境进行计算,类型为Int的状态,返回类型为Int。如果Reader取而代之的是ReaderT,您可以将其与IO链接起来,并在同一函数中读取/写入一些文件或其他内容。

关于如何实现所有内容,有很多细节我在那里进行了调整,但这就是monad变换器的原因,以及为什么要使用它。