2012-05-07 8 views
13

tengo el siguiente fragmento de código, lo que me pase a withFile:hGetContents ser demasiado perezoso

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

Aquí mano es un identificador de archivo válido, se abrió con ReadMode y parseCode es mi propia función que lee la entrada y devuelve un Tal vez Tal como está, la función falla y devuelve Nada. Si, en cambio escribo:

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

Me sale un Just, como debería.

Si hago openFile y hClose, tengo el mismo problema. ¿Por qué está pasando esto? ¿Cómo puedo resolverlo limpiamente?

Gracias

+0

¿Podría mostrar el código donde usa 'hClose' usted mismo? Parece que lo estás cerrando antes de que se requiera la entrada. –

Respuesta

12

hGetContents no es demasiado flojo, solo tiene que ser compuesto con otras cosas de manera apropiada para obtener el efecto deseado. Tal vez la situación sería más clara si se renombrara como exposeContentsToEvaluationAsNeededForTheRestOfTheAction o simplemente listen.

withFile abre el archivo, hace algo (o nada, como usted por favor - exactamente lo que usted requiere de él, en cualquier caso), y cierra el archivo.

Difícilmente será suficiente para llevar a cabo todos los misterios de 'IO perezoso', pero ahora considerar esta diferencia de horquillado

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 

crudamente, bad abre y cierra el archivo antes de hacer nada; good hace todo lo posible entre abrir y cerrar el archivo. Su primera acción fue similar a bad.withFile debe gobernar toda la acción que desee que dependa del asa.

No necesita un rigor enforcer si está trabajando con String, archivos pequeños, etc., solo una idea de cómo funciona la composición. De nuevo, en bad todo lo que 'hago' antes de cerrar el archivo es exposeContentsToEvaluationAsNeededForTheRestOfTheAction. En good compongo exposeContentsToEvaluationAsNeededForTheRestOfTheAction con el resto de la acción que tengo en mente, luego cierro el archivo.

El familiarizado length + seq truco mencionado por Patrick, o length + evaluate vale la pena conocer; su segunda acción con putStrLn txt fue una variante. Pero la reorganización es mejor, a menos que la pereza IO sea incorrecta para su caso.

$ 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 

No hace falta decir que ByteString y texto son vale la pena conocer, pero la reorganización con la evaluación en cuenta es mejor, ya que incluso con ellos las variantes perezosos son a menudo lo que necesita, y que luego implican agarrar las mismas distinciones entre formas de composición. Si está tratando con una de las (inmensa) clases de casos en los que este tipo de IO es inapropiado, eche un vistazo a enumerator, conduit y co., Todo maravilloso.

+0

Usar' evaluate' en la lectura 'String' no tiene sentido, ya que' evaluate' solo evalúa a WHNF, es decir, el primer constructor '(:)' . Sin embargo, podría ser apropiado usarlo en el resultado de, p. analizar el archivo, si eso depende de todo el contenido del archivo. – hammar

+0

Sí, esto se indica en la documentación; Lo mencioné porque se menciona en otro lugar aquí. – applicative

+0

Estos 'largos' hacks son realmente repugnantes. – applicative

0

Puede forzar el contenido de text a ser evaluados usando

length text `seq` return code 

como la última línea.

9

hGetContents usa IO lazy; solo lee del archivo a medida que fuerza más cadenas, y solo cierra el identificador del archivo cuando evalúa la cadena completa que devuelve. El problema es que lo está encerrando en withFile; en su lugar, simplemente use openFile y hGetContents directamente (o, más simplemente, readFile). El archivo se cerrará una vez que evalúe completamente la cadena. Algo como esto debe hacer el truco, para asegurarse de que el archivo está completamente leer y cierra inmediatamente al obligar a toda la cadena de antemano:

import Control.Exception (evaluate) 

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

situaciones poco intuitivo como este son una de las razones por las personas tienden a evitar perezoso IO estos días , pero lamentablemente no puede cambiar la definición de hGetContents. Una versión IO estricta de hGetContents está disponible en el paquete strict, pero probablemente no valga la pena depender del paquete solo para esa función.

Si desea evitar la sobrecarga que proviene de atravesar la cadena dos veces aquí, entonces probablemente debería considerar usar un tipo más eficiente que String, de todos modos; el tipo Text tiene strict IO equivalents para la mayor parte de la funcionalidad IO basada en String, as does ByteString (si se trata de datos binarios, en lugar de texto Unicode).

+2

Diría que * vale * dependiendo de 'estricto' solo para estricto' hGetContents'; ¡para eso es exactamente el paquete! No propague el síndrome NIH. –

+1

La definición de 'hGetContents' en' System.IO.Strict' es la familiar 'hGetContents h = IO.hGetContents h >> = \ s -> length s \' seq \ 'return s'; es el truco más antiguo del libro, no es una idea nueva de 'strict-0.3' – applicative

Cuestiones relacionadas