管道的剩余物有什么好处?

13
我正在尝试理解conduitpipes之间的区别。与pipes不同,conduit具有剩余概念。剩余物有什么用处?我想看一些必要的剩余物示例。
由于pipes没有剩余概念,是否有任何方法可以实现类似的行为?
2个回答

16

Gabriella提出剩余数据(leftovers)总是解析(parsing)的一部分是有意思的,但我不确定是否同意这个观点,可能取决于对解析的定义。

有许多使用情况需要剩余数据。解析确实是其中之一:任何时候解析需要某种向前看(lookahead),都需要剩余数据。其中一个例子是在markdown包的getIndented函数中,该函数会隔离出所有具有特定缩进级别的即将到来的行,并留下剩余的行以便后续处理。

但在conduit本身中存在一组更为平凡的示例。每当你处理紧密打包的数据(例如ByteString或Text)时,你就需要读取一个块,以某种方式进行分析,使用剩余数据将额外数据回推,然后对原始内容执行某些操作。其中可能最简单的例子是dropWhile

事实上,我认为剩余数据是流库的核心和基本功能,conduit的新1.0接口甚至不向用户公开禁用剩余数据的选项。我知道极少数真实世界的使用情况不需要它或者说以某种方式可以避免使用。


谢谢您的解释,我现在非常确定了。同时,我在思考如何在不具备剩余数据功能的类似conduit的库之上实现剩余数据。我的想法是使用返回 (Maybe i, r) 的conduit来表示带剩余数据的conduit。我在这里尝试实现了(用于conduit):https://gist.github.com/ppetr/5110909。 - Petr
我认为你的直觉是正确的,你的实现方式非常类似于内部的工作原理。我认为你发现了双剩余问题,这就是为什么在conduit中允许将剩余物堆叠成多个Leftover构造函数的原因。 - Michael Snoyman
又是一次尝试(我很可能会在我的Scala库中使用)处理剩余部分,将剩余部分视为一种反馈:对于Pipe Void i (Either i o) u m r,我们使用内部方法将任何Left i发送回其输入,从而将这样的管道转换为标准管道。 - Petr
能否请您指出一个剩余量的简单示例?我看了您的dropWhile链接,但它是404错误。在dropWhile的情况下,剩余物是什么?如果可能的话,请参见https://stackoverflow.com/q/44402598/409976? 谢谢! - Kevin Meredith
2
按下空格键以通过此处的幻灯片:http://www.snoyman.com/reveal/conduit-yesod#/13/1。这也在Conduit教程中涵盖,我建议阅读:https://haskell-lang.org/library/conduit。 - Michael Snoyman

15

我会回答有关 pipes 的问题。简短回答你的问题是,即将推出的 pipes-parse 库将支持剩余部分作为更一般解析框架的一部分。我发现几乎所有需要剩余部分的情况实际上都需要一个解析器,这就是为什么我将剩余部分问题定义为解析器的子集的原因。你可以在这里找到该库的当前草案。

但是,如果你想理解如何使 pipes-parse 工作,最简单的实现剩余部分的方法是使用 StateP 存储推回缓冲区。这只需要定义以下两个函数:

import Control.Proxy
import Control.Proxy.Trans.State

draw :: (Monad m, Proxy p) => StateP [a] p () a b' b m a
draw = do
    s <- get
    case s of
        []   -> request ()
        a:as -> do
            put as
            return a

unDraw :: (Monad m, Proxy p) => a -> StateP [a] p () a b' b m ()
unDraw a = do
    as <- get
    put (a:as)

draw首先查看回推缓冲区是否有存储的元素,如果有,则从堆栈弹出一个元素。如果缓冲区为空,则向上游请求一个新元素。当然,如果我们不能回推任何内容,那么拥有缓冲区也没有意义,因此我们还定义了unDraw函数以将元素推回堆栈以便稍后使用。

编辑:哎呀,我忘记包含一个有用的例子来说明剩余部分的用途了。就像迈克尔所说的,takeWhiledropWhile是剩余部分有用的情况。这里是drawWhile函数(类似于迈克尔所称的takeWhile):

drawWhile :: (Monad m, Proxy p) => (a -> Bool) -> StateP [a] p () a b' b m [a]
drawWhile pred = go
  where
    go = do
        a <- draw
        if pred a
        then do
            as <- go
            return (a:as)
        else do
            unDraw a
            return []

现在想象一下,如果你的制片人是:

producer () = do
    respond 1
    respond 3
    respond 4
    respond 6

... 然后将其连接到使用以下内容的消费者:

consumer () = do
    evens <- drawWhile odd
    odds  <- drawWhile even
如果第一个 drawWhile odd 没有推回它绘制的最后一个元素,那么您将会丢失4,导致它不会被正确传递到第二个 drawWhile even 语句。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接