19

我试图了解如何将 iteratee 库与 Haskell 一起使用。到目前为止,我所看到的所有文章似乎都专注于建立一种关于如何构建迭代器的直觉,这很有帮助,但现在我想深入了解并实际使用它们,我觉得有点不知所措。查看 iteratees 的源代码对我来说价值有限。

假设我有这个函数可以从一行中修剪尾随空格:

import Data.ByteString.Char8

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

我想做的是:将它变成一个迭代器,读取一个文件并将其写在其他地方,并从每行中去除尾随空格。我将如何使用迭代器构建它?我看到enumLinesBSData.Iteratee.Char 中有一个函数,我可以深入了解它,但我不知道是否应该使用mapChunksconvStream如何将上面的函数重新打包成一个 iteratee。

4

1 回答 1

16

如果你只想要代码,那就是:

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

评论:

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

您可以使用mapChunksconvStream,但mapChunks更简单。不同之处在于mapChunks不允许您跨越块边界,而convStream更通用。我更喜欢convStream它,因为它不公开任何底层实现,但如果mapChunks足够的话,生成的代码通常会更短。

rstripE :: Monad m => Enumeratee [ByteString] [ByteString] m a
rstripE = mapChunks (map rstrip)

请注意map. rstripE外部流(即 rstrip 的输入)具有 type [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)

无论如何,有了可用的剥离 enumeratee,它很容易与enumLinesBSenumeratee 组成:

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 的额外功能。假设您想将每 2 行连接成 1 行。你不能使用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视为使用提供的迭代器不断消耗流的一部分,然后将转换后的版本发送给内部消费者。有时即使这样也不够通用,因为在每一步都会调用同一个迭代对象。在这种情况下,您可以使用unfoldConvStream在连续迭代之间传递状态。

convStream并且unfoldConvStream还允许单子操作,因为流处理迭代器是单子转换器。

于 2011-07-11T02:21:15.467 回答