2010-05-06 15 views
10

Estoy tratando de obtener una comprensión más profunda de la pereza en Haskell.¿Cómo funcionan la pereza y la E/S juntas en Haskell?

me estaba imaginando el siguiente fragmento de hoy:

data Image = Image { name :: String, pixels :: String } 

image :: String -> IO Image 
image path = Image path <$> readFile path 

El atractivo aquí es que yo podría simplemente crear una instancia de imagen y pasarlo alrededor; si necesito los datos de imagen que se leería con pereza - si no, el costo de tiempo y la memoria de la lectura del archivo se evitaría:

main = do 
    image <- image "file" 
    putStrLn $ length $ pixels image 

Pero es que cómo funciona realmente? ¿Cómo es la pereza compatible con IO? ¿Se llamará readFile independientemente de si accedo a pixels image o el tiempo de ejecución dejará ese thunk no evaluado si nunca me refiero a él?

Si la imagen es de hecho leída perezosamente, entonces ¿no es posible que las acciones de E/S puedan ocurrir desordenadas? Por ejemplo, ¿qué ocurre si, inmediatamente después de llamar al image, borro el archivo? Ahora la llamada putStrLn no encontrará nada cuando intente leer.

Respuesta

17

¿Cómo es la pereza compatible con E/S?

Respuesta corta: No lo es.


Respuesta larga: IO acciones se ordenan en sentido estricto, de más o menos las razones que usted está pensando. Cualquier cálculo puro hecho con los resultados puede ser flojo, por supuesto; por ejemplo, si lee en un archivo, realiza algún procesamiento y luego imprime algunos de los resultados, es probable que no se evalúe ningún procesamiento que no necesite el resultado. Sin embargo, se leerá todo el archivo, incluso partes que nunca usas. Si quieres que perezosa de E/S, que tiene aproximadamente dos opciones:

  • rodar su propia rutina perezosa de carga explícitas y tal, como lo haría en cualquier idioma estricta. Parece molesto, concedido, pero por otro lado Haskell hace un buen lenguaje estricto e imperativo. Si quieres probar algo nuevo e interesante, prueba mirar Iteratees.

  • Cheat like a cheating cheater. Las funciones such as hGetContents harán las E/S por encargo, sin preguntas. ¿Cuál es el truco? (Técnicamente) rompe la transparencia referencial. El código puro indirectamente puede causar efectos secundarios, y pueden ocurrir cosas divertidas que impliquen ordenar los efectos secundarios si su código es realmente intrincado. hGetContents y amigos están implementados using unsafeInterleaveIO, que es ... exactamente lo que dice en la lata. No es tan probable que explote en tu cara como si usara unsafePerformIO, pero considérate advertido.

+1

Gracias por esta respuesta! De hecho, fue la descripción de hGetContents de RWH la que me confundió sobre este tema. No me di cuenta de que era un caso especial y usé llamadas IO inseguras debajo. Entonces, básicamente, ¿mi ejemplo lee el archivo tan pronto como se procesa la acción readFile? Eso parece mucho más consistente si es así. – Bill

+5

@Bill: Aquí está la implementación de readFile, directamente de las librerías estándar de GHC: 'readFile name = openFile name ReadMode >> = hGetContents' Así que no, tu ejemplo cae dentro de la categoría" tramposo infiel ". Dicho esto, las funciones de E/S perezosas suelen ser lo suficientemente seguras para la mayoría del uso diario, así que no te preocupes demasiado a menos que la pureza sea muy importante para ti. –

+1

Sé que Oleg dice 'unsafeInterleaveIO' rompe la transparencia referencial, pero no estoy de acuerdo. Yo diría que es meramente no determinista, como muchas cosas en la mónada de 'IO'. ¿'GetCurrentTime' rompe la transparencia referencial porque puedo usarla para determinar cuál de las dos funciones extrínsecamente iguales se implementa de manera más eficiente? –

9

Lazy I/O breaks Haskell's pureza. Los resultados de readFile se producen de forma perezosa, a pedido. El orden en que se producen las acciones de E/S no es fijo, por lo que sí, podrían producirse "fuera de servicio". El problema de eliminar el archivo antes de tirar de los píxeles es real. En resumen, la E/S perezosa es una gran conveniencia, pero es una herramienta con bordes muy afilados.

El libro en Real World Haskell tiene un lengthy treatment of lazy I/O y repasa algunas de las trampas.

Cuestiones relacionadas