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
.我得到了第一个例子,但无法理解人为的例子.更多的例子,不是那么明显但不那么做作,可能会澄清如何使用hoist
和lift
匹配任何基础monad的组合.
假设你有一个像单子转换堆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
使用的组合hoist
和lift
,你可以在单子转换堆栈中的任何位置插入这些"通配符".
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堆栈.
这两个包(我猜你的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
转换为Consumer
or Producer
monad转换器.
实际上有两种解决方案.
第一个解决方案是Daniel Wagner提出的解决方案:修改两个基础monad以使用相同的Left
类型.例如,我们可以将它们标准化为两种用途ByteString
.要做到这一点,我们首先采取ByteString
的pack
功能:
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 String
monad转换器:
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变换器之间定位"的含义.理论上启发的法律,并验证lift
和hoist
遵守这些法律.
一旦我们满足这些法律,我们就可以忽略所有确切lift
和有效的细节hoist
.类别理论使我们能够在非常高的抽象层次上工作,我们只是在monad变换器之间空间地"插入升降机",并且代码神奇地将我们的空间直觉转化为严格正确的行为.
我的猜测是你可能想要第一个解决方案,因为你可以在单个EitherT
层中共享生产者和消费者之间的错误处理.