2012-05-07 31 views
13

Mam następujący fragment kodu, które mijam na withFile:hGetContents zbyt leniwy

text <- hGetContents hand 
let code = parseCode text 
return code 

Tu ręka jest prawidłowy uchwyt pliku, otwierane ReadMode i parseCode jest moja własna funkcji, które odczytuje dane wejściowe i zwraca a Może. Tak jak jest, funkcja zawodzi i zwraca Nic. Jeśli zamiast tego napiszę:

text <- hGetContents hand 
putStrLn text 
let code = parseCode text 
return code 

Dostaję Just, tak jak powinienem.

Jeśli mam sam openFile i hClose, mam ten sam problem. Dlaczego to się dzieje? Jak mogę go w prosty sposób rozwiązać?

Dzięki

+0

Czy możesz pokazać kod, w którym sam używasz 'hClose'? Wygląda na to, że zamykasz go przed wprowadzeniem danych wejściowych. –

Odpowiedz

12

hGetContents nie jest zbyt leniwy, po prostu musi być skomponowany z innymi rzeczami odpowiednio, aby uzyskać pożądany efekt. Być może sytuacja byłaby wyraźniejsza, gdyby zmieniono ją na exposeContentsToEvaluationAsNeededForTheRestOfTheAction lub po prostu listen.

withFile otwiera plik, robi coś (lub nic, jak chcesz - dokładnie to, czego potrzebujesz w każdym przypadku) i zamyka plik.

To ledwie wystarczy, aby wydobyć wszystkie tajemnice „leniwe IO”, ale teraz pod uwagę tę różnicę w bracketing

good file operation = withFile file ReadMode (hGetContents >=> operation >=> print) 
bad file operation = (withFile file ReadMode hGetContents) >>= operation >>= print 

-- *Main> good "lazyio.hs" (return . length) 
-- 503 
-- *Main> bad "lazyio.hs" (return . length) 
-- 0 

grubsza mówiąc, bad otwiera i zamyka plik przed robi nic; good robi wszystko pomiędzy otwarciem i zamknięciem pliku. Twoja pierwsza czynność była podobna do bad.withFile powinien regulować wszystkie czynności, które chcesz wykonać, a to zależy od uchwytu.

Nie musisz egzekwować przestrzegania rygorów, jeśli pracujesz z String, małymi plikami itp., Tylko pomysłem, jak działa ta kompozycja. Ponownie, w bad wszystko co robię przed zamknięciem pliku to exposeContentsToEvaluationAsNeededForTheRestOfTheAction. W good komponuję exposeContentsToEvaluationAsNeededForTheRestOfTheAction z resztą akcji, którą mam na myśli, a następnie zamykam plik.

Znajomy length + seq trik wspomina Patrick lub length + evaluate warto wiedzieć; Twoje drugie działanie z putStrLn txt było wariantem. Ale reorganizacja jest lepsza, chyba że leniwy IO jest zły dla twojej sprawy.

$ time ./bad 
bad: Prelude.last: empty list 
         -- no, lots of Chars there 
real 0m0.087s 

$ time ./good 
'\n'    -- right 
() 
real 0m15.977s 

$ time ./seqing 
Killed    -- hopeless, attempting to represent the file contents 
    real 1m54.065s -- in memory as a linked list, before finding out the last char 

Jest rzeczą oczywistą, że ByteString i tekst są warte wiedząc o, ale reorganizacja z oceny w umyśle jest lepszy, ponieważ nawet z nimi leniwym warianty są często to, co trzeba, a oni następnie angażować chwytając te same wyróżnienia między formami kompozycji. Jeśli masz do czynienia z jedną z (ogromnych) klas przypadków, w których ten rodzaj IO jest nieodpowiedni, spójrz na enumerator, conduit i wsp., Wszystkie cudowne.

+0

Użycie' evaluate' w odczytywaniu 'String' jest bez sensu, ponieważ' evaluate' zwraca się tylko do WHNF, tzn. pierwszy konstruktor '(:)' . Jednak może być właściwe użycie go na wynik np. parsowanie pliku, jeśli zależy to od całej zawartości pliku. – hammar

+0

Tak, jest to określone w dokumentacji; Wspomniałem o tym, ponieważ jest tu wspomniany w innym miejscu. – applicative

+0

Te "długie" hacki są naprawdę odrażające. – applicative

0

można wymusić zawartość z text być oceniane przy użyciu

length text `seq` return code 

w ostatnim wierszu.

9

hGetContents używa leniwego IO; odczytuje tylko z pliku, gdy wymuszasz więcej ciągu znaków i zamyka on tylko uchwyt pliku, gdy ocenisz cały łańcuch, który zwraca. Problem polega na tym, że zamykasz go w withFile; zamiast tego wystarczy bezpośrednio użyć openFile i hGetContents (lub, po prostu, readFile). Plik zostanie zamknięty po zakończeniu oceny łańcucha. Coś jak to powinno wystarczyć, aby upewnić się, że plik jest w pełni odczytać i zamknięty natychmiast zmuszając cały ciąg wcześniej:

import Control.Exception (evaluate) 

readCode :: FilePath -> IO Code 
readCode fileName = do 
    text <- readFile fileName 
    evaluate (length text) 
    return (parseCode text) 

nieintuicyjne sytuacje takie jak ta są jednym z powodów, ludzie mają tendencję do unikania leniwe IO te dni , ale niestety nie można zmienić definicji hGetContents. Ścisła wersja IO hGetContents jest dostępna w pakiecie strict, ale prawdopodobnie nie jest ona warta w zależności od pakietu tylko dla tej jednej funkcji.

Jeśli chcesz uniknąć narzutu, który pochodzi od dwukrotnego przejścia przez sznur, powinieneś rozważyć użycie bardziej wydajnego typu niż w przypadku String; Typ Text ma strict IO equivalents dla większości funkcji IO opartych na String, as does ByteString (jeśli masz do czynienia z danymi binarnymi, a nie tekstem Unicode).

+2

Powiedziałbym, że * jest * warta w zależności od 'strict' tylko dla ścisłej' hGetContents'; właśnie po to jest ta paczka! Nie propaguj zespołu NIH. –

+1

Definicja 'hGetContents' w' System.IO.Strict' jest znanym 'hGetContents h = IO.hGetContents h >> = \ s -> length s \' seq \ 'return s'; to najstarsza sztuczka w książce, a nie nowatorski pomysł z 'strict-0.3' – applicative