Haskell 什么';这是导管的真正好处';s上游类型参数?

Haskell 什么';这是导管的真正好处';s上游类型参数?,haskell,conduit,haskell-pipes,Haskell,Conduit,Haskell 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
函数,该函数定义了一个来自
可能
的管道的函子,该管道不知道
可能
是否知道向后兼容性

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
一旦你有了一个尊重该协议的
生产者
,你就可以使用大量的解析器,对
无内容
进行抽象检查。最简单的是
绘图

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
。。。还有其他一些不错的功能。用户实际上根本不必直接与输入信号的末尾进行交互


通常,当人们要求输入端处理时,他们真正想要的是解析,这就是为什么管道解析将输入端问题作为解析的一个子集。

根据我的经验,上游终止符的实际好处非常有限,这就是为什么它们在公共API中隐藏的原因。我想我只在一段代码中使用了它们(wai extra的多部分解析)

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

然而,在实践中,实际使用此类功能的情况非常罕见,而且因为它只是混淆了API,所以它在1.0版本中隐藏在.Internal模块中。一个理论用例可以是:

  • 您有一个生成字节流的源
  • 一种管道,它消耗字节流,计算一个哈希作为最终结果,并将所有字节传递到下游
  • 一种接收器,它消耗字节流,例如将字节流存储在文件中
使用上游端接器,可以将这三个端接器连接起来,并将来自导管的结果作为管道的最终结果返回。然而,在大多数情况下,有一种替代的、更简单的方法来实现相同的目标。在这种情况下,您可以:

  • 使用
    conduitFile
    将字节存储在文件中,并将散列通道转换为散列接收器并将其置于下游
  • 用于将哈希接收器和文件写入接收器合并到单个接收器中

  • 我很好奇,这个协议是如何与管道的可组合性结合在一起的?假设我有一个管道
    readFileK
    ,它读取一个文件,然后发送
    Nothing
    来表示结束。如果我做了
    (readFileK“file1”>>readFileK“file2”)>->otherPipeK
    那么
    otherPipeK
    会两次接收
    什么都没有
    ?另一方面,如果我有
    readFileK“文件
    ”>->(pipe1K>>pipe2K)当
    pipe1K
    正在处理时,文件中的输入被耗尽,然后
    pipe2K
    永远不会知道输入已经耗尽。这就是为什么
    onlyK
    是一个单独的组合器,
    Nothing
    行为没有内置到源中。通过这种方式,您可以将多个源组合成一个源,例如
    onlyK(readFileS“file”>=>readSocketS socket)
    。你的第二个例子没有引起任何问题。如果
    pipe1K
    输入不足,它将在
    ParseP
    中失败,
    pipe2K
    将永远不会运行。没有一个解析原语能够通过输入结束标记。
    draw :: (Monad m, Proxy p) => Consumer (ParseP a p) (Maybe a) m a
    
    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