【问题标题】:The usage of monad transformersmonad转换器的使用
【发布时间】:2017-10-20 11:15:03
【问题描述】:

我正在阅读 Haskell 书中有关 monad 转换器的内容。

作者提到如下:

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

问题归结为缺乏信息。两种类型的 Compose 都是 使用是多态的,所以当你尝试为 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 的 Composing monads1 以了解 为什么不可能。

我想不通。他什么意思?究竟什么是 monad 转换器,它有什么用处?

【问题讨论】:

    标签: haskell


    【解决方案1】:

    作者试图说任何两个单子的组合是不可能的。这不是因为语言不好,而是因为有些单子的组成不是单子。

    例如,IsntMonad 不是 monad:

    newtype IsntMonad a = IsntMonad (Maybe (IO a))
    
    instance Monad IsntMonad where
        m >>= k = ???
    

    但是,IsMonad 是一个单子:

    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 =&gt; 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,我们也会组合它们的效果,我们会自动得到这个结果。

    例如:

    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
    ...
    

    单子的组成不可交换。例如,MaybeT (State s)StateT s Maybe 不同。理解组合会产生什么效果的一种简单方法是记住效果是从内部单子到外部单子产生的。 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 以便解决这个问题变得容易和简单。这只是十亿个例子中的一个,单子的组合可以轻松解决您的问题。

    【讨论】:

    • 第一个例子IsntMonad,为什么不是monad?不能写instance Monad IsntMonad where (IsntMonad a) &gt;&gt;= k = ??? ?
    • 是的,但这不是完整的答案。当您尝试编写实例时,您会发现无法从 IO 中提取值,然后将其应用于 Kleisli k :: a -&gt; IsntMonad b 的箭头。
    • Kleisli 到底是什么?我认为它是像带有鱼&gt;=&gt; 运算符的函数组合的单子组合。
    • 查看(&gt;&gt;=)的签名 - Monad m =&gt; m a -&gt; (a -&gt; m b) -&gt; m b。第二个参数有时称为 Kleisli 箭头(它来自自然与任何 monad 相关联的 Kleisli 类别,其中组合为 (&gt;=&gt;))。这就是为什么第二个参数的变量被称为k(Kleisli 的第一个字母)。
    • 你提到这意味着在StateT s Maybe失败的情况下,我们会丢失状态,但是在MaybeT (State s)我们会保存发生错误的状态。你能请给我一个例子。
    【解决方案2】:

    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 转换器是什么,以及为什么要使用它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-27
      • 2021-03-04
      • 2013-05-01
      • 1970-01-01
      • 2018-02-06
      • 2011-06-22
      • 2013-12-19
      • 2017-08-04
      相关资源
      最近更新 更多