2012-05-07 9 views
13

Ich habe den folgenden Code-Schnipsel, die ich withFile passieren:hGetContents ist zu faul

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

Hier Hand ist eine gültige Datei-Handle, geöffnet mit ReadMode und parseCode ist meine eigene Funktion, die die Eingabe liest und gibt ein Vielleicht zurück. So wie es ist, schlägt die Funktion fehl und gibt Nothing zurück. Wenn ich stattdessen schreibe:

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

Ich bekomme ein Just, wie ich sollte. Wenn ich openFile und hClose selbst mache, habe ich das gleiche Problem. Warum passiert dies? Wie kann ich es sauber lösen?

Dank

+0

Können Sie den Code anzeigen, in dem Sie 'hClose' selbst verwenden? Es klingt, als würden Sie es schließen, bevor die Eingabe erforderlich war. –

Antwort

12

hGetContents ist nicht zu faul, es muss nur mit anderen Dingen entsprechend zusammengesetzt werden, um den gewünschten Effekt zu erhalten. Vielleicht wäre die Situation klarer, wenn sie in exposeContentsToEvaluationAsNeededForTheRestOfTheAction oder nur listen umbenannt würde.

withFile öffnet die Datei, tut etwas (oder nichts, wie Sie bitte - genau das, was Sie in jedem Fall benötigen), und schließt die Datei.

Es wird kaum ausreichen, alle Geheimnisse des ‚faulen IO‘ zu bringen, aber bedenken Sie jetzt diesen Unterschied in

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 

Kunstlos setzen, bad öffnet und schließt die Datei, bevor er etwas tut; good macht alles zwischen dem Öffnen und Schließen der Datei. Ihre erste Aktion war verwandt mit bad.withFile sollte alle von Ihnen gewünschten Aktionen steuern, die vom Handle abhängen.

Sie brauchen keine Strenge Enforcer, wenn Sie mit String, kleine Dateien usw. arbeiten, nur eine Idee, wie die Zusammensetzung funktioniert. Nochmals, in bad alles, was ich vor dem Schließen der Datei "tun" ist exposeContentsToEvaluationAsNeededForTheRestOfTheAction. In good komponiere ich exposeContentsToEvaluationAsNeededForTheRestOfTheAction mit dem Rest der Aktion, die ich im Sinn habe, dann schließe die Datei.

Der vertraute length + seq Trick von Patrick erwähnt, oder length + evaluate ist wert zu wissen; Ihre zweite Aktion mit putStrLn txt war eine Variante. Aber Reorganisation ist besser, es sei denn, faule IO ist falsch für Ihren Fall.

$ 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 

Es versteht sich, dass ByteString und Text wert sind: Jetzt wird gespart, aber Reorganisation mit Auswertung im Auge ist besser, da mit ihnen auch die faulen Varianten häufig sind, was Sie brauchen, und sie dann beinhalten die gleichen Unterschiede zu erfassen zwischen Formen der Zusammensetzung. Wenn Sie mit einer der (immensen) Klasse von Fällen, wo diese Art von IO ist unangemessen ist, werfen Sie einen Blick auf enumerator, conduit und co., Alle wunderbar.

+0

Die Verwendung von' evaluate' auf dem gelesenen 'String' ist sinnlos, da' evaluate' nur zu WHNF, dh dem ersten '(:)' Konstruktor, ausgewertet wird . Es könnte jedoch angebracht sein, sie auf das Ergebnis von z.B. Parsing der Datei, wenn das vom gesamten Inhalt der Datei abhängt. – hammar

+0

Ja, das steht in der Dokumentation; Ich habe es erwähnt, weil es hier an anderer Stelle erwähnt wird. – applicative

+0

Diese "Längen" Hacks sind wirklich abstoßend. – applicative

0

Sie die Inhalte von text zwingen kann zu

length text `seq` return code 

als letzte Zeile verwendet werden ausgewertet.

9

hGetContents verwendet Lazy IO; Es liest nur aus der Datei, wenn Sie mehr von der Zeichenfolge erzwingen, und es schließt nur das Dateihandle, wenn Sie die gesamte zurückgegebene Zeichenfolge auswerten. Das Problem ist, dass Sie es in withFile einschließen; Verwenden Sie stattdessen einfach openFile und hGetContents direkt (oder einfacher readFile). Die Datei wird immer noch geschlossen, sobald Sie die Zeichenfolge vollständig ausgewertet haben. So etwas sollte es tun, um sicherzustellen, dass die Datei vollständig gelesen und sofort geschlossen durch die gesamte Zeichenfolge zwingt vorher:

import Control.Exception (evaluate) 

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

unintuitive Situationen wie diese sind einer der Gründe, neigen die Menschen faul IO in diesen Tagen zu vermeiden , aber leider können Sie die Definition von hGetContents nicht ändern. Eine strikte IO-Version von hGetContents ist im strict Paket verfügbar, aber es ist wahrscheinlich nicht wert, abhängig von dem Paket nur für diese eine Funktion.

Wenn Sie den Overhead vermeiden wollen, der durch das zweimalige Traversieren der Zeichenfolge entsteht, sollten Sie wahrscheinlich einen effizienteren Typ als String verwenden. der Typ hat strict IO equivalents für viele der String-basierte IO-Funktionalität, as does ByteString (wenn Sie mit binären Daten statt Unicode-Text beschäftigen).

+2

Ich würde sagen, es ist * wert * abhängig von 'strict' nur für strikte' hGetContents'; Genau dafür ist das Paket gedacht! Vermehren Sie das NIH-Syndrom nicht. –

+1

Die Definition von 'hGetContents' in' System.IO.Strict' ist die bekannte 'hGetContents h = IO.hGetContents h >> = \ s -> Länge s \' seq \ 'return s'; es ist der älteste Trick in diesem Buch, keine neue Idee von 'strict-0.3' – applicative