Los lenguajes impuros no difieren en principio de los lenguajes imperativos más familiares, especialmente ahora que se han copiado muchos trucos funcionales. Lo que es diferente es el estilo: cómo se resuelven los problemas.
Ya sea que cuente a Haskell como puro o que considere a la mónada IO como impureza, el estilo de Haskell es una forma extrema de este estilo y vale la pena aprenderlo.
La mónada Haskell IO se deriva de la teoría matemática de (por supuesto) mónadas. Sin embargo, para los programadores imperativos, creo que una forma inversa de llegar a las mónadas tiene más sentido.
Fase uno: un lenguaje funcional puro puede devolver fácilmente un valor de cadena grande como resultado. Esta gran cadena puede ser el código fuente de un programa imperativo, derivado de una manera puramente funcional de algunos parámetros que especifican requisitos. A continuación, puede crear un compilador de "nivel superior" que ejecute su generador de código, luego alimenta automáticamente ese código generado en el compilador de lenguaje imperativo.
Fase dos: en lugar de generar código fuente textual, genera un árbol de sintaxis abstracta fuertemente tipado. Su compilador de lenguaje imperativo se absorbe en su compilador de "nivel superior" y acepta el AST directamente como código fuente. Esto está mucho más cerca de lo que hace Haskell.
Sin embargo, esto todavía es incómodo. Por ejemplo, tiene dos tipos distintos de funciones: las evaluadas durante la fase de generación de código y las ejecutadas cuando se ejecuta el programa generado. Es un poco como la distinción entre funciones y plantillas en C ++.
Entonces, para la fase 3, haga que los dos sean iguales: la misma función con la misma sintaxis puede evaluarse parcialmente durante la "generación de código", o evaluarse completamente, o no evaluarse en absoluto. Además, descarte todos los nodos AST de construcción en bucle a favor de la recursividad. De hecho, descarte la idea de los nodos AST como un tipo especial de datos por completo: no tenga nodos AST de "valor literal", solo tenga valores, etc.
Esto es más o menos lo que hace la mónada IO: el operador de enlace es una forma de componer "acciones" para formar programas. No es nada especial, solo una función. Se pueden evaluar muchas expresiones y funciones durante la "generación de código", pero las que dependen de los efectos secundarios de E / S deben tener una evaluación retrasada hasta el tiempo de ejecución, no por ninguna regla especial, sino como consecuencia natural de las dependencias de datos en expresiones
Las mónadas en general son solo generalizaciones: tienen la misma interfaz, pero implementan las operaciones abstractas de manera diferente, por lo que en lugar de evaluar una descripción del código imperativo, evalúan otra cosa. Tener la misma interfaz significa que hay algunas cosas que puede hacer a las mónadas sin importar qué mónada, lo que resulta útil.
Esta descripción sin duda hará explotar las cabezas de los puristas, pero para mí explica algunas de las razones reales por las que Haskell es interesante. Borra el límite entre la programación y la metaprogramación, y utiliza las herramientas de programación funcional para reinventar la programación imperativa sin necesidad de una sintaxis especial.
Una crítica que tengo de las plantillas de C ++ es que son una especie de sublenguaje funcional puro roto en un lenguaje imperativo: para evaluar la misma función básica en tiempo de compilación en lugar de tiempo de ejecución, debe volver a implementarla usando un estilo completamente diferente de codificación En Haskell, aunque la impureza debe etiquetarse como tal en su tipo, la misma función exacta puede evaluarse tanto en un sentido de metaprogramación como en un sentido de no metaprogramación en tiempo de ejecución en el mismo programa: no hay una línea dura entre programación y metaprogramación.
Dicho esto, hay algunas cosas de metaprogramación que Haskell estándar no puede hacer, básicamente porque los tipos (y tal vez algunas otras cosas) no son valores de primera clase. Sin embargo, hay variantes de idioma que intentan abordar esto.
Muchas de las cosas que he dicho sobre Haskell pueden aplicarse en lenguajes funcionales impuros, y en ocasiones incluso en lenguajes imperativos. Haskell es diferente porque no tienes más remedio que adoptar este enfoque, básicamente te obliga a aprender este estilo de trabajo. Puede "escribir C en ML", pero no puede "escribir C en Haskell", al menos no sin saber lo que sucede debajo del capó.