Lazy IO tiene el problema de que liberar cualquier recurso que haya adquirido es algo impredecible, ya que depende de cómo su programa consume los datos: su "patrón de demanda". Una vez que su programa descarta la última referencia al recurso, el GC eventualmente se ejecutará y liberará ese recurso.
Los flujos perezosos son un estilo muy conveniente para programar. Es por eso que las tuberías de shell son tan divertidas y populares.
Sin embargo, si los recursos están limitados (como en escenarios de alto rendimiento o entornos de producción que esperan escalar hasta los límites de la máquina), confiar en el GC para limpiar puede ser una garantía insuficiente.
A veces es necesario liberar recursos con entusiasmo para mejorar la escalabilidad.
Entonces, ¿cuáles son las alternativas a la IO perezosa que no significan renunciar al procesamiento incremental (que a su vez consumiría demasiados recursos)? Bueno, hemos foldl
basado el procesamiento, también conocido como iterados o enumeradores, introducido por Oleg Kiselyov a finales de la década de 2000 y, desde entonces, popularizado por una serie de proyectos basados en redes.
En lugar de procesar los datos como flujos diferidos, o en un lote enorme, abstraemos el procesamiento estricto basado en fragmentos, con la finalización garantizada del recurso una vez que se lee el último fragmento. Esa es la esencia de la programación basada en iteratee y ofrece limitaciones de recursos muy agradables.
La desventaja de la E / S basada en iteratee es que tiene un modelo de programación algo incómodo (más o menos análogo a la programación basada en eventos, frente al buen control basado en subprocesos). Definitivamente es una técnica avanzada, en cualquier lenguaje de programación. Y para la gran mayoría de los problemas de programación, la IO perezosa es completamente satisfactoria. Sin embargo, si va a abrir muchos archivos, o hablará en muchos sockets, o si va a utilizar muchos recursos simultáneos, un enfoque de iteración (o enumerador) podría tener sentido.
Dons ha proporcionado una muy buena respuesta, pero ha dejado fuera lo que es (para mí) una de las características más atractivas de los iterados: hacen que sea más fácil razonar sobre la gestión del espacio porque los datos antiguos deben retenerse explícitamente. Considerar:
average :: [Float] -> Float average xs = sum xs / length xs
Esta es una pérdida de espacio bien conocida, porque toda la lista
xs
debe conservarse en la memoria para calcular tantosum
ylength
. Es posible hacer un consumidor eficiente creando un pliegue:average2 :: [Float] -> Float average2 xs = uncurry (/) <$> foldl (\(sumT, n) x -> (sumT+x, n+1)) (0,0) xs -- N.B. this will build up thunks as written, use a strict pair and foldl'
Pero es algo inconveniente tener que hacer esto para cada procesador de flujo. Hay algunas generalizaciones ( Conal Elliott - Beautiful Fold Zipping ), pero no parece que hayan tenido éxito . Sin embargo, los iterados pueden brindarle un nivel de expresión similar.
aveIter = uncurry (/) <$> I.zip I.sum I.length
Esto no es tan eficiente como un pliegue porque la lista todavía se repite varias veces; sin embargo, se recopila en fragmentos para que los datos antiguos se puedan recolectar de manera eficiente. Para romper esa propiedad, es necesario retener explícitamente toda la entrada, como con stream2list:
badAveIter = (\xs -> sum xs / length xs) <$> I.stream2list
El estado de los iterados como modelo de programación es un trabajo en progreso, sin embargo, es mucho mejor que hace un año. Estamos aprendiendo lo combinadores son útiles (por ejemplo
zip
,breakE
,enumWith
) y que son por lo menos, con el resultado de que incorporados iteratees y combinadores proporcionan continuamente más expresividad.Dicho esto, Dons tiene razón en que son una técnica avanzada; Ciertamente no los usaría para cada problema de E / S.
fuente
Utilizo E / S diferidas en el código de producción todo el tiempo. Es solo un problema en ciertas circunstancias, como mencionó Don. Pero con solo leer algunos archivos, funciona bien.
fuente
Actualización: Recientemente en haskell-cafe, Oleg Kiseljov demostró que
unsafeInterleaveST
(que se usa para implementar IO perezoso dentro de la mónada ST) es muy inseguro: rompe el razonamiento ecuacional. Demuestra que permite construir debad_ctx :: ((Bool,Bool) -> Bool) -> Bool
tal manera que> bad_ctx (\(x,y) -> x == y) True > bad_ctx (\(x,y) -> y == x) False
aunque
==
es conmutativo.Otro problema con la E / S diferida: la operación de E / S real puede aplazarse hasta que sea demasiado tarde, por ejemplo, después de cerrar el archivo. Citando de Haskell Wiki - Problemas con IO perezoso :
A menudo, esto es inesperado y un error fácil de cometer.
Consulte también: Tres ejemplos de problemas con E / S diferida .
fuente
hGetContents
y nowithFile
tiene sentido porque el primero pone el identificador en un estado "pseudo-cerrado" y manejará el cierre por usted (perezosamente) por lo que el código es exactamente equivalente areadFile
, o inclusoopenFile
sinhClose
. Eso es básicamente lo que es la E / S perezosa . Si no lo usareadFile
,getContents
ohGetContents
no usa E / S diferida. Por ejemplo,line <- withFile "test.txt" ReadMode hGetLine
funciona bien.hGetContents
se encargará de cerrar el archivo por usted, también está permitido cerrarlo usted mismo "antes" y ayuda a garantizar que los recursos se liberen de manera predecible.Otro problema con IO perezoso que no se ha mencionado hasta ahora es que tiene un comportamiento sorprendente. En un programa Haskell normal, a veces puede ser difícil predecir cuándo se evalúa cada parte de su programa, pero afortunadamente, debido a la pureza, realmente no importa a menos que tenga problemas de rendimiento. Cuando se introduce la IO perezosa, el orden de evaluación de su código en realidad tiene un efecto en su significado, por lo que los cambios que está acostumbrado a considerar como inofensivos pueden causarle problemas genuinos.
Como ejemplo, aquí hay una pregunta sobre el código que parece razonable pero se vuelve más confuso por IO diferido: withFile vs. openFile
Estos problemas no son invariablemente fatales, pero es otra cosa en la que pensar, y un dolor de cabeza lo suficientemente severo que personalmente evito IO perezoso a menos que haya un problema real con hacer todo el trabajo por adelantado.
fuente