2

假设我有组成两个 monad 动作的函数:

co :: Monad m => m a -> m a -> m a

您可以将其co视为一个高阶函数,它描述了两个单子动作如何相互合作以完成一项任务。

但现在我发现第一个 monad 动作可能被包裹在一个 monad 转换器中,而第二个不是:

one :: (MonadTrans t, Monad m) => t m a

two :: Monad m => m a

但仍想将它们组合在一起,所以我需要一个函数:

co' :: (MonadTrans t, Monad m) => t m a -> m a -> t m a

这样第一个就可以通过将所有原语提升到上下文中t m a来进行协作。m amt

co这里的诀窍是在不真正了解mor的实现的情况下进行构建t。我觉得答案在 MFunctor 包中的某个地方,实际上昨天问了一个类似的问题。但是想不出什么好东西,有什么想法吗?

4

3 回答 3

5

您可以使用包中 hoist的来执行此操作。允许您修改任何实现的基本 monad (这是大多数 monad 转换器):mmorphhoistMFunctor

hoist :: (MFunctor t) => (forall x . m x -> n x) -> t m r -> t n r

然后,您可以像这样使用它来解决您的问题:

co' tma ma = hoist (co ma) tma

然后你就完成了!

要理解为什么会这样,让我们​​逐步了解这些类型:

co :: (Monad m) => m a -> m a -> m a

ma :: (Monad m) => m a

co ma :: (Monad m) => m a -> m a

hoist (co ma) :: (Monad m, MFunctor t) => t m a -> t m a

tma :: (Monad m, MFunctor t) => t m a

hoist (co ma) tma :: (Monad m, MFunctor t) => t m a

请注意,hoist它必须满足某些法律,以确保它执行您期望的“正确的事情”:

hoist id = id

hoist (f . g) = hoist f . hoist g

这些只是函子定律,保证其hoist行为直观。

hoistControl.Monad.Morph在包的模块中提供mmorph,您可以在此处找到。主模块底部有教程教如何使用包

于 2013-08-22T20:17:45.570 回答
2

快速定义

您需要t m有一个 monad 实例才能使其工作。

import Control.Monad
import Control.Monad.Trans

co :: Monad m => m a -> m a -> m a
co = undefined

co' :: (MonadTrans t, Monad m, Monad (t m)) => t m a -> m a -> t m a
co' one two = lift . flip co two . return  =<< one

对问题的另一种看法

查看liftfrom transformerspackage 的定义应该可以帮助您找到答案,因为您想要类似的东西

lift . return = return
lift (m >>= f) = lift m >>= (lift . f)

正如你所拥有的flip co one :: Monad m => m a -> m a,并且你想提升它以获得 type 的功能(MonadTrans t, Monad m, Monad (t m)) => t m a -> t m a。所以跟随电梯的脚步

lift' :: (MonadTrans t, Monad m, Monad (t m)) => (m a -> m a) -> t m a -> t m a
lift' f tma = tma >>= (lift . f . return)

现在定义co'是微不足道的

co' one two = lift' (flip co two) one

问题

上面的解决方案只是满足类型,但它们也满足语义吗?要查看问题,让我们采用一个co始终返回第二个动作而不看第一个动作的函数。现在上面co'永远无法做到这一点,因为它总是在决定任何事情之前运行第一个动作。所以第一个动作的副作用仍然会发生在co'即使它们没有发生在co

是否存在通用解决方案?

我想不会。因为您想要实际实现该功能的一般功能是一个类似 的功能t m a -> (m a -> m b) -> t m b,您需要在其中执行操作m a而不t m a实际运行m a.

因此,假设mIOmonad 并且t是具有某种状态的状态转换器,并且您的one操作实际上发射了一枚导弹,并根据其成功或失败来修改State. 现在你想要的是在不发射导弹的情况下实际修改状态。一般来说,这是不可能的。

如果您知道一些关于co首先运行哪个操作的信息,那么您可以co'使用上述方法实现。

于 2013-08-22T19:35:11.443 回答
0

monad-control包可用于将函数和控制操作从基本 monad 提升到 monad 转换器,甚至是那些接受回调的函数。

Lift-base包建立在 monad-control 之上,包含许多处理异常、并发的常用函数的提升版本……特别是,它具有来自 Control.Exception 的 finally 的提升版本签名与.co

以下是一些链接:

http://www.yesodweb.com/book/monad-control

http://www.yesodweb.com/blog/2013/08/exceptions-and-monad-transformers

于 2013-08-22T20:01:36.207 回答