我试图了解如何在Haskell中使用iteratee库。到目前为止,我看过的所有文章似乎都集中在建立如何构造迭代器的直觉上,这很有帮助,但是现在我想开始并实际使用它们,我有点不高兴了。在我看来,查看迭代对象的源代码具有有限的价值。
假设我有此功能,可修剪一行中的尾随空白:
import Data.ByteString.Char8
rstrip :: ByteString -> ByteString
rstrip = fst . spanEnd isSpace
我想做的是:将其放入iteratee,读取文件并将其写到其他位置,并在每行尾部删除空白。我将如何与迭代对象进行结构化?我看到Data.Iteratee.Char中有一个
enumLinesBS
函数,可以对此进行深入研究,但是我不知道是否应该使用mapChunks
或convStream
或如何将上述函数重新打包为iteratee。 最佳答案
如果您只想要代码,就是这样:
procFile' iFile oFile = fileDriver (joinI $
enumLinesBS ><>
mapChunks (map rstrip) $
I.mapM_ (B.appendFile oFile))
iFile
评论:
这是一个分为三个阶段的过程:首先将原始流转换为行流,然后应用函数转换该行流,最后使用该流。由于
rstrip
处于中间阶段,它将创建一个流转换器(枚举)。您可以使用
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
剥离它们。现在,您只需要添加一个使用者(这是一个普通的iteratee),就可以完成: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
也允许单子(monad)动作,因为流处理iteratee是单子(monad)转换器。关于haskell - Haskell Iteratee:剥离尾随空格的简单示例,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/6643495/