代码之家  ›  专栏  ›  技术社区  ›  Jay Conrod

Haskell IO和关闭文件

  •  38
  • Jay Conrod  · 技术社区  · 17 年前

    当我在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 ?

    6 回复  |  直到 15 年前
        1
  •  39
  •   luqui    15 年前

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

    ByTestRing是处理大字符串(如文件内容)的方法。它们比字符串([Char])快得多,内存效率也更高。

    笔记:

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

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

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

        2
  •  4
  •   raould raould    17 年前

    使现代化 :Prelude.readFile会导致如下所述的问题,但切换到使用数据。ByteString的所有版本都有效:我不再获得异常。]

    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 ()
    

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

    ?!?!?

        3
  •  2
  •   Erik Hesselink Renaud    17 年前

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

    有关详细信息,请参阅 the part about lazy I/O 例如,在现实世界中,哈斯克尔。

        4
  •  1
  •   Charles Duffy    17 年前

    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

        5
  •  1
  •   Robin Green    15 年前

    safe-lazy-io . (但是,安全懒惰io不支持bytestring I/O。)

        6
  •  0
  •   ADEpt    17 年前

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

    简言之,这种行为是语义清晰和惰性评估之间的设计折衷。您应该推迟hClose,直到您完全确定不会对文件内容执行任何操作(例如,在错误处理程序中调用它或类似的操作),或者使用hGetContents之外的其他方法来非惰性地获取文件内容。