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>
?!?!?