我正试图在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
不会被limit
消费,而是被leftover
放回到流中。不幸的是,这看起来像一个可怕的kludge,并且不合需要(尽管不是那么不可行)它会在内存中缓冲到leftover
个字节,只能用takeWhile
来回放它。这似乎是一种浪费。
我能想到的唯一解决方案是将原语await
,yield
和leftover
写成isolate
和{{1}} {{ 3}}。虽然这可以解决所有问题而不会浪费太多,但似乎必须有更好的方法。
我错过了什么,或者真的没有更好的方法来写这个吗?
答案 0 :(得分:1)
当前版本的导管有一个已知的限制:融合总是丢弃下游剩余物,这正是你在这里遇到的。现在有一些关于解决这个问题的架构的讨论,但就目前而言,根据基元编写函数可能是最佳选择。