我正在阅读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变压器,它有什么用呢?
答案 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 IO
,MaybeT []
,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变换器的原因,以及为什么要使用它。