我想通过回答您关于为什么解码器不是Pipe
.
Pipe
a与以下类型的区别:
pipe :: Pipe a b m r
...和Producer
s 之间的函数(我称之为“getter”):
getter :: Producer a m r -> Producer b m r
...是aPipe
可以用来变换Producer
s、Consumer
s和其他Pipe
s:
(>-> pipe) :: Producer a m r -> Producer b m r
(>-> pipe) :: Pipe x a m r -> Pipe x b m r
(pipe >->) :: Consumer b m r -> Consumer a m r
(pipe >->) :: Pipe b y m r -> Pipe a y m r
...而“getter”只能转换Producer
s。有些东西不能用Pipe
s 正确建模,剩下的就是其中之一。
conduit
声称使用Conduit
s(s 的conduit
类似物Pipe
)对剩菜进行建模,但它弄错了。我整理了一个简单的例子来说明原因。首先,只需实现一个peek
函数conduit
:
import Control.Monad.Trans.Class (lift)
import Data.Conduit
import Data.Conduit.List (isolate, sourceList)
peek :: Monad m => Sink a m (Maybe a)
peek = do
ma <- await
case ma of
Nothing -> return ()
Just a -> leftover a
return ma
对于像这样的简单情况,这可以按预期工作:
source :: Monad m => Source m Int
source = sourceList [1, 2]
sink1 :: Show a => Sink a IO ()
sink1 = do
ma1 <- peek
ma2 <- peek
lift $ print (ma1, ma2)
这将返回源的第一个元素两次:
>>> source $$ sink1
(Just 1,Just 1)
...但是如果您在 a 的Conduit
上游构成Sink
,则接收器推回的任何剩余物都将不可逆转地丢失:
sink2 :: Show a => Sink a IO ()
sink2 = do
ma1 <- isolate 10 =$ peek
ma2 <- peek
lift $ print (ma1, ma2)
现在第二个peek
错误地返回2
:
>>> source $$ sink2
(Just 1,Just 2)
另外,请注意,pipes-parse
今天刚刚发布了一个新的主要版本,它简化了 API 并添加了一个广泛的教程,您可以在此处阅读。
这个新的 API 正确地将剩余物进一步传播到上游。以下是 的类似示例pipes
:
import Lens.Family.State.Strict (zoom)
import Pipes
import Pipes.Parse
import Prelude hiding (splitAt)
parser :: Show a => Parser a IO ()
parser = do
ma1 <- zoom (splitAt 10) peek
ma2 <- peek
lift $ print (ma1, ma2)
producer :: Monad m => Producer Int m ()
producer = each [1, 2]
即使第一个peek
值也仅限于前 10 个值,它正确地取消绘制第一个值并使其可用于第二个值peek
:
>>> evalStateT parser producer
(Just 1,Just 1)
从概念上讲,之所以pipes-parse
“根据 s 思考Producer
”,是因为否则剩余的概念没有明确定义。如果你没有清楚地定义你的来源是什么,你就不能清楚地阐明剩余价值应该去哪里。这就是为什么Pipe
s 和Consumer
s 不适合需要剩菜的任务。