如何制作像takeWhile这样的管道,但最多只需要一定数量的字节?

时间:2013-10-20 22:30:49

标签: haskell conduit

我正试图在takeWhileisolate之间建立一个交叉点。也就是说,它将从输入消耗并屈服于输出,直到谓词不再成立或已达到字节限制。我知道类型签名将是

isolateWhile :: (Monad m) => Int -> (Word8 -> Bool) -> Conduit ByteString m ByteString

作为其使用的一个例子:

{-# LANGUAGE OverloadedStrings #-}
import Data.Conduit
import qualified Data.Conduit.List   as CL
import qualified Data.Conduit.Binary as CB
import Control.Monad.Trans.Class

charToWord = fromIntegral . fromEnum

example :: Int -> Char -> IO ()
example limit upTo = do
    untaken <- CB.sourceLbs "Hello, world!" $= conduit $$ CB.sinkLbs
    putStrLn $ "Left " ++ show untaken
  where
    conduit = do
      taken <- toConsumer $ isolateWhile limit (/= charToWord upTo) =$ CB.sinkLbs
      lift $ putStrLn $ "Took " ++ show taken
      CL.map id  -- pass the rest through untouched

我希望

ghci> example 5 'l'
Took "He"
Left "llo, world!"
ghci> example 5 'w'
Took "Hello"
Left ", world!"

然而,isolateWhile的最简单定义:

isolateWhile limit pred = CB.isolate limit =$= CB.takeWhile pred

产量

ghci> example 5 'l'
Took "He"
Left ", world!"
ghci> example 5 'w'
Took "Hello"
Left ", world!"

换句话说,isolate将耗尽整个Hello,将He留给takeWhile并放弃llo。这种数据丢失对我的应用来说是不可取的然而,值得注意的是,第二种情况产生了预期的结果。

如果我像这样交换=$=的操作数:

isolateWhile limit pred = CB.takeWhile pred =$= CB.isolate limit

然后

ghci> example 5 'l'
Took "He"
Left ", world!"
ghci> example 5 'w'
Took "Hello"
Left ""

现在我已经修复了第一个测试,但是第二个测试已经破了!这一次,takeWhile将采取所需的一切,isolate将采用其中的一部分;但是takeWhile使用的isolate不会被丢弃,这是不可取的。

最后,我试过了:

isolateWhile limit pred = do
  untaken <- CB.isolate limit =$= (CB.takeWhile pred >> CL.consume)
  mapM_ leftover $ reverse untaken

这实际上有效! isolate所接受的takeWhile接受和CL.consume不会被limit消费,而是被leftover放回到流中。不幸的是,这看起来像一个可怕的kludge,并且不合需要(尽管不是那么不可行)它会在内存中缓冲到leftover个字节,只能用takeWhile来回放它。这似乎是一种浪费。

我能想到的唯一解决方案是将原语awaityieldleftover写成isolate和{{1}} {{ 3}}。虽然这可以解决所有问题而不会浪费太多,但似乎必须有更好的方法。

我错过了什么,或者真的没有更好的方法来写这个吗?

1 个答案:

答案 0 :(得分:1)

当前版本的导管有一个已知的限制:融合总是丢弃下游剩余物,这正是你在这里遇到的。现在有一些关于解决这个问题的架构的讨论,但就目前而言,根据基元编写函数可能是最佳选择。