我正在尝试了解如何在Haskell中使用iteratee库。到目前为止,我所看到的所有文章似乎都专注于建立一个如何构建迭代的直觉,这是有帮助的,但现在我想要下来并实际使用它们,我觉得有点海上。查看iteratees的源代码对我来说价值有限。
假设我有这个函数可以修剪一行中的尾随空格:
import Data.ByteString.Char8
rstrip :: ByteString -> ByteString
rstrip = fst . spanEnd isSpace
我想做的是:将它变成一个iteratee,读取一个文件并将其写在其他地方,并从每一行中删除尾随空格。我将如何使用iteratees进行结构化?我看到Data.Iteratee.Char中有一个enumLinesBS
函数,我可以使用它,但我不知道是否应该使用mapChunks
或convStream
或如何重新打包函数上面是一个迭代。
答案 0 :(得分:16)
如果您只想要代码,就是这样:
procFile' iFile oFile = fileDriver (joinI $
enumLinesBS ><>
mapChunks (map rstrip) $
I.mapM_ (B.appendFile oFile))
iFile
评论:
这是一个三阶段过程:首先将原始流转换为行流,然后应用函数转换该行流,最后使用流。由于rstrip
处于中间阶段,因此它将创建一个流变换器(Enumeratee)。
您可以使用mapChunks
或convStream
,但mapChunks
更简单。区别在于mapChunks
不允许您跨越块边界,而convStream
更通用。我更喜欢convStream
,因为它没有暴露任何底层实现,但如果mapChunks
足够,则生成的代码通常会更短。
rstripE :: Monad m => Enumeratee [ByteString] [ByteString] m a
rstripE = mapChunks (map rstrip)
请注意map
中的额外rstripE
。外部流(它是rstrip的输入)的类型为[ByteString]
,因此我们需要将rstrip
映射到它上。
为了进行比较,这是用convStream实现的样子:
rstripE' :: Enumeratee [ByteString] [ByteString] m a
rstripE' = convStream $ do
mLine <- I.peek
maybe (return B.empty) (\line -> I.drop 1 >> return (rstrip line)) mLine
这个更长,效率更低,因为它一次只能将rstrip函数应用于一行,即使可以使用更多行。可以处理所有当前可用的块,这更接近mapChunks
版本:
rstripE'2 :: Enumeratee [ByteString] [ByteString] m a
rstripE'2 = convStream (liftM (map rstrip) getChunk)
无论如何,有了可用的剥离枚举,它很容易用enumLinesBS
枚举词组成:
enumStripLines :: Monad m => Enumeratee ByteString [ByteString] m a
enumStripLines = enumLinesBS ><> rstripE
合成运算符><>
遵循与箭头运算符>>>
相同的顺序。 enumLinesBS
将流拆分为行,然后rstripE
将其删除。现在你只需要添加一个消费者(这是一个正常的迭代),你就完成了:
writer :: FilePath -> Iteratee [ByteString] IO ()
writer fp = I.mapM_ (B.appendFile fp)
processFile iFile oFile =
enumFile defaultBufSize iFile (joinI $ enumStripLines $ writer oFile) >>= run
fileDriver
函数是简单枚举文件并运行生成的iteratee的快捷方式(不幸的是,参数顺序是从enumFile切换的):
procFile2 iFile oFile = fileDriver (joinI $ enumStripLines $ writer oFile) iFile
附录:在这种情况下,您需要额外的convStream功能。假设您想要将每两行连接成一行。您无法使用mapChunks
。考虑当块是单个元素[bytestring]
时。 mapChunks
没有提供任何方法来访问下一个块,因此没有其他东西可以与此连接。但是,使用convStream
,这很简单:
concatPairs = convStream $ do
line1 <- I.head
line2 <- I.head
return $ line1 `B.append` line2
这在应用风格上看起来更好,
convStream $ B.append <$> I.head <*> I.head
您可以将convStream
视为使用提供的iteratee持续消耗部分流,然后将转换后的版本发送给内部使用者。有时即使这样也不够通用,因为在每一步都会调用相同的迭代。在这种情况下,您可以使用unfoldConvStream
在连续迭代之间传递状态。
convStream
和unfoldConvStream
也允许monadic动作,因为流处理iteratee是monad变换器。