我正在尝试了解 conduit 和管道之间的区别。与管道不同,管道具有剩余的概念。什么是剩菜有用?我想看一些残羹剩饭必不可少的例子。
由于管道没有剩余的概念,有没有办法与它们达成类似的行为?
答案 0 :(得分:17)
加布里埃尔认为,剩菜总是解析的一部分很有意思。我不确定我会同意,但这可能只取决于解析的定义。
有大量用例需要剩菜。解析肯定是一个:任何时候解析都需要某种前瞻,你需要剩下的东西。其中一个例子是markdown包的getIndented函数,它将所有即将到来的行隔离一定的缩进级别,剩下的行将在稍后处理。
但是更多平凡的例子存在于管道本身。无论何时处理打包数据(如ByteString或Text),您都需要读取一个块,以某种方式分析它,使用剩余的来推回额外的数据,然后对原始内容执行某些操作。也许最简单的例子是dropWhile。
事实上,我认为leftover是流媒体库的核心基本功能,管道的新1.0接口甚至不会向禁用剩余的用户公开选项。我知道很少有真实世界的用例不需要这种或那种方式。
答案 1 :(得分:16)
我会回答pipes
。对你的问题的简短回答是,即将到来的pipes-parse
库将支持剩余部分,作为更通用的解析框架的一部分。我发现几乎每个人都想要剩菜的情况下他们实际上都想要一个解析器,这就是为什么我将剩余问题作为解析的一个子集。您可以找到库here的当前草稿。
但是,如果您想了解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
函数(类似于Michael所称的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
语句`。