Haskell IO和closures文件

当我打开一个阅读Haskell的文件时,我发现在closures文件后我不能使用这个文件的内容。 例如,这个程序将打印一个文件的内容:

main = do inFile <- openFile "foo" ReadMode contents <- hGetContents inFile putStr contents hClose inFile 

我预计交换putStr线与hClose线将没有任何效果,但是这个程序什么也不打印:

 main = do inFile <- openFile "foo" ReadMode contents <- hGetContents inFile hClose inFile putStr contents 

为什么会这样呢? 我猜这与懒惰评估有关,但我认为这些expression式会被sorting,所以不会有问题。 你将如何实现像readFile这样的函数?

正如其他人所说,这是因为懒惰的评价。 操作完成后,手柄半关,所有数据读取后自动closures。 hGetContents和readFile都是这样懒的。 如果您在处理开放时遇到问题,通常您只需强制读取。 这是简单的方法:

 import Control.Parallel.Strategies (rnf) -- rnf means "reduce to normal form" main = do inFile <- openFile "foo" contents <- hGetContents inFile rnf contents `seq` hClose inFile -- force the whole file to be read, then close putStr contents 

但是,现在,没有人再使用string作为文件I / O了。 新的方法是使用Data.ByteString(在hackage上可用)和Data.ByteString.Lazy当你懒读。

 import qualified Data.ByteString as Str main = do contents <- Str.readFile "foo" -- readFile is strict, so the the entire string is read here Str.putStr contents 

ByteStrings是大string(如文件内容)的方式。 它们比String(= [Char])快得多,内存效率也高。

笔记:

我只是为了方便而从Control.Parallel.Strategies中导入了rnf。 你可以很容易地写下类似的东西:

  forceList [] = () forceList (x:xs) = forceList xs 

这只是强制遍历列表的脊柱(而不是值),这将有读取整个文件的效果。

惰性I / O被专家视为邪恶; 我build议暂时使用严格的字节串作为大部分的文件I / O。 在烤箱中有几个解决scheme试图恢复可组合的增量读取,其中最有希望的是被Oleg称为“Iteratee”。

[ 更新 :Prelude.readFile导致如下所述的问题,但切换到使用Data.ByteString的版本的一切工作:我不再有exception。]

Haskell新手在这里,但目前我不买声称“readFile是严格的,并closures文件,当它完成”:

 go fname = do putStrLn "reading" body <- readFile fname let body' = "foo" ++ body ++ "bar" putStrLn body' -- comment this out to get a runtime exception. putStrLn "writing" writeFile fname body' return () 

这是工作,因为它站在我正在testing的文件,但如果你注释掉putStrLn然后显然writeFile失败。 (有趣的是,Haskell的exception消息如何,缺less行号等?)

 Test> go "Foo.hs" reading writing Exception: Foo.hs: openFile: permission denied (Permission denied) Test> 

?!?!?

这是因为hGetContents还没有做任何事情:它是懒惰的I / O。 只有当你使用结果string时,文件实际上是读取的(或者是需要的部分)。 如果你想迫使它被读取,你可以计算它的长度,并使用seq函数来强制评估长度。 懒惰的I / O可以很酷,但也可能会令人困惑。

有关更多信息,请参阅Real World Haskell中关于惰性I / O的部分 。

如前所述, hGetContents是懒惰的。 readFile是严格的,并在完成时closures文件:

 main = do contents <- readFile "foo" putStr contents 

在Hugs中产生以下内容

 > main blahblahblah 

foo在哪里

 blahblahblah 

有趣的是, seq只能保证input的一部分被读取,而不是全部:

 main = do inFile <- openFile "foo" ReadMode contents <- hGetContents $! inFile contents `seq` hClose inFile putStr contents 

产量

 > main b 

一个好的资源是: 使Haskell程序更快,更小:hGetContents,hClose,readFile

如果你想保持你的IO懒,但要安全地做到这一点,这样的错误不会发生,请使用为此devise的包如safe-lazy-io 。 (但是,safe-lazy-io不支持字节串I / O。)

这里的解释是相当长的。 原谅我只分配一个短小的提示:你需要阅读“半封闭文件句柄”和“unsafePerformIO”。

简言之 – 这种行为是语义清晰度与懒惰评估之间的devise折中。 你应该推迟hClose,直到你完全确定你正在用文件内容做任何事情(比如,在error handling程序中调用它,或者使用其他方法),或者使用hGetContents之外的其他方法来非懒惰地获取文件内容。