design - ¿Por qué Haskell no tiene una I Monad(solo para entrada, a diferencia de la mónada IO)?
monads (5)
Creo que la división entre código puro e impuro es algo arbitraria. Depende de dónde pongas la barrera. Los diseñadores de Haskell decidieron separar claramente la parte funcional pura del lenguaje del resto.
Así que tenemos IO mónada que incorpora todos los efectos posibles (tan diferentes, como lecturas / escrituras de disco, redes, acceso a la memoria). Y el lenguaje impone una división clara mediante el tipo de devolución. Y esto induce un tipo de pensamiento que divide todo en puro e impuro.
Si se trata de la seguridad de la información, sería muy natural separar la lectura y la escritura. Pero para el objetivo inicial de haskell, ser un lenguaje funcional puro y holgazán, era una exageración.
Conceptualmente, parece que un cálculo que realiza un resultado es muy diferente de uno que realiza solo la entrada. Este último es, en cierto sentido, mucho más puro.
Yo, por mi parte, me gustaría tener una manera de separar las partes de entrada de mi programa de las que realmente podrían escribir algo.
Entonces, ¿por qué no hay entrada solo Monad?
¿Alguna razón por la cual no funcionaría tener una I mónada (y una O Mónada, que podría combinarse en la IO Monad)?
Editar : La mayoría de las veces me refería a la entrada como leer archivos, sin interactuar con el usuario. Este es también mi caso de uso, donde puedo suponer que los archivos de entrada no cambian durante la ejecución del programa (de lo contrario, está bien obtener un comportamiento indefinido).
Cuando obtienes entrada, causas efectos secundarios que cambian tanto el estado del mundo exterior (la entrada se consume) como tu programa (se usa la entrada). Cuando sale, causa efectos secundarios que solo cambian el estado del mundo exterior (se produce la salida); el hecho de salir solo no cambia el estado de su programa. Entonces, podrías decir que O
es más "puro" que I
.
Excepto que la salida realmente cambia el estado de ejecución de su programa (No repetirá la misma operación de salida una y otra vez, tiene que tener algún tipo de cambio de estado para seguir adelante). Todo depende de cómo lo mires. Pero es mucho más fácil agrupar la suciedad de la entrada y la salida en la misma mónada. Cualquier programa útil entrará y saldrá. Puede categorizar las operaciones que usa en una u otra, pero no veo una razón convincente para emplear el sistema de tipo para la tarea.
O estás jugando con el mundo exterior o no.
El lado ''Entrada'' de la mónada IO tiene la misma salida que entrada. Si consume una línea de entrada, el hecho de que haya consumido esa entrada se comunica al exterior y también sirve para registrarlo como estado impuro (es decir, no consume la misma línea nuevamente más adelante); es tan solo una operación de salida como un putStrLn
. Además, las operaciones de entrada deben ordenarse con respecto a las operaciones de salida; esto nuevamente limita cuánto puedes separar los dos.
Si quieres una mónada pura de solo lectura, probablemente deberías usar la mónada del lector en su lugar.
Dicho esto, parece que estás un poco confundido acerca de lo que pueden hacer las combinaciones de mónadas. Si bien puedes combinar dos mónadas (asumiendo que una es un transformador de mónada) y obtener algún tipo de semántica híbrida, debes poder ejecutar el resultado . Es decir, incluso si pudieras definir un IT (OT Identity) r
, ¿cómo lo ejecutas? No tiene una mónada IO
raíz en este caso, por lo que main debe ser una función pura. Lo que significaría que tendrías main = runIdentity . runOT . runIT $ ...
main = runIdentity . runOT . runIT $ ...
main = runIdentity . runOT . runIT $ ...
Lo cual es una tontería, ya que estás obteniendo efectos impuros desde un contexto puro.
En otras palabras, el tipo de la mónada IO debe ser reparado. No puede ser un tipo transformado seleccionable por el usuario, ya que su tipo está clavado en main
. Claro, podrías llamarlo I (O Identity)
, pero no ganas nada; O (I Identity)
sería un tipo inútil, como sería I []
o O Maybe
, porque nunca podrías ejecutar ninguno de estos.
Por supuesto, si se deja IO
como el tipo de mónada IO
fundamental, podría definir rutinas como:
runI :: I Identity r -> IO r
Esto funciona, pero de nuevo, no puedes tener nada debajo de esta mónada muy fácilmente, y no estás obteniendo mucho de esta complejidad. ¿Qué significaría tener una mónada de salida transformada en una mónada base de lista, de todos modos?
No estoy de acuerdo con la respuesta de bdonlan. Es cierto que ni la entrada ni la salida son más "puras", pero son bastante diferentes. Es bastante válido criticar a IO como el único "bin del pecado" donde todos los efectos se agolpan juntos, y hace que sea más difícil garantizar ciertas propiedades. Por ejemplo, si tiene muchas funciones que sabe que solo leen de ciertas ubicaciones de memoria y que nunca podrían alterar esas ubicaciones, sería bueno saber que puede reordenar su ejecución. O si tiene un programa que usa forkIO y MVars, sería bueno saber, en función de su tipo, que tampoco está leyendo / etc / passwd.
Además, uno puede componer efectos monádicos de una manera además de solo transformadores apilados. No puedes hacer esto con todas las mónadas (solo mónadas gratuitas), pero para un caso como este eso es todo lo que realmente necesitas. El paquete iospec, por ejemplo, proporciona una especificación pura de IO: no separa la lectura y la escritura, pero sí los separa de, por ejemplo, STM, MVars, forkIO, soforth.
http://hackage.haskell.org/package/IOSpec
Las ideas clave sobre cómo puede combinar las diferentes mónadas de manera limpia se describen en el documento a la carta "Tipos de datos" (lectura excelente, muy influyente, no puedo recomendar lo suficiente, etc.).
Respuesta corta: IO no es E / S. Otras personas tienen respuestas más largas si lo desean.