haskell 为什么使用rdeepseq不足以在关闭句柄之前完成对它的阅读?

eulz3vhy  于 2022-12-19  发布在  其他
关注(0)|答案(1)|浏览(147)

我一直在慢慢地学习Real World Haskell,在第24章中,作者详细介绍了一个程序,可以分块阅读文件,然后以MapReduce的方式处理它,但是在hGetBufSome: illegal operation (handle is closed)中失败了,该程序被缩减为MRE,并更新为现代的Haskell,如下所示:

import Control.Exception (finally)
import Control.Parallel.Strategies (NFData, rdeepseq)
import qualified Data.ByteString.Lazy.Char8 as LB
import GHC.Conc (pseq)
import System.Environment (getArgs)
import System.IO

main :: IO ()
main = do
  args <- getArgs
  res <- chunkedReadWith id (head args)
  print res

chunkedReadWith ::
  (NFData a) =>
  (LB.ByteString -> a) ->
  FilePath ->
  IO a
chunkedReadWith process path = do
  (chunk, handle) <- chunkedRead path
  let r = process chunk
  -- the RHS of finally is for some reason being run before the handle is
  -- finished being used. removing it allows the program to run, with the obvious
  -- disadvantage of leaving closing the handle to the garbage collector
  (rdeepseq r `seq` return r) `finally` hClose handle

chunkedRead ::
  FilePath ->
  IO (LB.ByteString, Handle)
chunkedRead path = do
  h <- openFile path ReadMode
  chunk <- LB.take 64 <$> LB.hGetContents h
  rdeepseq chunk `pseq` return (chunk, h)

我怀疑这是由于没有充分地强制执行严格的求值而导致的问题,但是我目前对seq/pseqStrategies的理解告诉我,所编写的程序应该可以工作,因为归约到范式应该意味着在hClose求值时,handle已经被读取了。
顺便说一句,作者为什么选择在一个地方使用seq,而在另一个地方使用pseq还不清楚,但是由于我的示例已经删除了任何并行操作,所以不应该(实际上也没有)有什么不同。

3qpi33ja

3qpi33ja1#

引用我提交的bug的评论,
LazyByteStringNFData示例是正确的,尽管可能写得很钝。注意Chunk构造函数的S.ByteString字段是一个strict字段,StrictByteStringNFData示例只计算WHNF。
问题出在别处:这是因为rdeepseq chunk是一个Eval LazyByteString对象,它可以在chunk实际上被deepseq 'ed之前到达WHNF(由seqpseq证明)。
换句话说,仅仅应用rdeepseq似乎是不够的。相反,我们必须使用withStrategy(或者using)来实际应用该策略。1.x API中的rnf似乎有略微不同的行为。Control.DeepSeq中的rnf似乎有类似的行为。
具体地说,用以下代码替换有问题的行可以解决问题:

(withStrategy rdeepseq r `seq` return r) `finally` mapM_ hClose handles

或者使用deepseq,我们可以更简洁地说

(rnf r `seq` return r) `finally` mapM_ hClose handles

甚至

(r `deepseq` return r) `finally` mapM_ hClose handles

相关问题