2

我正在尝试制作一个介于takeWhile和之间的管道isolate。也就是说,它将从输入消耗并屈服于输出,直到谓词不再成立或达到字节限制。我知道类型签名将是

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留下HetakeWhile丢弃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并用 放回流中leftover。不幸的是,这似乎是一个可怕的组合,并且不希望(尽管并非如此)它会缓冲至limit在内存中缓冲最多字节,然后将其放回leftover. 这似乎是一种浪费。

我能想到的唯一解决方案是根据原语编写它awaityield并且它们本身就是leftover编写takeWhileisolate 。虽然这可以解决所有问题而不会浪费太多,但似乎必须有更好的方法。

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

4

1 回答 1

1

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

于 2013-10-21T13:26:52.520 回答