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