如果其中一个monad被包裹在monad变换器中,是否可以重用monad合成函数?

时间:2013-08-22 18:48:42

标签: haskell monads monad-transformers

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

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

您可以将co视为更高阶函数,描述两个monadic动作如何相互合作以完成任务。

但是现在我发现第一个monadic动作可能包含在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 a中,第一个m可以与t合作。

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

3 个答案:

答案 0 :(得分:5)

您可以使用hoist包中的mmorph执行此操作。 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表现得直观。

hoistControl.Monad.Morph包的mmorph模块中提供,您可以找到here。主模块的底部有a tutorial教学如何使用包

答案 1 :(得分: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

关于问题的替代观点

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

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这样的函数,你需要从m a中获取行动t m a而不实际运行{{1}的副作用}。

所以假设m am monad而IO是具有某种状态的状态转换器,而你的t动作实际上是发射导弹并取决于它的成功或失败修改one。现在你想要的是在没有发射导弹的情况下实际修改状态。一般来说,这是不可能的。

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

答案 2 :(得分:0)

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

lifted-base包构建在monad-control之上,包含处理异常,并发的许多常用函数的提升版本......特别是,它具有来自Control的lifted version finally .Exception,其签名与co非常相似。

以下是一些链接:

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

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