您已经发现了单子变换器为什么是“内部”的而不是“外部”的(注:我相信这里使用的术语不正确,但您知道我的意思-请随时在这里纠正我)。将您知道的单子放在数据位置(内部)而不是容器位置(外部)要容易得多(易=约束较少)。直观地说,您可以看到为什么-毕竟,单子的本质要求
容器必须相同。您可以对数据进行任何操作,但在绑定期间不得更改容器的类型。
当然,通过实际尝试实现荒谬的事情更容易实现所有这些。这就是这个问题所涉及的内容。在为MaybeOT实现>>=/bind时,您将被卡住的最后一步是这个-
m a >>= ??? . runMaybeOT . f
在这里,m
是一个 Monad
,m a :: m a
,f :: a -> MaybeOT m a
,以及 runMaybeOT :: MaybeOT m a -> Maybe (m a)
(还有 ??? :: ???
,呵呵)
现在,您必须使用容器类型 m
来获取一个具有绑定到 m a
成功的单子。但遗憾的是,您无法从 Maybe (m a)
中获取 m a
!当它为 Nothing
时会发生什么?实现单子变换器的主要工具是关于您正在为其实现变换器的 特定单子 的知识。您的知识告诉您这是荒谬的。
相比之下,实现 MaybeT
的 >>=
的最后一步非常顺利。为了完整起见,这里是 MaybeT
的-
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
在这里,你将绑定类型为
m (Maybe a)
。在绑定期间,你必须返回类型为
m b
的任何
b
。你有一个类型为
a -> MaybeT m b
的函数
f
,你可以轻松从
MaybeT m b
获取
m (Maybe b)
。
啊哈!现在你可以看到解决方案了。你获得的函数
返回与你要绑定的相同的外部 monad 。这正是你需要的。在
MaybeOT
的情况下,你被卡在了绑定在monad
m
上,而函数没有返回值与外面的monad
m
!
这就是关键所在-你获得的函数必须能够给你一个具有相同外部 monad 的值。这就是为什么
MaybeT
(以及其他变换器)将未知的 monad 保持在外部-因为当你实现
>>=
用于
MaybeT
时,你知道绑定函数将需要构造那个外部 monad。
至少从直觉上来说,需要注意的是你在这里卡住的问题——即实现单子组合时所面临的问题——与嵌套单子的
>>=
相同。
单子不可组合!这就是为什么你需要单子转换器的原因!
如果你将这个问题分解开来,你会注意到,如果你只有一个可以
交换单子的函数,从
m (Maybe a)
得到
Maybe (m a)
,反之亦然——一切都会好起来。单子将组合,单子转换器可以看起来任何你喜欢的样子,事实上,单子转换器实际上没有任何用处。这个细节在
上面链接的答案中已经注意到了。你所需要的只是——
swap :: (Monad m, Monad n) => m (n a) -> n (m a)
这里是英文文本,大意为:
This exists。它们被称为可遍历的单子,如果你仅仅将
Traversable
约束添加到
MaybeOT
的
Monad
实现中,你就能得到很好的效果。
instance (Traversable m, Monad m) => Monad (MaybeOT m) where
return = pure
MaybeOT Nothing >>= _ = MaybeOT Nothing
MaybeOT (Just ma) >>= f = MaybeOT $ sequence $ ma >>= sequence . runMaybeOT . f
我认为它符合法律,但我需要检查一下。
另外,将MaybeOT
变成符合法律的应用程序是完全可能的。毕竟,在实现Monad
时面临的问题在这里根本不存在。应用程序确实可以组合。
instance Applicative f => Applicative (MaybeT f) where
pure = MaybeT . pure . Just
MaybeT f <*> MaybeT a = MaybeT $ liftA2 (<*>) f a
(或者,更简单地说)
instance Applicative f => Applicative (MaybeT f) where
pure = MaybeT . pure . Just
MaybeT f <*> MaybeT a = MaybeT $ (<*>) <$> f <*> a
根据我所知,应该符合法律规定。
where inner ma = ...
而不是let
? - Will Nessm a
执行bind
/>>=
操作。然而,你得到的是返回类型为MaybeOT (m b)
的函数。当然,对于任何b
,m a
上的bind
必须给出返回m b
的函数。你可以从MaybeOT (m b)
中获取Maybe (m a)
。但接下来呢?外部 monad 的信息是你所拥有的全部信息,也是你实现 monadic composition(transformers)所需的信息——然而,在这一步中,你需要将其丢弃。 - ChaseMaybeT
,其中Maybe
是内层单子。你只需一次在m (Maybe a)
上进行绑定。在这里,你有机会检查内部的Maybe
是否为Nothing
。在这种情况下,pure
帮助你构造外层单子。此外,给定的函数返回MaybeT m b
。你可以从中获得m (Maybe b)
- 这正是你成功在m (Maybe a)
上进行bind
所需要的。因此,它很好地工作了。你已经使用关于Maybe
的信息以及它在实现中的具体表示,而不必将其丢弃。 - ChaseMaybeOT
时面临的主要问题是你被困在了Maybe (m a)
,而你需要的是m (Maybe a)
。这也是你在实现任意嵌套单子的bind
时面临的主要问题。因此,需要使用单子变换器。有趣的是,可遍历的 单子具有交换外部和内部上下文的能力 - 因此使它们能够绕过所有这些问题。 - Chase