在Haskell中,我想读取一个文件然后写入它。我需要严格注释吗?

时间:2010-03-26 22:48:04

标签: haskell file-io io lazy-evaluation

Haskell仍然很新......

我想读取文件的内容,用它做一些可能涉及IO的事情(现在使用putStrLn),然后将新内容写入同一个文件。

我想出了:

doit :: String -> IO ()
doit file = do
    contents <- withFile tagfile ReadMode $ \h -> hGetContents h
    putStrLn contents
    withFile tagfile WriteMode $ \h -> hPutStrLn h "new content"

然而,由于懒惰,这不起作用。文件内容不会打印。我发现this post可以很好地解释它。

建议的解决方案是在putStrLn中包含withFile

doit :: String -> IO ()
doit file = do
    withFile tagfile ReadMode $ \h -> do
        contents <- hGetContents h
        putStrLn contents
    withFile tagfile WriteMode $ \h -> hPutStrLn h "new content"

这有效,但这不是我想做的。 I中的操作最终会替换putStrLn可能很长,我不想让文件保持打开状态。一般来说,我只希望能够获取文件内容,然后在使用该内容之前将其关闭。

我想出的解决方案如下:

doit :: String -> IO ()
doit file = do
    c <- newIORef ""
    withFile tagfile ReadMode $ \h -> do
        a <- hGetContents h
        writeIORef c $! a
    d <- readIORef c
    putStrLn d
    withFile tagfile WriteMode $ \h -> hPutStrLn h "Test"

然而,我觉得这很长,有点混淆。我认为我不需要IORef只是为了得到一个值,但我需要“放置”来放置文件内容。此外,如果没有$!的严格性注释writeIORef,它仍然无效。我猜IORef本质上并不严格?

有人可以推荐一种更好,更短的方法来保持我想要的语义吗?

谢谢!

4 个答案:

答案 0 :(得分:21)

第一个程序不起作用的原因是withFile在执行传递给它的IO操作后关闭文件。在您的情况下,IO操作为hGetContents立即读取文件,但仅在需要其内容时才执行。当您尝试打印文件的内容时,withFile已经关闭了文件,因此读取失败(无声)。

您可以通过不重新发明轮子并仅使用readFilewriteFile修复问题:

doit file = do
    contents <- readFile file
    putStrLn contents
    writeFile file "new content"

但是假设您希望新内容依赖于旧内容。那么你通常不能简单地做

doit file = do
    contents <- readFile file
    writeFile file $ process contents

因为writeFile可能影响readFile返回的内容(请记住,它实际上还没有读取文件)。或者,根据您的操作系统,您可能无法在两个单独的句柄上打开相同的文件进行读写。简单但丑陋的解决方法是

doit file = do
    contents <- readFile file
    length contents `seq` (writeFile file $ process contents)

会强制readFile读取整个文件并在writeFile操作开始之前将其关闭。

答案 1 :(得分:10)

我认为解决此问题的最简单方法是使用严格的IO:

import qualified System.IO.Strict as S
main = do
    file <- S.readFile "filename"
    writeFile "filename" file

答案 2 :(得分:1)

您可以复制文件句柄,使用原始文件进行惰性写入(到文件末尾),然后用另一个进行惰性读取。因此,在附加到文件的情况下不会涉及严格注释。

import System.IO
import GHC.IO.Handle

main :: IO ()
main = do
    h <- openFile "filename" ReadWriteMode
    h2 <- hDuplicate h

    hSeek h2 AbsoluteSeek 0
    originalFileContents <- hGetContents h2
    putStrLn originalFileContents

    hSeek h SeekFromEnd 0
    hPutStrLn h $ concatMap ("{new_contents}" ++) (lines originalFileContents)

    hClose h2
    hClose h

hDuplicate函数由GHC.IO.Handle模块提供。

  

返回原始句柄的副本,并带有自己的缓冲区。但是,两个Handles将共享一个文件指针。在复制句柄之前,刷新原始句柄的缓冲区,包括丢弃任何输入数据。

使用hSeek,您可以在读取或写入之前设置句柄的位置。

但我不确定使用&#34; AbsoluteSeek 0&#34;而不是&#34; SeekFromEnd 0&#34;用于写入,即覆盖内容。一般来说,我建议先写一个临时文件,例如使用openTempFile(来自System.IO),然后替换原文。

答案 3 :(得分:0)

这很难看,但你可以通过询问输入的length来强制读取内容,并用你的do-block中的下一个语句seq来读取它。但实际上解决方案是使用严格版本的hGetContents。我不确定它叫什么。