Все еще довольно в новинку для 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
. Я предполагаю IORef
s не строги по своей природе?
Кто-либо может рекомендовать лучшему, более короткому способу сделать это при хранении моей желаемой семантики?
Спасибо!
Причина, по которой ваша первая программа не работает, заключается в том, что 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
.
Это некрасиво, но вы можете принудительно прочитать содержимое, запросив длину
ввода и seq
со следующим оператором в вашем do-блоке. Но на самом деле решение состоит в использовании строгой версии hGetContents
. Я не знаю, как это называется.