14

我试图了解导管管道之间的区别。与管道不同,管道有剩菜的概念。剩菜有什么用?我想看看一些剩菜必不可少的例子。

而且由于管道没有剩菜的概念,有没有办法用它们实现类似的行为?

4

2 回答 2

17

Gabriella 关于剩余部分总是解析的一部分的观点很有趣。我不确定我是否会同意,但这可能仅取决于解析的定义。

有一大类用例需要剩余部分。解析当然是一个:任何时候解析需要某种前瞻,你就需要剩余部分。一个例子是在 markdown 包的getIndented函数中,它将所有即将出现的行与某个缩进级别隔离开来,剩下的行留待以后处理。

但是,在管道本身中有一组更平凡的例子。每当您处理打包数据(如 ByteString 或 Text)时,您都需要读取一个块,以某种方式对其进行分析,使用剩余的东西来推回额外的内容,然后对原始内容做一些事情。也许最简单的例子是dropWhile

事实上,我认为 leftover 是流式库的核心、基本功能,以至于管道的新 1.0 接口甚至没有向用户公开禁用 leftovers 的选项。我知道很少有实际用例不需要以某种方式使用它。

于 2013-03-07T05:22:41.177 回答
16

我会回答的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语句`。

于 2013-03-06T22:11:53.123 回答