我一直在慢慢地学习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
/pseq
和Strategies
的理解告诉我,所编写的程序应该可以工作,因为归约到范式应该意味着在hClose
求值时,handle
已经被读取了。
顺便说一句,作者为什么选择在一个地方使用seq
,而在另一个地方使用pseq
还不清楚,但是由于我的示例已经删除了任何并行操作,所以不应该(实际上也没有)有什么不同。
1条答案
按热度按时间3qpi33ja1#
引用我提交的bug的评论,
LazyByteString
的NFData
示例是正确的,尽管可能写得很钝。注意Chunk
构造函数的S.ByteString
字段是一个strict字段,StrictByteString
的NFData
示例只计算WHNF。问题出在别处:这是因为
rdeepseq chunk
是一个Eval LazyByteString
对象,它可以在chunk
实际上被deepseq
'ed之前到达WHNF(由seq
或pseq
证明)。换句话说,仅仅应用
rdeepseq
似乎是不够的。相反,我们必须使用withStrategy
(或者using
)来实际应用该策略。1.x API中的rnf
似乎有略微不同的行为。Control.DeepSeq
中的rnf
似乎有类似的行为。具体地说,用以下代码替换有问题的行可以解决问题:
或者使用
deepseq
,我们可以更简洁地说甚至