Haskell IO i zamykanie plików
Kiedy otwieram plik do czytania w Haskell, odkryłem, że nie mogę użyć zawartości pliku po zamknięciu go. Na przykład ten program wyświetli zawartość pliku:
main = do inFile <- openFile "foo" ReadMode
contents <- hGetContents inFile
putStr contents
hClose inFile
Spodziewałem się, że zamiana linii putStr
z linią hClose
nie przyniesie żadnego efektu, ale ten program nic nie wyświetla:
main = do inFile <- openFile "foo" ReadMode
contents <- hGetContents inFile
hClose inFile
putStr contents
Dlaczego tak się dzieje? Domyślam się, że ma to coś wspólnego z leniwą oceną, ale myślałem, że te wyrażenia zostaną zsekwencjonowane, więc tam nie byłoby problemu. Jak zaimplementowałbyś funkcję readFile
? 6 answers
Jak stwierdzili inni, wynika to z leniwej oceny. Uchwyt jest w połowie zamknięty po tej operacji i zostanie automatycznie zamknięty po odczytaniu wszystkich danych. Zarówno hGetContents, jak i readFile są w ten sposób leniwe. W przypadkach, gdy masz problemy z otwartymi uchwytami, Zwykle wymusza się odczyt. Oto prosty sposób:
import Control.Parallel.Strategies (rnf)
-- rnf means "reduce to normal form"
main = do inFile <- openFile "foo"
contents <- hGetContents inFile
rnf contents `seq` hClose inFile -- force the whole file to be read, then close
putStr contents
W dzisiejszych czasach jednak nikt już nie używa ciągów znaków do wejścia/wyjścia plików. Nowym sposobem jest wykorzystanie danych.ByteString (dostępny na hackage), oraz Data.ByteString.Leniwy kiedy chcesz [[6]}leniwy czyta.
import qualified Data.ByteString as Str
main = do contents <- Str.readFile "foo"
-- readFile is strict, so the the entire string is read here
Str.putStr contents
Bytestringi to sposób na duże łańcuchy znaków (np. zawartość pliku). Są one znacznie szybsze i wydajniejsze niż String (=[Char]).
Uwagi:
Zaimportowałem rnf z Control.Równolegle.Strategie tylko dla wygody. Można by coś takiego napisać samemu dość łatwo:
forceList [] = ()
forceList (x:xs) = forceList xs
Wymusza to tylko przesunięcie kręgosłupa (nie wartości) listy, które miałoby efekt odczytu całego pliku.
Leniwe I/o staje się uważane przez ekspertów za złe; zalecam używanie ścisłych bajtów dla większości We/Wy plików na razie. W piecu jest kilka rozwiązań, które próbują przywrócić komponowalne odczyty przyrostowe, z których najbardziej obiecująca jest tzw. "Iteratee" Olega.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-08 12:32:34
[Update : Prelude.readFile powoduje problemy opisane poniżej, ale przejście na używanie danych.Wersje ByteString wszystko działa: nie dostaję już wyjątku.]
Haskell newbie tutaj, ale obecnie nie kupuję twierdzenia, że "readFile jest ścisły, i zamyka plik, gdy jest zrobione":
go fname = do
putStrLn "reading"
body <- readFile fname
let body' = "foo" ++ body ++ "bar"
putStrLn body' -- comment this out to get a runtime exception.
putStrLn "writing"
writeFile fname body'
return ()
To działa tak, jak na pliku, z którym testowałem, ale jeśli skomentujesz putStrLn, to najwyraźniej plik writeFile zawiedzie. (Ciekawe jak lamerskie Komunikaty WYJĄTKÓW Haskell są, brak numerów linii itp.?)
Test> go "Foo.hs"
reading
writing
Exception: Foo.hs: openFile: permission denied (Permission denied)
Test>
?!?!?
Dzieje się tak dlatego, że hGetContents jeszcze nic nie robi: jest to leniwe we / wy. tylko gdy użyjesz ciągu wynikowego plik jest rzeczywiście odczytany (lub jego część, która jest potrzebna). Jeśli chcesz wymusić jego odczyt, możesz obliczyć jego długość i użyć funkcji seq, aby wymusić Długość do obliczenia. Leniwe I / O może być fajne, ale może być również mylące.
Aby uzyskać więcej informacji, zobacz Część o leniwych I/O w prawdziwym świecie Haskell, na przykład.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-17 22:58:06
Jak już wspomniano, hGetContents
jest leniwy. readFile
jest ścisłe i zamyka plik po zakończeniu:
main = do contents <- readFile "foo"
putStr contents
Daje następujące w uściskach
> main
blahblahblah
Gdzie foo
jest
blahblahblah
Co ciekawe, seq
zagwarantuje tylko, że odczytana zostanie część danych wejściowych, a nie całość:
main = do inFile <- openFile "foo" ReadMode
contents <- hGetContents $! inFile
contents `seq` hClose inFile
putStr contents
> main
b
Dobrym zasobem jest: tworzenie programów Haskell szybszych i mniejszych: hGetContents, hClose, readFile
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-18 02:06:46
Jeśli chcesz, aby Twoje IO było leniwe, ale aby zrobić to bezpiecznie, aby takie błędy nie wystąpiły, użyj pakietu zaprojektowanego do tego celu, takiego jak safe-lazy-io. (Jednak safe-Lazy-io nie obsługuje bajtestring I / O.)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-03 10:42:17
Wyjaśnienie jest dość długie. Proszę mi wybaczyć tylko krótką końcówkę: należy przeczytać o "półzamkniętych uchwytach do plików" i "unsafePerformIO".
W skrócie-to zachowanie jest konstrukcyjnym kompromisem między jasnością semantyczną a leniwą oceną. Powinieneś albo odłożyć hClose, dopóki nie będziesz absolutnie pewien, że nie będziesz robił nic z zawartością pliku( np. wywołaj go w programie obsługi błędów lub innym), lub użyj czegoś innego niż hGetContents, aby uzyskać zawartość pliku nie-leniwie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-17 20:52:42