我刚从最新的来源安装了GHC,现在我的程序给了我一条关于“关闭句柄的延迟读取”的错误消息。这是什么意思?
答案 0 :(得分:10)
基本的懒惰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并返回一个常量(例如()
),则不必担心这一点。如果您需要通过延迟读取生成实际值,则需要确保在返回之前强制该值。在上面的示例中,您可以强制字符串为&#34;普通形式&#34;使用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)
另一个需要考虑的功能是readFile
。 readFile
保持文件处于打开状态,直到读完文件为止。您应该只使用readFile
,但是,如果您知道您实际上会要求文件的全部内容 - 否则您可能会泄漏文件描述符。
根据Haskell报告,一旦句柄关闭,字符串的内容就会固定。
在过去,GHC只是在句柄关闭时缓冲的任何内容结束时简单地结束了字符串。例如,如果您在关闭句柄之前检查了字符串的前10个字符,并且GHC已经缓冲了额外的634个字符,但没有到达文件的末尾,那么您将得到一个包含644个字符的普通字符串。这是新用户之间混淆的常见原因,也是生产代码中偶尔出现的错误来源。
从GHC 7.10.1开始,这种行为正在发生变化。当你关闭一个懒惰的句柄时,它现在有效地在缓冲区的末尾放置一个异常而不是通常的:""
。因此,如果您尝试检查超出文件关闭点的字符串,您将收到错误消息。