【问题标题】:Haskell iteratee: simple worked example of stripping trailing whitespaceHaskell iteratee:去除尾随空格的简单示例
【发布时间】:2011-10-02 09:03:52
【问题描述】:

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

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

import Data.ByteString.Char8

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

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

【问题讨论】:

    标签: haskell iteration bytestring iterate


    【解决方案1】:

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

    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)
    

    注意rstripE 中额外的map。外部流(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
    

    组合运算符&gt;&lt;&gt; 遵循与箭头运算符&gt;&gt;&gt; 相同的顺序。 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 在连续迭代之间传递状态。

    convStreamunfoldConvStream 也允许单子操作,因为流处理迭代器是单子转换器。

    【讨论】:

    • 约翰,感谢您提供如此详细的答案!这正是我所需要的。
    • 两个小注意事项:rstripE 的类型需要一个类型类限定符 (Monad m) =>,而我的 rstrip 函数需要在末尾粘上一个换行符以与 enumLinesBS 集成。否则,它就像一个魅力!
    • 感谢您指出这一点,我已经添加了类型类上下文。
    猜你喜欢
    • 2011-08-28
    • 2019-12-16
    • 1970-01-01
    • 2010-11-22
    • 2021-10-22
    • 1970-01-01
    • 1970-01-01
    • 2017-01-22
    • 1970-01-01
    相关资源
    最近更新 更多