我试图了解如何在Haskell中使用iteratee库。到目前为止,我看过的所有文章似乎都集中在建立如何构造迭代器的直觉上,这很有帮助,但是现在我想开始并实际使用它们,我有点不高兴了。在我看来,查看迭代对象的源代码具有有限的价值。

假设我有此功能,可修剪一行中的尾随空白:

import Data.ByteString.Char8

rstrip :: ByteString -> ByteString
rstrip = fst . spanEnd isSpace

我想做的是:将其放入iteratee,读取文件并将其写到其他位置,并在每行尾部删除空白。我将如何与迭代对象进行结构化?我看到Data.Iteratee.Char中有一个enumLinesBS函数,可以对此进行深入研究,但是我不知道是否应该使用mapChunksconvStream或如何将上述函数重新打包为iteratee。

最佳答案

如果您只想要代码,就是这样:

procFile' iFile oFile = fileDriver (joinI $
   enumLinesBS ><>
   mapChunks (map rstrip) $
   I.mapM_ (B.appendFile oFile))
   iFile

评论:

这是一个分为三个阶段的过程:首先将原始流转换为行流,然后应用函数转换该行流,最后使用该流。由于rstrip处于中间阶段,它将创建一个流转换器(枚举)。

您可以使用mapChunksconvStream,但是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在连续的迭代之间传递状态。
convStreamunfoldConvStream也允许单子(monad)动作,因为流处理iteratee是单子(monad)转换器。

关于haskell - Haskell Iteratee:剥离尾随空格的简单示例,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/6643495/

10-13 06:50