我正在使用管道库,需要使用ASCII编码将ByteString流转换为行流(即String
)。我知道还有其他库(Pipes.Text和Pipes.Prelude)可能会让我更容易地从文本文件中生成行,但由于其他一些代码,我需要能够从ByteString
的Producer中获得作为String
的行。
更正式地说,我需要将Producer ByteString IO ()
转换为Producer String IO ()
,从而生成行。
我相信对于一个经验丰富的Pipes程序员来说,这一定是一句俏皮话,但到目前为止,我还没有成功破解Pipes ByteString中的所有FreeT
和Lens
技巧。
非常感谢您的帮助!
Stephan
如果你需要那种类型的签名,那么我建议你这样做:
import Control.Foldl (mconcat, purely)
import Data.ByteString (ByteString)
import Data.Text (unpack)
import Lens.Family (view)
import Pipes (Producer, (>->))
import Pipes.Group (folds)
import qualified Pipes.Prelude as Pipes
import Pipes.Text (lines)
import Pipes.Text.Encoding (utf8)
import Prelude hiding (lines)
getLines
:: Producer ByteString IO r -> Producer String IO (Producer ByteString IO r)
getLines p = purely folds mconcat (view (utf8 . lines) p) >-> Pipes.map unpack
这是因为purely folds mconcat
的类型是:
purely folds mconcat
:: (Monad m, Monoid t) => FreeT (Producer t m) r -> Producer t m r
其中在这种情况下t
将是Text
:
purely folds mconcat
:: Monad m => FreeT (Producer Text m) r -> Producer Text m r
任何时候,如果要减少以FreeT
为分隔符的流的每个Producer
子组,则可能需要使用purely folds
。那么,只需要选择合适的Fold
来减少子组。在这种情况下,您只想将一个组中的所有Text
块连接起来,因此传入mconcat
。我通常不建议这样做,因为它会在非常长的行上中断,但您指定需要这种行为。
之所以如此冗长,是因为pipes
生态系统将Text
提升到了String
之上,并试图鼓励处理任意长的行。如果您不受其他代码的约束,那么更惯用的方法就是:
view (utf8 . lines)
经过一点黑客攻击和这个博客的一些提示,我想出了一个解决方案,但它出奇地笨拙,我担心它也有点低效,因为它使用了ByteString.append:
import Pipes
import qualified Pipes.ByteString as PB
import qualified Pipes.Prelude as PP
import qualified Pipes.Group as PG
import qualified Data.ByteString.Char8 as B
import Lens.Family (view )
import Control.Monad (liftM)
getLines :: Producer PB.ByteString IO r -> Producer String IO r
getLines = PG.concats . PG.maps toStringProducer . view PB.lines
toStringProducer :: Producer PB.ByteString IO r -> Producer String IO r
toStringProducer producer = go producer B.empty
where
go producer bs = do
x <- lift $ next producer
case x of
Left r -> do
yield $ B.unpack bs
return r
Right (bs', producer') -> go producer' (B.append bs' bs)