如果其中一个monad被封装在monad转换器中,是否可以重用monad组合函数



假设我有一个函数,它包含两个monad操作:

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

你可以把co看作一个更高阶的函数,它描述了两个一元动作如何相互合作来完成一项任务。

但现在我发现第一个一元动作可能被封装在一个一元变换器中,而第二个动作不是:

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原语提升到上下文t中来与m a协作。

这里的技巧是在不知道mt的实现的情况下构建co。我觉得答案就在MFunctor软件包的某个地方,事实上昨天我也问了一个类似的问题。但是不能想出什么好主意,有什么想法吗?

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

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的行为直观。

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

快速定义

您需要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

对这个问题的另一种看法

transformers包中看到lift的定义应该可以帮助您找到答案,因为您想要类似的

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

因为你有flip co one :: Monad m => m a -> m a,你想把它举起来得到一个类型为(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的函数,在这里您需要从t m a中获得操作m a,而不需要实际运行m a的副作用。

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

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

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

提升的基本包构建在monad控件上,包含许多处理异常、并发等常见函数的提升版本。。。特别是,它有一个提升版的finally from Control.Exception,其签名与co的签名非常相似。

以下是一些链接:

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

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

最新更新