В Haskell я хочу прочитать файл, а затем записать в него. Нужна ли аннотация строгости? - PullRequest
16 голосов
/ 27 марта 2010

Все еще довольно плохо знаком с Haskell ..

Я хочу прочитать содержимое файла, сделать что-то с ним, возможно, с использованием ввода-вывода (сейчас использую putStrLn), а затем записать новое содержимое в тот же файл.

Я придумал:

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

Однако это не работает из-за лени. Содержимое файла не распечатывается. Я нашел этот пост , который хорошо это объясняет.

Предлагаемое решение заключается в том, чтобы включить 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"

Это работает, но это не то, что я хочу сделать. Операция, которую я в конечном итоге заменю 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 s не являются строгими по своей природе?

Кто-нибудь может порекомендовать лучший, более короткий способ сделать это, сохранив желаемую семантику?

Спасибо!

Ответы [ 4 ]

21 голосов
/ 28 марта 2010

Причина, по которой ваша первая программа не работает, заключается в том, что withFile закрывает файл после выполнения переданного ему действия ввода-вывода. В вашем случае IO-действие - hGetContents, которое не читает файл сразу, а только по требованию его содержимого. К тому времени, когда вы пытаетесь распечатать содержимое файла, withFile уже закрыл файл, поэтому чтение завершается неудачно (без вывода сообщений).

Вы можете исправить эту проблему, не изобретая велосипед и просто используя readFile и writeFile:

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.

10 голосов
/ 29 мая 2012

Я думаю, что самый простой способ решить эту проблему - использовать строгий ввод-вывод:

import qualified System.IO.Strict as S
main = do
    file <- S.readFile "filename"
    writeFile "filename" file
1 голос
/ 08 мая 2016

Вы можете продублировать файл Handle, выполнить ленивую запись с оригинальной (до конца файла) и ленивое чтение с другой. Поэтому при добавлении в файл аннотации строгости не применяются.

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.

Возвращает копию оригинального дескриптора с собственным буфером. Однако эти два дескриптора будут совместно использовать файловый указатель. Буфер исходного дескриптора сбрасывается, включая удаление любых входных данных, до того, как дескриптор будет дублирован.

С помощью hSeek вы можете установить положение ручки перед чтением или записью.

Но я не уверен, насколько надежным было бы использовать «AbsoluteSeek 0» вместо «SeekFromEnd 0» для записи, то есть перезаписи содержимого. Обычно я бы предложил сначала записать во временный файл, например, с помощью openTempFile (из System.IO), а затем заменить оригинальный.

0 голосов
/ 27 марта 2010

Это уродливо, но вы можете заставить содержимое быть прочитанным, запросив length ввода и seq ', используя следующий оператор в вашем блоке do. Но на самом деле решение заключается в использовании строгой версии hGetContents. Я не уверен, как это называется.

...