在基本monad中将代理与不同的EitherT组合在一起

时间:2013-02-14 12:34:06

标签: haskell proxies haskell-pipes

例如,有......

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。我得到了第一个例子,但无法理解人为的例子。更多的例子,不是那么明显但不那么做作,可能会澄清如何使用hoistlift来匹配任何基础monad的组合。

3 个答案:

答案 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

使用hoistlift的组合,您可以在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变换器堆栈,则需要在ConsumerEitherT 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指定几个理论启发的法律,并验证hoistlift是否遵守这些法律。

一旦我们满足这些法律,我们就可以忽略hoistlift所做的所有细节。类别理论使我们能够在一个非常高的抽象层次上工作,我们只是在monad变换器之间空间地“插入升降机”,并且代码神奇地将我们的空间直觉转化为严格正确的行为。

我的猜测是你可能想要第一个解决方案,因为你可以在一个hoist层中共享生产者和消费者之间的错误处理。