【问题标题】:What's the real benefit of conduit's upstream type parameter?管道的上游类型参数的真正好处是什么?
【发布时间】:2013-03-06 21:27:08
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: haskell conduit haskell-pipes


    【解决方案1】:

    目前使用conduit 更容易实现的经典示例是处理来自上游的输入结束。例如,如果你想折叠一个值列表并在管道中绑定结果,你不能在 pipes 内完成,除非在 pipes 之上设计一个额外的协议。

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

    例如,您有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 将输入结束问题作为解析的子集。

    【讨论】:

    • 我很好奇,这个协议如何与管道可组合性结合在一起?假设我有一个管道readFileK 读取文件并发送Nothing 以表示结束。如果我做(readFileK "file1" >> readFileK "file2") >-> otherPipeK 然后otherPipeK 收到Nothing 两次?另一方面,如果我有readFileK "file" >-> (pipe1K >> pipe2K) 并且在pipe1K 正在处理时文件的输入已经用尽,那么pipe2K 永远不会知道输入已经耗尽。
    • 这就是为什么onlyK 是一个单独的组合子并且Nothing 行为没有内置到源代码中。这样您就可以将多个来源合二为一,例如onlyK (readFileS "file" >=> readSocketS socket)。您的第二个示例不会引起任何问题。如果pipe1K 用完输入,它将在ParseP 中失败,而pipe2K 将永远不会运行。没有一个解析原语能够超过输入标记的结尾。
    【解决方案2】:

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

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-11
      • 2012-04-01
      • 1970-01-01
      • 2014-09-08
      • 1970-01-01
      • 2021-12-19
      • 2010-12-15
      • 2015-05-12
      相关资源
      最近更新 更多