10

我试图了解管道概念的不同实现之间的差异。导管管道之间的区别之一是它们如何将管道融合在一起。导管

(>+>) :: Monad m
      => Pipe l a b r0 m r1 -> Pipe Void b c r1 m r2 -> Pipe l a c r0 m r2

管道

(>->) :: (Monad m, Proxy p)
      => (b' -> p a' a b' b m r) -> (c' -> p b' b c' c m r) -> c' -> p a' a c' c m r

如果我理解正确,则使用管道,当两个管道中的任何管道停止时,将返回其结果并停止另一个。使用管道,如果左管道完成,其结果将向下游发送到右管道。

我想知道,管道方法的好处是什么?我希望看到一些示例(最好是真实世界),使用管道和 很容易实现>+>使用管道>->.

4

2 回答 2

9

当前更容易实现的经典示例conduit是处理来自上游的输入结束。例如,如果你想折叠一个值列表并将结果绑定到管道中,如果没有pipespipes.

事实上,这正是即将推出的pipes-parse库所要解决的问题。它Maybe在上面设计了一个协议,pipes然后定义了方便的函数,用于从上游绘制尊重该协议的输入。

例如,您有一个onlyK函数,它接受一个管道并将所有输出包装在Just其中,然后以 a 结束Nothing

onlyK :: (Monad m, Proxy p) => (q -> p a' a b' b m r) -> (q -> p a' a b' (Maybe b) m r)

您还具有该justK函数,该函数定义了一个函子,Maybe从不知道的管道到知道的管道,以Maybe实现向后兼容性

justK :: (Monad m, ListT p) => (q -> p x a x b m r) -> (q -> p x (Maybe a) x (Maybe b) m r)

justK idT = idT
justK (p1 >-> p2) = justK p1 >-> justK p2

然后,一旦你有了一个Producer尊重该协议的协议,你就可以使用各种各样的解析器来Nothing为你抽象检查。最简单的是draw

draw :: (Monad m, Proxy p) => Consumer (ParseP a p) (Maybe a) m a

如果上游输入不足,它会检索类型的值a或在代理转换器中失败。ParseP您也可以一次取多个值:

drawN :: (Monad m, Proxy p) => Int -> Consumer (ParseP a p) (Maybe a) m [a]

drawN n = replicateM n draw  -- except the actual implementation is faster

...以及其他几个不错的功能。用户实际上根本不需要直接与输入信号的结尾进行交互。

通常当人们要求处理输入结束时,他们真正想要的是解析,这就是为什么pipes-parse将输入结束问题作为解析的子集。

于 2013-03-06T22:23:35.523 回答
5

根据我的经验,上游终结器的实际好处非常渺茫,这就是为什么它们在此时对公共 API 隐藏的原因。我想我只在一段代码中使用过它们(wai-extra 的多部分解析)。

在最一般的形式中,管道允许您生成输出值流和最终结果。当您将该 Pipe 与另一个下游 Pipe 融合时,该输出值流将成为下游的输入流,而上游的最终结果将成为下游的“上游终结者”。所以从这个角度来看,拥有任意上游终止符允许对称 API。

然而,在实践中,这样的功能很少被实际使用,而且因为它只是混淆了 API,所以它在 1.0 版本中被隐藏在 .Internal 模块中。一个理论用例可能如下:

  • 您有一个 Source 产生字节流。
  • 消耗字节流的管道,计算哈希作为最终结果,并将所有字节传递到下游。
  • 消耗字节流的接收器,例如,将它们存储在文件中。

使用上游终结器,您可以将这三个连接起来,并将来自 Conduit 的结果作为管道的最终结果返回。然而,在大多数情况下,有一种替代的、更简单的方法可以实现相同的目标。在这种情况下,您可以:

  1. 用于conduitFile将字节存储在文件中,并将哈希 Conduit 转换为哈希 Sink 并将其放置在下游
  2. 使用zipSinks将哈希接收器和文件写入接收器合并到单个接收器中。
于 2013-03-07T05:15:13.510 回答