Haskell iteratee:剥离尾随空格的简单工作示例

Dan*_*ons 19 iteration haskell bytestring iterate

我试图了解如何在Haskell中使用iteratee库.到目前为止,我所看到的所有文章似乎都专注于建立一个如何构建迭代的直觉,这是有帮助的,但现在我想要下来并实际使用它们,我觉得有点海上.查看iteratees的源代码对我来说价值有限.

假设我有这个函数修剪一行的尾随空格:

import Data.ByteString.Char8

rstrip :: ByteString -> ByteString
rstrip = fst . spanEnd isSpace
Run Code Online (Sandbox Code Playgroud)

我想做的是:将它变成一个iteratee,读取一个文件并将其写在其他地方,并从每一行中删除尾随空格.我将如何使用iteratees进行结构化?我看到有一个enumLinesBS在Data.Iteratee.Char功能,我可以下探到这一点,但我不知道我是否应该使用mapChunksconvStream或如何重新包装上面的功能于iteratee.

Joh*_*n L 16

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

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

评论:

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

您可以使用mapChunksconvStream,但mapChunks更简单.区别在于mapChunks不允许您跨越块边界,而convStream更通用.我更喜欢,convStream因为它不暴露任何底层实现,但如果mapChunks足够,结果代码通常更短.

rstripE :: Monad m => Enumeratee [ByteString] [ByteString] m a
rstripE = mapChunks (map rstrip)
Run Code Online (Sandbox Code Playgroud)

请注意额外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
Run Code Online (Sandbox Code Playgroud)

这个时间更长,效率更低,因为它只会将rstrip函数一次应用于一行,即使可能有更多行.可以处理所有当前可用的块,这更接近mapChunks版本:

rstripE'2 :: Enumeratee [ByteString] [ByteString] m a
rstripE'2 = convStream (liftM (map rstrip) getChunk)
Run Code Online (Sandbox Code Playgroud)

无论如何,使用剥离枚举器,它很容易与enumLinesBS枚举器组成:

enumStripLines :: Monad m => Enumeratee ByteString [ByteString] m a
enumStripLines = enumLinesBS ><> rstripE
Run Code Online (Sandbox Code Playgroud)

合成运算符><>遵循与箭头运算符相同的顺序>>>. 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
Run Code Online (Sandbox Code Playgroud)

这些fileDriver函数是简单地枚举文件并运行生成的iteratee的快捷方式(不幸的是,参数顺序是从enumFile切换的):

procFile2 iFile oFile = fileDriver (joinI $ enumStripLines $ writer oFile) iFile
Run Code Online (Sandbox Code Playgroud)

附录:在这种情况下,您需要额外的convStream功能.假设您想要将每两行连接成一行.你不能使用mapChunks.考虑当块是单个元素时,[bytestring]. mapChunks没有提供任何方法来访问下一个块,所以没有什么可以与此连接.随着convStream然而,这很简单:

concatPairs = convStream $ do
  line1 <- I.head
  line2 <- I.head
  return $ line1 `B.append` line2
Run Code Online (Sandbox Code Playgroud)

这在应用风格上看起来更好,

convStream $ B.append <$> I.head <*> I.head
Run Code Online (Sandbox Code Playgroud)

您可以认为convStream使用提供的iteratee不断消耗流的一部分,然后将转换后的版本发送给内部消费者.有时即使这样也不够通用,因为在每一步都会调用相同的迭代.在这种情况下,您可以使用unfoldConvStream在连续迭代之间传递状态.

convStream并且unfoldConvStream还允许monadic动作,因为流处理iteratee是monad变换器.