functional-programming - preguntas - programacion funcional ventajas y desventajas
¿Son posibles los efectos secundarios en la programación funcional pura? (9)
El único lenguaje funcional completamente puro que conozco es el sistema de plantillas en C ++. Haskell ocupa el segundo lugar al hacer que las porciones imperativas del programa sean explícitas.
En Haskell el programa tiene estado mutable, pero las funciones (casi siempre) no. Mantiene puro como el 99% por ciento del programa, y solo la parte que interactúa con el mundo exterior es impura. Por lo tanto, cuando pruebas una función, sabes que no hay efectos secundarios. Núcleo puro, con una cáscara impura.
He estado tratando de guiarme por la programación funcional desde hace un tiempo. He buscado cálculo lambda, LISP, OCML, F # e incluso lógica combinatoria, pero el principal problema que tengo es cómo hacer cosas que requieren efectos secundarios como (interactuar con un usuario, comunicarse con un servicio remoto, o incluso manejar la simulación usando muestreo aleatorio) sin violar la premisa fundamental de la programación funcional pura que es, que para una entrada dada, la salida es determinista? Espero que tenga sentido, si no, doy la bienvenida a cualquier intento de educarme adecuadamente. Gracias por adelantado.
Haskell es un lenguaje de programación puramente funcional. En Haskell todas las funciones son puras (es decir, siempre dan la misma salida para las mismas entradas). ¿Pero cómo manejas los efectos secundarios en Haskell? Bueno, este problema se resuelve maravillosamente mediante el uso de mónadas .
Tomando I / O como un ejemplo. En Haskell, cada función que hace I / O devuelve un cálculo IO, es decir, un cálculo en la mónada IO. Así, por ejemplo, una función que lee un int del teclado, en lugar de devolver un int, devuelve un cálculo de IO que produce un int cuando se ejecuta:
askForInt :: String -> IO Int
Como devuelve un cálculo de E / S en lugar de un Int
, no puede usar este resultado directamente en una suma, por ejemplo. Para acceder al valor Int
, necesita "desenvolver" el cálculo. La única forma de hacerlo es usar la función bind ( >>=
):
(>>=) :: IO a -> (a -> IO b) -> IO b
Como esto también devuelve un cálculo de E / S, siempre termina con un cálculo de E / S. Así es como Haskell aísla los efectos secundarios. La mónada IO actúa como una abstracción del estado del mundo real (de hecho, bajo las cubiertas se implementa generalmente con un tipo llamado RealWorld
para la parte de estado).
Incluso si no lo usa en su trabajo, aprender uno o más lenguajes de programación funcionales es una excelente manera de aprender a pensar de manera diferente y le brinda un conjunto de herramientas con enfoques alternativos a los problemas (también puede frustrarlo cuando no puede hacerlo). algo tan ordenado y limpio como un enfoque funcional en otros idiomas).
Y me ayudó a escribir hojas de estilo XSL.
Interactuar con un usuario y comunicarse con un servicio remoto requiere algún tipo de parte no funcional de su software.
Muchos "lenguajes funcionales" (como la mayoría de los Lisp) no son puramente funcionales. Todavía te permiten hacer cosas con efectos secundarios, aunque las cosas con efectos secundarios se "desaconsejan" en la mayoría de los contextos.
Haskell es "puramente funcional" pero aún le permite hacer cosas no funcionales a través de la mónada IO. La idea básica es que su programa puramente funcional emite una estructura de datos diferida que es evaluada por un programa no funcional (que no se escribe, es parte del entorno). Se podría argumentar que esta estructura de datos en sí misma es un programa imperativo. Entonces estás haciendo meta-programación imperativa en un lenguaje funcional.
Ignorando qué enfoque es "mejor", el objetivo en ambos casos es crear una separación entre las partes funcionales y no funcionales de sus programas, y limitar el tamaño de las partes no funcionales tanto como sea posible. Las partes funcionales tienden a ser más reutilizables, comprobables y más fáciles de razonar.
La forma en que lo hace Haskell es usando mónadas, ver wikipedia y la explicación de Haskell en su página .
Básicamente, la idea es que no te deshagas de la mónada IO. Tengo entendido que puede encadenar funciones que desenvuelven una mónima IO y ejecutan esa función. Pero no puede eliminar la mónada IO por completo.
Otro ejemplo que utiliza mónadas que no está directamente relacionado con IO es el Maybe Monad. Esta mónada es ''unwrappable'' en contra de la mónada IO. Pero es más fácil explicar el uso de mónadas usando la mónada Maybe. Supongamos que tiene la siguiente función.
wrap :: Maybe x -> (x -> y) -> Maybe y
wrap Nothing f = Nothing
wrap (Just x) f = Just (f x)
ahora puedes llamar a wrap (Just 4) (5+)
que devolverá Just 9
.
La idea de IO-mónada es que puedes usar funciones como (+5) en el tipo interno. La mónada garantizará que las funciones serán llamadas en serie, ya que cada función está encadenada con la envoltura IO-mónada.
La mayoría de la programación funcional del mundo real no es "pura" en la mayoría de los sentidos, por lo que la mitad de la respuesta a su pregunta es "usted lo hace al renunciar a la pureza". Dicho eso, hay alternativas.
En el sentido "puro" de pureza, el programa completo representa una función única de uno o más argumentos, devolviendo un valor. Si entrecierra los ojos y mueve las manos un poco, puede declarar que toda la entrada del usuario es parte de los "argumentos" de la función y que todos los resultados son parte del "valor de retorno" y luego mezclar un poco las cosas para que solo la E / S real "a pedido".
Una perspectiva similar es declarar que la entrada a la función es "el estado completo del mundo exterior" y que al evaluar la función se devuelve un "estado del mundo" nuevo y modificado. En ese caso, cualquier función en el programa que usa el estado mundial está obviamente libre de ser "determinista" ya que no hay dos evaluaciones del programa que tengan exactamente el mismo mundo exterior.
Si quisieras escribir un programa interactivo en el cálculo lambda puro (o algo equivalente, como el lenguaje esotérico Lazy K), eso es conceptualmente cómo lo harías.
En términos más prácticos, el problema se reduce a garantizar que las E / S se produzcan en el orden correcto cuando la entrada se utiliza como argumento para una función. La estructura general de la solución "pura" para este problema es la composición de funciones . Por ejemplo, supongamos que tiene tres funciones que hacen E / S y desea llamarlas en un orden determinado. Si haces algo como RunThreeFunctions(f1, f2, f3)
no hay nada que determine el orden en el que se evaluarán. Por otro lado, si permites que cada función tome otra función como argumento, puedes encadenarlas así: f1( f2( f3()))
, en cuyo caso usted sabe que f3
se evaluará primero porque la evaluación de f2
depende de su valor. [ Editar: Vea también el comentario a continuación sobre evaluación perezosa vs. evaluación entusiasta. Esto es importante, porque la evaluación perezosa es bastante común en contextos muy puros; por ejemplo, la implementación estándar de recursión en el cálculo lambda puro no es determinante bajo una evaluación entusiasta].
De nuevo, para escribir un programa interactivo en el cálculo lambda, así es como probablemente lo haría. Si quieres algo realmente utilizable para programar, probablemente quieras combinar la parte de composición de funciones con la estructura conceptual de funciones que toman y devuelven valores que representan el estado del mundo, y crear una abstracción de orden superior para manejar pipelining el " "valores de estado mundial" entre las funciones de E / S, idealmente también manteniendo el "estado mundial" contenido para imponer la linealidad estricta, momento en el que prácticamente reinventó la IO
Mónada de Haskell.
Espero que eso no te haya confundido aún más .
La programación funcional se trata de limitar y aislar los efectos secundarios, no tratar de deshacerse por completo de ellos ... porque no se puede.
... y sí, creo que FP es útil (ciertamente con Erlang de todos modos): me parece más fácil pasar de "idea" a "programa" (o problema a solución;) ... pero, por supuesto, podría ser solo yo.
Necesitas saber al menos otro concepto esencial: Mónadas . ¡Necesitarás esto para hacer E / S y otras cosas "útiles"!
Dado que la mayoría de los programas tienen algunos efectos en el mundo exterior (escribir en archivos, modificar datos en una base de datos ...) los programas en su conjunto rara vez son libres de efectos secundarios. Fuera de los ejercicios académicos, no tiene sentido siquiera intentarlo.
Pero los programas se ensamblan a partir de bloques de construcción (subrutina, función, método, llámalo como quieras) y las funciones puras crean bloques de construcción de muy buen comportamiento.
La mayoría de los lenguajes de programación funcionales no requieren que las funciones sean puras, aunque los buenos programadores funcionales intentarán purificar la mayor cantidad de sus funciones posible y práctico, para aprovechar los beneficios de la transparencia referencial.
Haskell va más allá. Cada parte de un Haskell Programm es pura (al menos en ausencia de pecados como "insafePerformIO"). Todas las funciones que escribes en Haskell son puras.
Los efectos secundarios se introducen a través de mónadas. Se pueden usar para introducir una especie de "lista de compras - comprador" - separación. Esencialmente, su programa escribe una lista de compras (que es solo información y se puede manipular de forma pura), mientras que el tiempo de ejecución del lenguaje interpreta la lista de compras y realiza compras efectivas. Todo su código es puro y amigable con el razonamiento ecuacional y tal, mientras que el código impuro es proporcionado por los compiladores-escritores.