La innovación más evidente notada por las personas nuevas en Haskell es que existe una separación entre el mundo impuro que se ocupa de comunicarse con el mundo exterior y el mundo puro de la computación y los algoritmos. Una pregunta frecuente para principiantes es "¿Cómo puedo deshacerme IO
, es decir, convertirme IO a
en a
?" La forma de hacerlo es usar mónadas (u otras abstracciones) para escribir código que realice IO y efectos de cadenas. Este código recopila datos del mundo exterior, crea un modelo de él, realiza algunos cálculos, posiblemente empleando código puro, y genera el resultado.
En lo que respecta al modelo anterior, no veo nada terriblemente malo en manipular las GUI en la IO
mónada. El mayor problema que surge de este estilo es que los módulos ya no son componibles, es decir, pierdo la mayor parte de mi conocimiento sobre el orden de ejecución global de las declaraciones en mi programa. Para recuperarlo, tengo que aplicar un razonamiento similar al del código GUI concurrente e imperativo. Mientras tanto, para el código impuro, no GUI, el orden de ejecución es obvio debido a la definición del operador de la IO
mónada >==
(al menos siempre que haya un solo hilo). Para el código puro, no importa en absoluto, excepto en casos de esquina para aumentar el rendimiento o evitar evaluaciones que resulten ⊥
.
La mayor diferencia filosófica entre la consola y el IO gráfico es que los programas que implementan los primeros generalmente se escriben en estilo sincrónico. Esto es posible porque hay (dejando de lado las señales y otros descriptores de archivos abiertos) solo una fuente de eventos: el flujo de bytes comúnmente llamado stdin
. Sin embargo, las GUI son inherentemente asíncronas y tienen que reaccionar a los eventos del teclado y los clics del mouse.
Una filosofía popular de hacer IO asíncrona de manera funcional se llama Programación Reactiva Funcional (FRP). Recientemente obtuvo mucha tracción en lenguajes impuros y no funcionales gracias a bibliotecas como ReactiveX y frameworks como Elm. En pocas palabras, es como ver elementos de la GUI y otras cosas (como archivos, relojes, alarmas, teclado, mouse) como orígenes de eventos, llamados "observables", que emiten flujos de eventos. Estos eventos se combinan mediante operadores familiares, tales como map
, foldl
, zip
, filter
, concat
, join
, etc, para producir nuevas fuentes. Esto es útil porque el estado del programa en sí mismo puede verse como parte scanl . map reactToEvents $ zipN <eventStreams>
del programa, donde N
es igual al número de observables que el programa haya considerado.
Trabajar con observables de FRP hace posible recuperar la componibilidad porque los eventos en una secuencia se ordenan a tiempo. La razón es que la abstracción del flujo de eventos hace posible ver todos los observables como cuadros negros. En última instancia, la combinación de flujos de eventos utilizando operadores devuelve algunos pedidos locales en la ejecución. Esto me obliga a ser mucho más honesto acerca de los invariantes en los que realmente confía mi programa, de manera similar a la forma en que todas las funciones en Haskell tienen que ser referencialmente transparentes: si quiero obtener datos de otra parte de mi programa, tengo que ser explícito anuncio declara un tipo apropiado para mis funciones. (La mónada IO, al ser un lenguaje específico de dominio para escribir código impuro, efectivamente lo evita)