【问题标题】:Haskell IO and closing filesHaskell IO 和关闭文件
【发布时间】:2010-09-22 17:54:07
【问题描述】:

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

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

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

【问题讨论】:

    标签: haskell file-io io


    【解决方案1】:

    这里的解释相当长。请原谅我只提供一个简短的提示:您需要阅读“半封闭文件句柄”和“unsafePerformIO”。

    简而言之 - 这种行为是语义清晰和惰性评估之间的设计折衷。你应该推迟 hClose 直到你完全确定你不会对文件内容做任何事情(比如,在错误处理程序中调用它,或者类似的东西),或者使用除了 hGetContents 之外的其他东西来非懒惰地获取文件内容。

    【讨论】:

    • 你能链接任何关于这些主题的好东西吗?除了关于特定问题的稀疏文档和邮件列表消息外,我找不到太多其他信息。
    • 我认为unsafePerformIO 与此无关。也许unsafeInterleaveIO.
    【解决方案2】:

    这是因为 hGetContents 还没有做任何事情:它是惰性 I/O。只有当您使用结果字符串时,才会实际读取文件(或需要的部分)。如果要强制读取它,可以计算它的长度,并使用 seq 函数强制计算长度。惰性 I/O 可能很酷,但也可能令人困惑。

    如需了解更多信息,请参阅 Real World Haskell 中的the part about lazy I/O

    【讨论】:

      【解决方案3】:

      如前所述,hGetContents 是懒惰的。 readFile 是严格的,完成后关闭文件:

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

      在拥抱中产生以下效果

      > main
      blahblahblah
      

      foo 在哪里

      blahblahblah
      

      有趣的是,seq 只会保证读取输入的部分,而不是全部:

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

      产量

      > main
      b
      

      一个好的资源是:Making Haskell programs faster and smaller: hGetContents, hClose, readFile

      【讨论】:

      • readFile 使用 hGetContents 并且不关闭文件。根据 Real World Haskell 和源代码本身,它很懒惰。
      • 首先,readFile 并不严格,如前所述,其次,$!hGetContents 的使用完全是多余的。
      【解决方案4】:

      正如其他人所说,这是因为懒惰的评估。此操作后句柄处于半关闭状态,读取完所有数据后将自动关闭。 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
      

      然而,这些天来,没有人再将字符串用于文件 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 (= [Char]) 更快,内存效率更高。

      注意事项:

      我从 Control.Parallel.Strategies 导入 rnf 只是为了方便。您可以很容易地自己编写类似的内容:

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

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

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

      【讨论】:

      • 两个厘米。首先,很多人仍然使用字符串进行文件 IO。当您想从文件中获取的是字符串时,它们非常好!其次,Lazy IO 并不被很多人认为是邪恶的,但它被认为是棘手的。它让我们能够以非常低的句法开销完成各种简洁的事情,但代价是在维护等式推理的同时维护某些有限类型的操作推理。
      • 遇到了这个答案,谢谢@liqui!只是想指出(3 年后)你的rnf 应该是:rnf contents 'seq' hClose inFile,在seq 周围加上反引号。此外,rnf 已移至 Control.DeepSeq
      • @Peter,我想我们在谈论 lazy IO,你的评论没有提到。
      • “严重的服务器端编程中的惰性 IO 是不专业的” – Oleg Kiselyov
      【解决方案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 ()
      

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

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

      ?!?!?

      【讨论】:

      • 我刚刚运行了你的代码。 GHCI 说:openFile: resource busy (file is locked)。这与 readFile 的惰性是一致的。
      【解决方案6】:

      如果您想让您的 IO 保持惰性,但要安全地执行此操作,以免发生此类错误,请使用为此设计的包,例如 safe-lazy-io。 (但是,safe-lazy-io 不支持字节串 I/O。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-06-02
        • 2019-03-28
        • 2021-03-24
        • 2014-09-18
        • 1970-01-01
        • 2014-09-23
        • 2020-02-03
        相关资源
        最近更新 更多