由于pipes没有剩余概念,是否有任何方法可以实现类似的行为?
Gabriella提出剩余数据(leftovers)总是解析(parsing)的一部分是有意思的,但我不确定是否同意这个观点,可能取决于对解析的定义。
有许多使用情况需要剩余数据。解析确实是其中之一:任何时候解析需要某种向前看(lookahead),都需要剩余数据。其中一个例子是在markdown包的getIndented函数中,该函数会隔离出所有具有特定缩进级别的即将到来的行,并留下剩余的行以便后续处理。
但在conduit本身中存在一组更为平凡的示例。每当你处理紧密打包的数据(例如ByteString或Text)时,你就需要读取一个块,以某种方式进行分析,使用剩余数据将额外数据回推,然后对原始内容执行某些操作。其中可能最简单的例子是dropWhile。
事实上,我认为剩余数据是流库的核心和基本功能,conduit的新1.0接口甚至不向用户公开禁用剩余数据的选项。我知道极少数真实世界的使用情况不需要它或者说以某种方式可以避免使用。
我会回答有关 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
函数以将元素推回堆栈以便稍后使用。
编辑:哎呀,我忘记包含一个有用的例子来说明剩余部分的用途了。就像迈克尔所说的,takeWhile
和dropWhile
是剩余部分有用的情况。这里是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
语句。
(Maybe i, r)
的conduit来表示带剩余数据的conduit。我在这里尝试实现了(用于conduit):https://gist.github.com/ppetr/5110909。 - PetrPipe Void i (Either i o) u m r
,我们使用内部方法将任何Left i
发送回其输入,从而将这样的管道转换为标准管道。 - Petr