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

Dan*_*rro 4 haskell proxies haskell-pipes

例如,有......

consumer :: Proxy p => () -> Consumer p a (EitherT String IO) ()
producer :: Proxy p => () -> Producer p a (EitherT ByteString IO) r
Run Code Online (Sandbox Code Playgroud)

......我该如何工作?

session :: EitherT ByteString (EitherT String IO) ()
session = runProxy $ producer >-> consumer
Run Code Online (Sandbox Code Playgroud)

:我读过混合基地单子Control.Proxy.Tutorial.我得到了第一个例子,但无法理解人为的例子.更多的例子,不是那么明显但不那么做作,可能会澄清如何使用hoistlift匹配任何基础monad的组合.

dan*_*iaz 8

假设你有一个像单子转换堆MT1 MT2 MT3 M a在那里M为基地单子.

使用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,你可以在单子转换堆栈中的任何位置插入这些"通配符".

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堆栈.


Dan*_*ner 5

两个包(我猜你的EitherT类型来自哪里)提供了几个修改第一个参数的函数,例如

bimapEitherT :: Functor m => (e -> f) -> (a -> b) -> EitherT e m a -> EitherT f m b
Run Code Online (Sandbox Code Playgroud)

你可以使用它,以及一些适当的编码(或解码),将a EitherT String IO a转换为EitherT ByteString IO a(反之亦然),然后hoist转换为Consumeror Producermonad转换器.


Gab*_*lez 5

实际上有两种解决方案.

第一个解决方案是Daniel Wagner提出的解决方案:修改两个基础monad以使用相同的Left类型.例如,我们可以将它们标准化为两种用途ByteString.要做到这一点,我们首先采取ByteStringpack功能:

pack :: String -> ByteString
Run Code Online (Sandbox Code Playgroud)

然后我们将它提升到左边的值EitherT:

import Control.Error (fmapLT)  -- from the 'errors' package

fmapLT pack :: (Monad m) => EitherT String m r -> EitherT ByteString m r
Run Code Online (Sandbox Code Playgroud)

现在我们需要使用以下方法将转换定位到您Consumer的基础monad hoist:

hoist (fmapLT pack)
 :: (Monad m, Proxy p)
 => Consumer p a (EitherT String m) r -> Consumer p a (EitherT ByteString m) r
Run Code Online (Sandbox Code Playgroud)

现在,您可以直接与您的生产者组成您的消费者,因为他们具有相同的基础monad.

第二个解决方案是Daniel Diaz Carrete提出的解决方案.你改为让你的两个管道同意一个包含两个EitherT层的公共monad变换器堆栈.您所要做的就是决定嵌套这两层的顺序.

让我们假设您选择将EitherT String变压器层叠在变压器外部EitherT ByteString.这意味着你的最终目标monad变换器堆栈将是:

(Proxy p) => Session (EitherT String (EitherT ByteString p)) IO r
Run Code Online (Sandbox Code Playgroud)

现在,您需要提升两个管道以定位该变换器堆栈.

对于您来说Consumer,您需要EitherT ByteString在两者之间插入一个图层EitherT String,IO如果您想匹配最终的monad变换器堆栈.创建图层很简单:您只需使用lift,但需要在这两个特定图层之间定位,所以您使用hoist两次,因为您需要跳过代理monad转换器和EitherT Stringmonad转换器:

hoist (hoist lift) . consumer
 :: Proxy p => () -> Consumer p a (EitherT String (EitherT ByteString IO)) ()
Run Code Online (Sandbox Code Playgroud)

对于您来说Producer,如果要匹配最终的monad变换器堆栈,则需要EitherT String在代理monad变换器和EitherT ByteString变换器之间插入一个层.同样,创建图层很简单:我们只是使用lift,但您需要在这两个特定图层之间定位该提升.你只是hoist,但这次你只使用一次,因为你只需要跳过代理monad变换器就可以将它lift放在正确的位置:

hoist lift . producer
 :: Proxy p => () -> Producer p a (EitherT String (EitherT ByteString IO)) r
Run Code Online (Sandbox Code Playgroud)

现在你的生产者和消费者拥有相同的monad变换器堆栈,你可以直接组合它们.

现在,你可能想知道:是的这个过程hoist荷兰国际集团lift在做"正确的事"?答案是肯定的.类别理论的一部分神奇之处在于我们可以严格定义正确插入"空monad变换器层"的lift含义,我们可以hoist通过指定几个来严格定义"在两个monad变换器之间定位"的含义.理论上启发的法律,并验证lifthoist遵守这些法律.

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

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