例如,有......
consumer :: Proxy p => () -> Consumer p a (EitherT String IO) ()
producer :: Proxy p => () -> Producer p a (EitherT ByteString IO) r
...我该如何做到这一点?
session :: EitherT ByteString (EitherT String IO) ()
session = runProxy $ producer >-> consumer
注意:我在Control.Proxy.Tutorial
中看过Mixing Base Monads。我得到了第一个例子,但无法理解人为的例子。更多的例子,不是那么明显但不那么做作,可能会澄清如何使用hoist
和lift
来匹配任何基础monad的组合。
答案 0 :(得分:8)
假设您有一个像MT1 MT2 MT3 M a
这样的monad变换器堆栈,其中M
是基础monad。
使用lift
,您可以在左侧添加新的monad变换器。它可以是任何变换器,所以让我们用?
来表示它。
lift :: MT1 MT2 MT3 M a -> ? MT1 MT2 MT3 M a
使用hoist
,您可以操作最左边元素右侧的monad堆栈。操纵它怎么样?例如,提供lift
:
hoist lift :: MT1 MT2 MT3 M a -> MT1 ? MT2 MT3 M a
使用hoist
和lift
的组合,您可以在monad变换器堆栈中的任何位置插入这些“通配符”。
hoist (hoist lift) :: MT1 MT2 MT3 M a -> MT1 MT2 ? MT3 M a
hoist (hoist (hoist lift)) :: MT1 MT2 MT3 M a -> MT1 MT2 MT3 ? M a
此技术可用于均衡您示例中的两个monad堆栈。
答案 1 :(得分:5)
either包(我想你的EitherT
类型来自哪里)提供了几个修改第一个参数的函数,例如
bimapEitherT :: Functor m => (e -> f) -> (a -> b) -> EitherT e m a -> EitherT f m b
您可以使用此功能以及一些适当的编码(或解码)将EitherT String IO a
转换为EitherT ByteString IO a
(反之亦然),然后将hoist
转换为{ {1}}或Consumer
monad transformer。
答案 2 :(得分:5)
实际上有两种解决方案。
第一个解决方案是Daniel Wagner提出的解决方案:修改两个基本monad以使用相同的Left
类型。例如,我们可以将它们标准化为使用ByteString
。为此,我们首先采用ByteString
的{{1}}函数:
pack
然后我们解除它以处理pack :: String -> ByteString
的左值:
EitherT
现在我们需要使用import Control.Error (fmapLT) -- from the 'errors' package
fmapLT pack :: (Monad m) => EitherT String m r -> EitherT ByteString m r
将转化定位到Consumer
的基础monad:
hoist
现在,您可以直接与您的制作人一起撰写您的消费者,因为他们拥有相同的基础monad。
第二个解决方案是Daniel Diaz Carrete提出的解决方案。你改为让你的两个管道同意一个包含hoist (fmapLT pack)
:: (Monad m, Proxy p)
=> Consumer p a (EitherT String m) r -> Consumer p a (EitherT ByteString m) r
层的公共monad变换器堆栈。您所要做的就是决定嵌套这两层的顺序。
让我们假设您选择将EitherT
变换器分层到EitherT String
变换器之外。这意味着你的最终目标monad变换器堆栈将是:
EitherT ByteString
现在你需要提升你的两个管道来定位那个变换器堆栈。
对于(Proxy p) => Session (EitherT String (EitherT ByteString p)) IO r
,如果要匹配最终的monad变换器堆栈,则需要在Consumer
和EitherT ByteString
之间插入EitherT String
图层。创建图层非常简单:您只需使用IO
,但需要在这两个特定图层之间定位,因此您需要使用lift
两次,因为您需要跳过代理monad变压器和hoist
monad变压器:
EitherT String
对于hoist (hoist lift) . consumer
:: Proxy p => () -> Consumer p a (EitherT String (EitherT ByteString IO)) ()
,如果要匹配最终的monad变换器堆栈,则需要在代理monad转换器和Producer
转换器之间插入EitherT String
层。同样,创建图层很简单:我们只使用EitherT ByteString
,但您需要在这两个特定图层之间定位该提升。您只需lift
,但这次只使用一次,因为您只需要跳过代理monad转换器,将hoist
置于正确的位置:
lift
现在你的制作人和消费者拥有相同的monad变换器堆栈,你可以直接编写它们。
现在,你可能想知道:hoist lift . producer
:: Proxy p => () -> Producer p a (EitherT String (EitherT ByteString IO)) r
hoist
这个过程是{正确的事情'吗?答案是肯定的。类别理论的一部分神奇之处在于我们可以严格定义使用lift
正确插入“空monad变换器层”意味着什么,我们可以类似地严格定义“在两个monad变换器之间定位”的含义“使用lift
指定几个理论启发的法律,并验证hoist
和lift
是否遵守这些法律。
一旦我们满足这些法律,我们就可以忽略hoist
和lift
所做的所有细节。类别理论使我们能够在一个非常高的抽象层次上工作,我们只是在monad变换器之间空间地“插入升降机”,并且代码神奇地将我们的空间直觉转化为严格正确的行为。
我的猜测是你可能想要第一个解决方案,因为你可以在一个hoist
层中共享生产者和消费者之间的错误处理。