7

我刚刚从最新来源安装了 GHC,现在我的程序给了我一条关于“关闭句柄延迟读取”的错误消息。这是什么意思?

4

1 回答 1

13

基本的惰性 I/O 原语 ,hGetContents产生一个String惰性 - 它只根据需要从句柄中读取,以生成程序实际需要的字符串部分。但是,一旦关闭句柄,就无法再从句柄中读取,如果您尝试检查尚未读取的字符串部分,您将收到此异常。例如,假设你写

main = do
  most <- withFile "myfile" ReadMode
                (\h -> do
                         s <- hGetContents h
                         let (first12,rest) = splitAt 12 s
                         print first12
                         return rest)
  putStrLn most

GHC 打开myfile并设置它以延迟读取我们绑定到的字符串s。它实际上并没有开始从文件中读取。然后它设置了一个惰性计算以在 12 个字符后拆分字符串。然后print强制计算,GHC 读取myfile至少 12 个字符长的块,并打印出前 12 个字符。然后它在完成时关闭文件withFile,并尝试打印出其余部分。如果文件比 GHC 缓冲的块长,一旦到达块的末尾,您将得到延迟读取异常。

如何避免这个问题

关闭文件或从withFile. 如果您传递给的函数withFile只是执行一些 IO 并返回一个常量(例如()),您无需担心这一点。如果您需要它从惰性读取中产生一个真正的值,您需要确保在返回之前充分强制该值。在上面的示例中,您可以使用模块中的函数或运算符将字符串强制为“正常形式” Control.DeepSeq

return $!! rest

这可确保在withFile关闭文件之前实际读取字符串的其余部分。$!!如果您返回的是从文件内容计算的某个值,只要它是类的实例,该方法也可以很好地工作NFData。在这种情况下以及许多其他情况下,最好将用于处理文件内容的其余代码简单地移动到传递给的函数中withFile,如下所示:

main = withFile "myfile" ReadMode
            (\h -> do
                     s <- hGetContents h
                     let (first12,rest) = splitAt 12 s
                     print first12
                     putStrLn rest)

作为替代方案,要考虑的另一个功能是readFilereadFile保持文件打开,直到它完成读取文件。但是,如果您知道您实际上将需要文件的全部内容,则应该只使用readFile—— 否则您可能会泄漏文件描述符。

历史

根据 Haskell 报告,一旦句柄关闭,字符串的内容就固定了。

过去,GHC 只是在句柄关闭时缓冲的任何内容的末尾结束字符串。例如,如果您在关闭句柄之前检查了字符串的前 10 个字符,并且 GHC 已经缓冲了额外的 634 个字符,但没有到达文件末尾,那么您将得到一个 644 个字符的普通字符串。这是新用户混淆的常见来源,也是生产代码中偶尔出现的错误来源。

从 GHC 7.10.1 开始,这种行为正在发生变化。当你关闭一个你懒惰读取的句柄时,它现在有效地将异常放在缓冲区的末尾,而不是通常的:"". 因此,如果您尝试检查超出文件关闭点的字符串,您将收到一条错误消息。

于 2014-11-15T18:44:28.857 回答