В Haskell я хочу считать файл и затем записать в него. Мне нужна аннотация строгости?

Все еще довольно в новинку для 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"

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

Решение, предложенное там, состоит в том, чтобы включать 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. Я предполагаю IORefs не строги по своей природе?

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

Спасибо!

16
задан Don Stewart 24 April 2011 в 16:23
поделиться

2 ответа

Причина, по которой ваша первая программа не работает, заключается в том, что withFile закрывает файл после выполнения переданного ему действия ввода-вывода. В вашем случае действие ввода-вывода - 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 .

21
ответ дан 30 November 2019 в 17:27
поделиться

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

0
ответ дан 30 November 2019 в 17:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: