如果两个单子变换器的底层单子类型相同,但它们的类型不同,是否有一种原则性的方法来组合它们?

7

我很难扩展这个问题。但是这里有一个使用案例:假设你有两个单子变换器 ts,它们在相同的单子 m 上进行变换:

master :: (MonadTrans t, Monad m) => t m a b
slave  :: (MonadTrans t, Monad m) => s m a b

我希望能够组合主节点从节点,使它们可以在将m个原语提升到ts时相互通信。签名可能如下:

bound :: (MonadTrans t, MonadTrans s, Monad m, Monoid a) => t m a b -> s m a b -> (...)
But what is the type of (...) ?

一个用例,采用简化的表示方法:
master :: Monoid a => a -> t m a b
master a = do 
   a <- lift . send $ (a,False)     -- * here master is passing function param to slave
   ...                              -- * do some logic with a
   b <- lift . send $ (mempty,True) -- * master terminates slave, and get back result

slave :: Monoid a => (a -> b) -> s m a b
slave g = do 
    (a,end) <- lift receive
    case end of 
        True -> get >>= \b -> exit b  
        _    -> (modify (++[g a])) >> slave g

更新: sendreceive 是类型为m的原语。
如果这个例子看起来很牵强,或者太像协程,请忽略所有相似之处,因为问题的实质与此无关。但主要观点是,在两个单子ts之前无法合理地组合彼此,但在它们都包装了一些底层单子m之后,它们现在可以组合并作为一个单一函数运行。至于组合函数的类型,我真的不确定,所以需要一些指导。如果这种抽象已经存在,而我只是不知道它,那就最好了。

st是任意的还是有特定的含义 - 我们是否试图创建这样的st?同样的问题也适用于m - 它以及它的sendreceive是否有特定的含义? - Petr
是的,st是任意的。m是我们在这里尝试创建的特定类型。sendreceive只是类型为(a,Bool) -> m a bm a b的原始函数。但它们与我提供的人为用例无关,它们的实现细节并不重要。 - xiaolingxiao
1个回答

8

是的。使用来自 mmorph 包的 hoistlift 来实现:

bound
    :: (MonadTrans t, MonadTrans s, MFunctor t, Monad m)
    => t m () -> s m () -> t (s m) ()
bound master slave = do
    hoist lift master
    lift slave

为了理解这个原理,需要仔细研究 hoist 类型:
hoist :: (MFunctor t) => (forall x . m x -> n x) -> t m r -> t n r
hoist让你可以修改任何实现了MFunctor(大部分Monad变换器都有)的基础Monad。 bound的代码所做的是让两个Monad变换器在最终目标Monad上达成一致,这里的目标Monad是t (s m)。嵌套ts的顺序由您自己决定,因此我只假设您希望t在外面。
然后,只需要使用hoistlift的各种组合来使两个子计算达成对最终Monad堆栈的一致。第一个组合的工作方式如下:
master :: t m r
hoist lift master :: t (s m) r

第二个工作方式如下:
slave :: s m r
lift slave :: t (s m) r

现在他们两个都同意了,因此我们可以在同一个do块中按顺序执行它们,这样就可以“正常工作”了。
如果想了解更多关于hoist如何工作的信息,我建议您查看mmorph包的文档,其中底部有一个不错的教程

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接