In Haskell, I want to read a file and then write to it. Do I need strictness annotation?

▼魔方 西西 提交于 2019-12-03 06:08:06

The reason your first program does not work is that withFile closes the file after executing the IO action passed to it. In your case, the IO action is hGetContents which does not read the file right away, but only as its contents are demanded. By the time you try to print the file's contents, withFile has already closed the file, so the read fails (silently).

You can fix this issue by not reinventing the wheel and simply using readFile and writeFile:

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

But suppose you want the new content to depend on the old content. Then you cannot, generally, simply do

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

because the writeFile may affect what the readFile returns (remember, it has not actually read the file yet). Or, depending on your operating system, you might not be able to open the same file for reading and writing on two separate handles. The simple but ugly workaround is

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

which will force readFile to read the entire file and close it before the writeFile action can begin.

I think the easiest way to solve this problem is useing strict IO:

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

You can duplicate the file Handle, do lazy write with original one (to the end of file) and lazy read with another. So no strictness annotation involved in case of appending to file.

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

The hDuplicate function is provided by GHC.IO.Handle module.

Returns a duplicate of the original handle, with its own buffer. The two Handles will share a file pointer, however. The original handle's buffer is flushed, including discarding any input data, before the handle is duplicated.

With hSeek you can set position of the handle before reading or writing.

But I'm not sure how reliable would be using "AbsoluteSeek 0" instead of "SeekFromEnd 0" for writing, i.e. overwriting contents. Generally I would suggest to write to a temporary file first, for example using openTempFile (from System.IO), and then replace original.

It's ugly but you can force the contents to be read by asking for the length of the input and seq'ing it with the next statement in your do-block. But really the solution is to use a strict version of hGetContents. I'm not sure what it's called.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!