导管上游类型参数的真正好处是什么?

时间:2013-03-06 21:27:08

标签: haskell conduit haskell-pipes

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

(>+>) :: 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

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

我想知道,渠道的方法有什么好处?我想看一些使用 conduit >+>轻松实现的示例(最好是真实世界),但是使用管道实现很难(呃)>->

2 个答案:

答案 0 :(得分:9)

当前使用conduit更容易实现的经典示例是处理来自上游的输入结束。例如,如果要折叠值列表并将结果绑定到管道中,则无法在pipes之内在pipes之上设置额外协议。

事实上,这正是即将到来的pipes-parse库解决的问题。它在Maybe之上设计了一个pipes协议,然后定义了方便的函数,用于从上游绘制符合该协议的输入。

例如,您有onlyK函数,该函数接受管道并包裹Just中的所有输出,然后以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将输入结束问题作为解析子集的原因。

答案 1 :(得分:5)

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

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

然而,在实践中,实际使用这种功能非常罕见,并且由于它只是混淆了API,因此它被隐藏在1.0版本的.Internal模块中。一个理论用例可能如下:

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

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

  1. 使用conduitFile将字节存储在文件中,并将哈希管道转换为哈希接收器并将其放在下游
  2. 使用zipSinks将哈希接收器和文件写入接收器合并到一个接收器中。