Soy principalmente un programador de C / C ++, lo que significa que la mayor parte de mi experiencia es con paradigmas de procedimientos y orientados a objetos. Sin embargo, como saben muchos programadores de C ++, C ++ ha cambiado su énfasis a lo largo de los años a un estilo funcional, que culminó finalmente con la incorporación de lambdas y cierres en C ++ 0x.
De todos modos, aunque tengo una experiencia considerable en la codificación de un estilo funcional usando C ++, tengo muy poca experiencia con lenguajes funcionales reales como Lisp, Haskell, etc.
Recientemente comencé a estudiar estos lenguajes, porque la idea de "no tener efectos secundarios" en lenguajes puramente funcionales siempre me ha intrigado, especialmente con respecto a sus aplicaciones para la concurrencia y la computación distribuida.
Sin embargo, viniendo de un entorno de C ++, estoy confundido acerca de cómo funciona esta filosofía sin efectos secundarios con la programación asincrónica. Por programación asincrónica me refiero a cualquier estilo de marco / API / codificación que despacha controladores de eventos proporcionados por el usuario para manejar eventos que ocurren de forma asincrónica (fuera del flujo del programa). Esto incluye bibliotecas asincrónicas como Boost.ASIO, o incluso simplemente C viejo manejadores de señal o manejadores de eventos GUI Java.
Lo único que todos estos tienen en común es que la naturaleza de la programación asincrónica parece requerir la creación de efectos secundarios (estado), para que el flujo principal del programa tome conciencia de que se ha invocado un controlador de eventos asincrónico. Típicamente, en un marco como Boost.ASIO, un controlador de eventos cambia el estado de un objeto, de modo que el efecto del evento se propaga más allá del tiempo de vida de la función del controlador de eventos. Realmente, ¿qué más puede hacer un controlador de eventos? No puede "devolver" un valor al punto de llamada, porque no hay un punto de llamada. El controlador de eventos no es parte del flujo principal del programa, por lo que la única forma en que puede tener algún efecto en el programa real es cambiar algún estado (o bien longjmp
a otro punto de ejecución).
Por lo tanto, parece que la programación asincrónica se trata de producir asincrónicamente efectos secundarios. Esto parece completamente en desacuerdo con los objetivos de la programación funcional. ¿Cómo se concilian estos dos paradigmas (en la práctica) en lenguajes funcionales?
fuente
Respuestas:
Toda su lógica es sólida, excepto que creo que su comprensión de la programación funcional es demasiado extrema. En la programación funcional del mundo real, al igual que la programación orientada a objetos o imperativa, se trata de la mentalidad y de cómo aborda el problema. Todavía puede escribir programas en el espíritu de la programación funcional mientras modifica el estado de la aplicación.
De hecho, debe modificar el estado de la aplicación para hacer realmente cualquier cosa. Los muchachos de Haskell le dirán que sus programas son 'puros' porque envuelven todos sus cambios de estado en una mónada. Sin embargo, sus programas aún interactúan con el mundo exterior. (De lo contrario, ¿cuál es el punto!)
Programación funcional énfasis "sin efectos secundarios" cuando tiene sentido. Sin embargo, para hacer una programación del mundo real, como dijiste, necesitas modificar el estado del mundo. (Por ejemplo, responder a eventos, escribir en el disco, etc.)
Para obtener más información sobre la programación asincrónica en lenguajes funcionales, le recomiendo encarecidamente que consulte el modelo de programación Asynchronous Workflows de F # . Le permite escribir programas funcionales mientras oculta todos los detalles desordenados de la transición de subprocesos dentro de una biblioteca. (De una manera muy similar a las mónadas de estilo Haskell).
Si el 'cuerpo' del hilo simplemente calcula un valor, entonces generar múltiples hilos y hacer que calculen valores en paralelo todavía está dentro del paradigma funcional.
fuente
Esta es una pregunta fascinante. La opinión más interesante es, en mi opinión, el enfoque adoptado en Clojure y explicado en este video:
http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey
Básicamente, la "solución" propuesta es la siguiente:
Probablemente no he expresado la idea tan claramente como lo han hecho otros, pero espero que esto dé la idea general: básicamente está usando un sistema STM concurrente para proporcionar el "puente" entre la programación funcional pura y el manejo de eventos asíncronos.
fuente
Una nota: un lenguaje funcional es puro, pero su tiempo de ejecución no lo es.
Por ejemplo, los tiempos de ejecución de Haskell incluyen colas, multiplexación de subprocesos, recolección de basura, etc., todo lo cual no es puro.
Un buen ejemplo es la pereza. Haskell admite la evaluación diferida (ese es el valor predeterminado, en realidad). Crea un valor diferido al preparar una operación, luego puede crear varias copias de este valor y sigue siendo "diferido" siempre que no sea necesario. Cuando se necesita el resultado, o si el tiempo de ejecución encuentra algo de tiempo, el valor se calcula realmente y el estado del objeto vago cambia para reflejar que ya no es necesario realizar el cálculo (una vez más) para obtener su resultado. Ahora está disponible a través de todas las referencias, por lo que el estado del objeto ha cambiado, aunque es un lenguaje puro.
fuente
Ese sería el punto, entonces.
Un estilo de sonido sin efectos secundarios es incompatible con marcos que dependen del estado. Encuentra un nuevo marco.
El estándar WSGI de Python, por ejemplo, nos permite crear aplicaciones sin efectos secundarios.
La idea es que los diversos "cambios de estado" se reflejan en un entorno de valores que se pueden construir de forma incremental. Cada solicitud es una tubería de transformaciones.
fuente
Habiendo aprendido la encapsulación de Borland C ++ después de aprender C, cuando Borland C ++ carecía de plantillas que permitieran genéricos, el paradigma de orientación a objetos me hizo sentir incómodo. Una forma algo más natural de calcular parecía filtrar datos a través de tuberías. La secuencia externa tenía una identidad separada e independiente de la secuencia de entrada inmutable interna, en lugar de considerarse como un efecto secundario, es decir, cada fuente de datos (o filtro) era autónoma de las demás. La pulsación de tecla (un evento de ejemplo) restringió las combinaciones de entrada de usuario asíncronas a los códigos de teclas disponibles. Las funciones operan en argumentos de parámetros de entrada, y el estado encapsulado por clase es solo un atajo para evitar pasar explícitamente argumentos repetitivos entre un pequeño subconjunto de funciones, además de ser cauteloso con el contexto vinculado para evitar el abuso de esos argumentos de cualquier función arbitraria.
Adherirse rígidamente a un paradigma particular causa inconvenientes al tratar con abstracciones permeables, por ejemplo. tiempos de ejecución comerciales tales como JRE, DirectX, .net. Para limitar las molestias, los idiomas optan por mónadas académicas y sofisticadas como lo hace Haskell, o finalmente se obtiene un soporte flexible de paradigmas múltiples como F #. A menos que la encapsulación sea útil en algún caso de uso de herencia múltiple, el enfoque de paradigma múltiple podría ser una alternativa superior a algunos patrones de programación específicos de paradigma, a veces complejos.
fuente