monad变换器的用法

zer*_*ing 3 haskell

我正在阅读Haskell书中关于monad变换器的内容.

作者提到了以下内容:

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

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

{-# 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
  (>>=) = ???
Run Code Online (Sandbox Code Playgroud)

这些是我们尝试组合的类型,因为并且必然都是具有自己的Monad实例的monad:

Monad f => f a -> (a -> f b) -> f b
Monad g => g a -> (a -> g b) -> g b
Run Code Online (Sandbox Code Playgroud)

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

(Monad f, Monad g) => f (g a) -> (a -> f (g b)) -> f (g b)
Run Code Online (Sandbox Code Playgroud)

或者换句话说:

(Monad f, Monad g) => f (g (f (g a))) -> f (g a)
Run Code Online (Sandbox Code Playgroud)

这是不可能的.加入决赛并没有好方法.试图让它发挥作用是一个很好的练习,因为你遇到的障碍本身就具有指导意义.您还可以阅读Mark P. Jones和Luc Duponcheel的Composing monads1,了解为什么它不可能.

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

fre*_*yle 7

作者试图说任何两个单子的构成都是不可能的.这不是因为语言不好,而是因为有monad的组成不是monad.

例如,IsntMonad不是monad:

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

instance Monad IsntMonad where
    m >>= k = ???
Run Code Online (Sandbox Code Playgroud)

但是,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
Run Code Online (Sandbox Code Playgroud)

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

因此,有monad可以与任何其他monad组成.在第一近似中的这种单子是单子变换器.

例如,monad Maybe可以与任何其他monad组合:Monad m => m (Maybe a)这样m (Maybe a)的monad也是如此.但是我们如何才能为它做monad实例呢?

instance Monad m => Monad ??? where ...
Run Code Online (Sandbox Code Playgroud)

然后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
Run Code Online (Sandbox Code Playgroud)

在那之后,自动MaybeT IO,MaybeT [],MaybeT STM...是单子.您不需要为它们编写实例.这是他们的好处.


我们知道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
...
Run Code Online (Sandbox Code Playgroud)

monad的组成不是可交换的.例如,MaybeT (State s)与此不一样StateT s Maybe.理解构图将产生什么效果的简单方法是记住效果是从内部monad到外部monad产生的.MaybeT (State s)第一个效果将由State smonad 产生然后由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);
...
Run Code Online (Sandbox Code Playgroud)

在Haskell中我们可以写:

thing <- flip execStateT defaultValue $ do
    when transformByOption0 $
        modify doTransformationByOption0
    when transformByOption1 $
        modify doTransformationByOption1
    when transformByOption2 $
        put =<< doMonadicTransformationByOption2 =<< get
    ...
Run Code Online (Sandbox Code Playgroud)

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