dfe*_*uer 7 haskell ghc lazy-io
我刚刚从最新的来源安装了GHC,现在我的程序给了我一个关于"关闭句柄的延迟读取"的错误消息.这是什么意思?
dfe*_*uer 11
基本的惰性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
Run Code Online (Sandbox Code Playgroud)
GHC打开myfile并将其设置为懒惰读入我们必须绑定的字符串s.它实际上并没有从文件中读取.然后它设置一个延迟计算,以便在12个字符后分割字符串.然后print强制计算,并且GHC读取myfile至少12个字符长的块,并打印出前12个字符.然后在完成时关闭文件withFile,并尝试打印其余文件.如果文件长于缓冲的块GHC,则一旦到达块的末尾,您将获得延迟的读取异常.
在关闭文件或返回文件之前,您需要确保实际上已经阅读了所需的所有内容withFile.如果传递给的函数withFile只执行某些IO并返回常量(例如()),则无需担心这一点.如果你需要通过延迟读取产生实际值,你需要确保在返回之前充分强制该值.在上面的示例中,您可以使用Control.DeepSeq模块中的函数或运算符将字符串强制为"normal form" :
return $!! rest
Run Code Online (Sandbox Code Playgroud)
这可确保在withFile关闭文件之前实际读取字符串的其余部分.$!!如果返回的是从文件内容计算的某个值,只要它是NFData类的实例,那么该方法也可以很好地工作.在这种情况下,以及许多其他情况下,将代码的其余部分移动到传递给函数的文件内容中更好withFile,如下所示:
main = withFile "myfile" ReadMode
(\h -> do
s <- hGetContents h
let (first12,rest) = splitAt 12 s
print first12
putStrLn rest)
Run Code Online (Sandbox Code Playgroud)
作为替代方案,另一个需要考虑的功能是readFile.readFile保持文件打开,直到它完成读取文件.readFile但是,如果您知道实际需要文件的全部内容,则应该使用- 否则您可能会泄漏文件描述符.
根据Haskell报告,一旦句柄关闭,字符串的内容就会固定.
在过去,GHC只是在句柄关闭时缓冲的任何内容结束时简单地结束了字符串.例如,如果您在关闭句柄之前检查了字符串的前10个字符,并且GHC已经缓冲了额外的634个字符,但没有到达文件的末尾,那么您将得到一个包含644个字符的普通字符串.这是新用户之间混淆的常见原因,也是生产代码中偶尔出现的错误来源.
从GHC 7.10.1开始,这种行为正在发生变化.当你关闭一个懒惰的句柄时,它现在有效地在缓冲区的末尾放置一个异常而不是通常的异常:"".因此,如果您尝试检查超出文件关闭点的字符串,您将收到错误消息.