作者试图说任何两个单子的组合是不可能的。这不是因为语言不好,而是因为有些单子的组成不是单子。
例如,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 => 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,我们也会组合它们的效果,我们会自动得到这个结果。
例如:
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 以便解决这个问题变得容易和简单。这只是十亿个例子中的一个,单子的组合可以轻松解决您的问题。