В 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
. Я думаю, IORef
не являются строгими по своей природе?
Может ли кто-нибудь рекомендовать лучший, более короткий способ сделать это, сохраняя мою желаемую семантику?
Спасибо!
Ответы
Ответ 1
Причина, по которой ваша первая программа не работает, заключается в том, что 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
.
Ответ 2
Я думаю, что самый простой способ решить эту проблему - использовать строгий IO:
import qualified System.IO.Strict as S
main = do
file <- S.readFile "filename"
writeFile "filename" file
Ответ 3
Вы можете дублировать файл 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), а затем заменить оригинал.
Ответ 4
Это некрасиво, но вы можете заставить содержимое читать, запросив length
ввода и seq
, используя его в следующем блоке do-block. Но на самом деле решение заключается в использовании строгой версии hGetContents
. Я не уверен, что он назвал.