我正在尝试制作一个介于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
留下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
并用 放回流中leftover
。不幸的是,这似乎是一个可怕的组合,并且不希望(尽管并非如此)它会缓冲至limit
在内存中缓冲最多字节,然后将其放回leftover
. 这似乎是一种浪费。
我能想到的唯一解决方案是根据原语编写它await
,yield
并且它们本身就是leftover
编写takeWhile
的isolate
。虽然这可以解决所有问题而不会浪费太多,但似乎必须有更好的方法。
我错过了什么,还是真的没有更好的方法来写这个?