什么时候调用 runResourceT on streaming-bytestring?

When to call runResourceT on streaming-bytestring?

我是 Haskell 初学者,仍在学习 monad 转换器。

我正在尝试使用 streaming-bytestring library to read a binary file, process chunks of bytes, and print the result as each chunk is processed. I believe this is the popular streaming library that provides an alternative to lazy bytestrings. It appears the authors copy-pasted the lazy bytestring 文档并添加了一些任意示例。

这些示例提到了 runResourceT,但没有讨论它是什么或如何使用它。似乎应该在执行操作的任何流字节串函数上使用 runResourceT。这很好,但是如果我正在读取处理块并打印它们的无限流怎么办?我是否应该在每次处理块时都调用 runResourceT?

我的代码是这样的:

import qualified Data.ByteString.Streaming as BSS
import System.TimeIt

main = timeIt $ processByteChunks $ BSS.drop 100 $ BSS.readFile "filename"

而且我不确定如何将 processByteChunks 组织为遍历二进制文件的递归函数。

如果我只调用 runResourceT 一次,它会在打印前读取无限文件,对吧?这似乎很糟糕。

main = timeIt $ runResourceT $ processByteChunks $ BSS.drop 100 $ BSS.readFile "filename"

ResourceT monad 会在您用完资源后及时清理它们。在这种情况下,它将确保在使用流时关闭 BSS.readFile 打开的文件句柄。 (除非流真的是无限的,在这种情况下我猜它不会。)

在您的应用程序中,您只想调用它一次,因为您不想在读取所有块之前关闭文件。别担心——它与输出时间或类似的东西无关。

这是一个递归 processByteChunks 应该可以工作的示例。它将延迟读取并在块被延迟读取时生成输出:

import Control.Monad.IO.Class
import Control.Monad.Trans.Resource
import qualified Data.ByteString.Streaming as BSS
import qualified Data.ByteString as BS
import System.TimeIt

main :: IO ()
main = timeIt $ runResourceT $
  processByteChunks $ BSS.drop 100 $ BSS.readFile "filename"

processByteChunks :: MonadIO m => BSS.ByteString m () -> m ()
processByteChunks = go 0 0
  where go len nulls stream = do
          m <- BSS.unconsChunk stream
          case m of
            Just (bs, stream') -> do
              let len' = len + BS.length bs
                  nulls' = nulls + BS.length (BS.filter (==0) bs)
              liftIO $ print $ "cumulative length=" ++ show len'
                                      ++ ", nulls=" ++ show nulls'
              go len' nulls' stream'
            Nothing -> return ()