read putstrln print haskell file-io io

print - putstrln haskell



Haskell IO y archivos de cierre (6)

Cuando abro un archivo para leer en Haskell, descubro que no puedo usar el contenido del archivo después de cerrarlo. Por ejemplo, este programa imprimirá el contenido de un archivo:

main = do inFile <- openFile "foo" ReadMode contents <- hGetContents inFile putStr contents hClose inFile

putStr que intercambiar la línea putStr con la línea hClose no tendría ningún efecto, pero este programa no imprime nada:

main = do inFile <- openFile "foo" ReadMode contents <- hGetContents inFile hClose inFile putStr contents

¿Por qué pasó esto? Supongo que tiene algo que ver con la evaluación perezosa, pero pensé que estas expresiones se secuenciarían para que no haya ningún problema. ¿Cómo implementarías una función como readFile ?


Como otros han declarado, es a causa de la evaluación perezosa. El mango está medio cerrado después de esta operación y se cerrará automáticamente cuando se lean todos los datos. Tanto hGetContents como readFile son flojos de esta manera. En los casos en los que tenga problemas con los controles se mantienen abiertos, normalmente solo fuerza la lectura. Aquí está la manera fácil:

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

En estos días, sin embargo, ya nadie usa cadenas para E / S de archivos. La nueva forma es usar Data.ByteString (disponible en hackage) y Data.ByteString.Lazy cuando desee lecturas diferidas.

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

ByteStrings es el camino a seguir para grandes cadenas (como contenido de archivos). Son mucho más rápidos y más eficientes en cuanto a la memoria que String (= [Char]).

Notas:

Importé rnf desde Control.Parallel.Strategies solo por conveniencia. Podrías escribir algo así como tú muy fácilmente:

forceList [] = () forceList (x:xs) = forceList xs

Esto solo obliga a atravesar el lomo (no los valores) de la lista, lo que tendría el efecto de leer todo el archivo.

Lazy I / O se está convirtiendo en malvada por los expertos; Recomiendo usar cadenas de bytes estrictas para la mayoría de las E / S de archivo por el momento. Hay algunas soluciones en el horno que intentan traer de vuelta lecturas incrementales compostables, la más prometedora de las cuales se llama "Iteratee" por Oleg.


Como se indicó anteriormente, hGetContents es flojo. readFile es estricto y cierra el archivo cuando termina:

main = do contents <- readFile "foo" putStr contents

cede lo siguiente en abrazos

> main blahblahblah

donde foo es

blahblahblah

Curiosamente, seq solo garantizará que se lea parte de la entrada, no toda:

main = do inFile <- openFile "foo" ReadMode contents <- hGetContents $! inFile contents `seq` hClose inFile putStr contents

rendimientos

> main b

Un buen recurso es: Hacer que los programas Haskell sean más rápidos y más pequeños: hGetContents, hClose, readFile


Esto es porque hGetContents aún no hace nada: es E / S perezosa. Solo cuando utiliza la cadena de resultados, el archivo se lee realmente (o la parte necesaria). Si desea obligarlo a leer, puede calcular su longitud y usar la función seq para forzar la evaluación de la longitud. Lazy I / O puede ser genial, pero también puede ser confuso.

Para obtener más información, consulte la parte sobre E / S perezosa en Real World Haskell, por ejemplo.


La explicación es bastante larga para ser incluida aquí. Perdónenme por ofrecer solo un pequeño consejo: debe leer sobre "manejadores de archivos semicerrados" y "UnsafePerformIO".

En resumen, este comportamiento es un compromiso de diseño entre una claridad semántica y una evaluación perezosa. Debe posponer hClose hasta que esté absolutamente seguro de que no hará nada con el contenido del archivo (como, llámelo en el manejador de errores, o algo así), o use algo más además de hGetContents para obtener contenido de archivos sin dificultad.


Si desea mantener su IO flojo, pero para hacerlo de forma segura para que no se produzcan errores como este, utilice un paquete diseñado para esto, como safe-lazy-io . (Sin embargo, safe-lazy-io no es compatible con bytestring I / O).


[ Actualización : Prelude.readFile causa problemas como se describe a continuación, pero cambiar a usar las versiones de Data.ByteString funciona: ya no recibo la excepción.]

Haskell novato aquí, pero actualmente no compro la afirmación de que "readFile es estricto y cierra el archivo cuando está listo":

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 ()

Eso funciona tal como se encuentra en el archivo con el que estaba probando, pero si comenta el putStrLn, aparentemente el writeFile falla. (Interesante cómo son los mensajes de excepción de Haskell cojos, sin números de línea, etc.)

Test> go "Foo.hs" reading writing Exception: Foo.hs: openFile: permission denied (Permission denied) Test>

?!?!?