sirve - ¿Es realmente Haskell un lenguaje puramente funcional considerando UnpafePerformIO?
lenguajes funcionales (8)
Haskell generalmente se menciona como un ejemplo de un lenguaje puramente funcional. ¿Cómo puede justificarse esto dada la existencia de System.IO.Unsafe.unsafePerformIO?
Editar: pensé que con "puramente funcional" significaba que es imposible introducir código impuro en la parte funcional del programa.
La mónada IO en realidad está definida en Haskell, y de hecho puedes ver su definición here . Esta mónada no existe para tratar las impurezas, sino para manejar los efectos secundarios . En cualquier caso, en realidad podrías emparejar patrones para salir de la mónada IO
, por lo que la existencia de una unsafePerformIO
de unsafePerformIO
no debería ser realmente preocupante para ti.
Haskell generalmente se menciona como un ejemplo de un lenguaje puramente funcional. ¿Cómo puede justificarse esto dada la existencia de System.IO.Unsafe.unsafePerformIO
?
Editar: pensé que con "puramente funcional" significaba que es imposible introducir código impuro en la parte funcional del programa.
Pensé que con "puramente funcional" se quería decir que es imposible introducir código impuro ...
La verdadera respuesta es que unsafePerformIO
no es parte de Haskell, más que decir, el recolector de basura o el sistema de tiempo de ejecución son parte de Haskell. unsafePerformIO
está ahí en el sistema para que las personas que crean el sistema puedan crear una abstracción funcional pura sobre un hardware muy efectivo. Todos los lenguajes reales tienen lagunas que hacen posible que los creadores de sistemas hagan las cosas de manera más efectiva que bajar al código C o al código ensamblador.
En cuanto a la imagen más amplia de cómo encajan los efectos secundarios y E / S en Haskell a través de la mónada IO
, creo que la manera más fácil de pensar en Haskell es que es un lenguaje puro que describe los cálculos efectivos. Cuando el cómputo descrito es main
, el sistema de tiempo de ejecución lleva a cabo esos efectos fielmente.
unsafePerformIO
es una forma de obtener efectos de una manera insegura; donde "inseguro" significa "la seguridad debe ser garantizada por el programador" - nada es verificado por el compilador. Si usted es un programador inteligente y está dispuesto a cumplir con las obligaciones de pruebas pesadas, puede utilizar unsafePerformIO
. Pero en ese momento ya no estás programando en Haskell; estás programando en un lenguaje inseguro que se parece mucho a Haskell.
El lenguaje / implementación es puramente funcional. Incluye un par de "escotillas de escape", que no tienes que usar si no quieres.
Lamentablemente, el lenguaje tiene que hacer un poco de trabajo en el mundo real, y esto implica hablar con el entorno externo.
Lo bueno es que puede (y debería) limitar el uso de este código "desactualizado" a algunas partes específicas bien documentadas de su programa.
No creo que UnPerformIO inseguro signifique que haskell de alguna manera se vuelve impuro. Puede crear funciones puras (referencialmente transparentes) a partir de funciones impuras.
Considera la lista de skiplists. Para que funcione bien, requiere acceso a un RNG, una función impura, pero esto no hace que la estructura de datos sea impura. Si agrega un elemento y luego lo convierte en una lista, se le devolverá la misma lista cada vez que le proporcione el artículo que agrega.
Por esta razón, creo que unaPerformIO insegura debe considerarse como una promesaPureIO. Una función que significa que las funciones que tienen efectos secundarios y que, por lo tanto, podrían etiquetarse impuras por el sistema de tipos pueden ser reconocidas como referencialmente transparentes por el sistema de tipos.
Entiendo que para que esto suceda es necesario tener una definición de purificación un poco más débil. es decir, las funciones puras son referencialmente transparentes y nunca se llaman debido a un efecto secundario (como imprimir).
Safe Haskell, una extensión reciente de GHC, da una nueva respuesta a esta pregunta. unsafePerformIO
es parte de GHC Haskell, pero no forma parte del dialecto seguro.
unsafePerformIO
debe usarse solo para crear funciones referencialmente transparentes; por ejemplo, memorización. En estos casos, el autor de un paquete lo marca como "confiable". Un módulo seguro solo puede importar módulos seguros y confiables; no puede importar módulos inseguros.
Para más información: manual GHC , papel Safe Haskell
Tengo la sensación de que seré muy impopular por decir lo que voy a decir, pero sentí que tenía que responder a parte de la información (en mi opinión errónea) presentada aquí.
Aunque es cierto que inseguroPerformIO se agregó oficialmente al idioma como parte del anexo de FFI, las razones para esto son en gran parte históricas en lugar de lógicas. Existía extraoficialmente y fue ampliamente utilizado mucho antes de que Haskell tuviera alguna vez un FFI. Nunca fue oficialmente parte del estándar principal de Haskell porque, como habrás observado, fue demasiado vergonzoso. Creo que la esperanza era que desaparecería en algún momento en el futuro, de alguna manera. Bueno, eso no ha sucedido, ni lo hará en mi opinión.
El desarrollo de la adición de FFI proporcionó un pretexto conveniente para que la forma no segura dePerformIO ingrese al estándar de idioma oficial ya que probablemente no parece tan malo aquí, en comparación con agregar la capacidad de llamar al código extranjero (IE C) (donde todas las apuestas son apagado con respecto a garantizar estáticamente la pureza y tipo de seguridad de todos modos). También fue conveniente ponerlo aquí por razones esencialmente políticas. Fomentó el mito de que Haskell sería puro, si no fuera por todo ese sucio "mal diseñado" C, o sistemas operativos "mal diseñados", o hardware "mal diseñado" o lo que sea ... Es cierto que inseguroPerformIO se usa regularmente con código relacionado con FFI, pero las razones para esto a menudo tienen más que ver con un mal diseño de la FFI y, en realidad, con Haskell, no es un mal diseño de cualquier cosa extraña que Haskell intente también con la interfaz.
Como dice Norman Ramsey, la posición oficial fue que estaba bien usar UnpafePerformIO con la condición de que ciertas obligaciones de prueba fueran satisfechas por quienquiera que lo usara (principalmente que hacerlo no invalida las transformaciones importantes del compilador como la eliminación de sub-expresiones internas y comunes) . Hasta aquí todo bien, o al menos uno podría pensar. El verdadero truco es que estas obligaciones de prueba no pueden ser satisfechas por lo que probablemente sea el caso de uso más común para inseguroPerformIO, que según mi estimación representa más del 50% de todas las formas de riesgo inseguras que hay en la naturaleza. Estoy hablando de la espantosa expresión idiomática conocida como "hack inseguro de PerformIO", que es demostrable (de hecho obviamente) completamente insegura (en presencia de inline y cse).
Realmente no tengo el tiempo, espacio o inclinación para entrar en lo que es el "truco inseguro dePerformIO" o por qué es necesario en bibliotecas IO reales, pero la conclusión es que la gente que trabaja en la infraestructura IO de Haskells suele estar "atrapada entre una Sitio de rock duro". Pueden proporcionar una API intrínsecamente segura que no tiene una implementación segura (en Haskell), o pueden proporcionar una API intrínsecamente insegura que se puede implementar con seguridad, pero lo que rara vez pueden hacer es proporcionar seguridad tanto en el diseño como en la implementación de la API. A juzgar por la deprimente regularidad con la que aparece el "hack inseguro dePerformIO" en el código del mundo real (incluidas las bibliotecas estándar de Haskell), parece que la mayoría elige la primera opción como el menor de los dos males, y solo espera que el compilador no las cosas con inlining, cse o cualquier otra transformación.
Ojalá todo esto no fuera así. Desafortunadamente es.
Los idiomas que llamamos Haskell
unsafePerformIO
es parte de la especificación de la Interfaz de Función Extranjera , no de la especificación Haskell 98 básica. Se puede usar para hacer efectos secundarios locales que no escapan a algún ámbito, a fin de exponer una interfaz puramente funcional. Es decir, lo usamos para ocultar los efectos cuando el verificador de tipos no puede hacerlo por nosotros (a diferencia de la mónada ST, que oculta los efectos con una garantía estática).
Para ilustrar con precisión los múltiples idiomas que llamamos "Haskell", considere la imagen a continuación. Cada anillo corresponde a un conjunto específico de características computacionales, ordenadas por seguridad, y con un área que se correlaciona con el poder expresivo (es decir, la cantidad de programas que puede escribir si tiene esa característica).
El lenguaje conocido como Haskell 98 se especifica justo en el medio, admitiendo funciones totales y parciales. Agda (o Epigram ), donde solo se permiten las funciones totales, es aún menos expresivo, pero "más puro" y más seguro. Mientras que Haskell, tal como lo usamos hoy, incluye todo en FFI, donde vive InseguroPerformIO. Es decir, puede escribir cualquier cosa en Haskell moderno, aunque si utiliza elementos de los anillos exteriores, será más difícil establecer garantías de seguridad simplificadas por los anillos internos.
Por lo tanto, los programas Haskell generalmente no se crean a partir de un código 100% referencialmente transparente, sin embargo, es el único lenguaje moderadamente común que es puro por defecto .