假设我有一个组成两个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
合作。
这里的诀窍是在不真正了解co
或m
的实现的情况下构建t
。我觉得答案是在MFunctor包中的某个地方,事实上昨天也提出了类似的问题。但是想不出什么好的,有什么想法吗?
答案 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
表现得直观。
hoist
在Control.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 a
是m
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