Haskell IO和关闭文件

Jay*_*rod 39 io file-io haskell

当我在Haskell中打开一个文件进行读取时,我发现在关闭文件后我无法使用该文件的内容.例如,该程序将打印文件的内容:

main = do inFile <- openFile "foo" ReadMode
          contents <- hGetContents inFile
          putStr contents
          hClose inFile
Run Code Online (Sandbox Code Playgroud)

我期望将putStr线与hClose线交换将没有任何效果,但此程序不打印任何内容:

main = do inFile <- openFile "foo" ReadMode
          contents <- hGetContents inFile
          hClose inFile
          putStr contents
Run Code Online (Sandbox Code Playgroud)

为什么会这样?我猜这与懒惰评估有关,但我认为这些表达式会被排序,所以不会出现问题.你会如何实现这样的功能readFile

luq*_*qui 38

正如其他人所说,这是因为懒惰的评价.此操作后手柄处于半关闭状态,并在读取所有数据时自动关闭.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
Run Code Online (Sandbox Code Playgroud)

然而,现在,没有人使用字符串进行文件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
Run Code Online (Sandbox Code Playgroud)

ByteStrings是获取大字符串(如文件内容)的方法.它们比String(= [Char])更快,内存效率更高.

笔记:

我只是为了方便从Control.Parallel.Strategies导入了rnf.你可以很容易地自己写一些类似的东西:

  forceList [] = ()
  forceList (x:xs) = forceList xs
Run Code Online (Sandbox Code Playgroud)

这只会强制遍历列表的主干(而不是值),这将具有读取整个文件的效果.

懒惰的I/O被专家们视为邪恶; 我建议暂时对大多数文件I/O使用严格的字节串.烤箱中有一些解决方案试图带回可组合的增量读数,其中最有希望的是由Oleg称为"Iteratee".

  • 两条评论.首先,很多人仍然使用字符串来处理文件IO.当你想要从文件中获取的是一个字符串时,它们就完美了!其次,Lazy IO不被许多人视为邪恶,但它被认为是棘手的.它让我们以非常低的语法开销做各种简洁的事情,但代价是保持某些有限类型的操作推理和等式推理. (10认同)
  • 遇到了这个答案,谢谢,@ liqui!只是想指出(3年后)你的`rnf`应该是:`rnf contents'seq'hClose inFile`,反引号围绕`seq`.此外,`rnf`已移至`Control.DeepSeq`. (3认同)

小智 5

[更新:Prelude.readFile 会导致如下所述的问题,但切换到使用 Data.ByteString 的所有版本都有效:我不再遇到异常。]

Haskell 新手,但目前我不相信“readFile 是严格的,完成后关闭文件”的说法:

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 ()
Run Code Online (Sandbox Code Playgroud)

这与我正在测试的文件上的情况一样有效,但是如果您注释掉 putStrLn ,那么显然 writeFile 会失败。(有趣的是,Haskell 异常消息是多么蹩脚,缺少行号等?)

Test> go "Foo.hs"
reading
writing
Exception: Foo.hs: openFile: permission denied (Permission denied)
Test> 
Run Code Online (Sandbox Code Playgroud)

?!?!?