Siento que los efectos secundarios son un fenómeno natural. Pero es algo así como un tabú en lenguajes funcionales. ¿Cuales son las razones?
Mi pregunta es específica del estilo de programación funcional. No todos los lenguajes / paradigmas de programación.
functional-programming
side-effect
Gulshan
fuente
fuente
Respuestas:
Escribir sus funciones / métodos sin efectos secundarios, por lo que son funciones puras , hace que sea más fácil razonar sobre la corrección de su programa.
También facilita la composición de esas funciones para crear un nuevo comportamiento.
También hace posibles ciertas optimizaciones, donde el compilador puede, por ejemplo, memorizar los resultados de las funciones o usar la Eliminación de subexpresión común.
Editar: a pedido de Benjol: debido a que gran parte de su estado está almacenado en la pila (flujo de datos, no flujo de control, como Jonas lo ha llamado aquí ), puede paralelizar o reordenar la ejecución de aquellas partes de su cálculo que son independientes de El uno al otro. Puede encontrar fácilmente esas partes independientes porque una parte no proporciona entradas a la otra.
En entornos con depuradores que le permiten revertir la pila y reanudar la computación (como Smalltalk), tener funciones puras significa que puede ver muy fácilmente cómo cambia un valor, porque los estados anteriores están disponibles para inspección. En un cálculo con mucha mutación, a menos que agregue explícitamente acciones de hacer / deshacer a su estructura o algoritmo, no podrá ver el historial del cálculo. (Esto se relaciona con el primer párrafo: escribir funciones puras facilita la inspección de la corrección de su programa).
fuente
De un artículo sobre programación funcional :
fuente
Se equivocó, la programación funcional promueve la limitación de los efectos secundarios para que los programas sean fáciles de entender y optimizar. Incluso Haskell te permite escribir en archivos.
Esencialmente, lo que digo es que los programadores funcionales no piensan que los efectos secundarios sean malos, simplemente piensan que limitar el uso de los efectos secundarios es bueno. Sé que puede parecer una distinción tan simple, pero hace toda la diferencia.
fuente
readFile
están haciendo es definir una secuencia de acciones. esta secuencia es funcionalmente pura y es como un árbol abstracto que describe QUÉ hacer. los efectos secundarios sucios reales son llevados a cabo por el tiempo de ejecución.Algunas notas
Las funciones sin efectos secundarios se pueden ejecutar triviamente en paralelo, mientras que las funciones con efectos secundarios generalmente requieren algún tipo de sincronización.
Las funciones sin efectos secundarios permiten una optimización más agresiva (p. Ej., Usando transparentemente un caché de resultados), porque mientras obtengamos el resultado correcto, ni siquiera importa si la función se ejecutó realmente o no.
fuente
deterministic
cláusula para funciones sin efectos secundarios, por lo que no se ejecutan más de lo necesario.deterministic
cláusula es solo una palabra clave que le dice al compilador que esta es una función determinista, comparable a cómo lafinal
palabra clave en Java le dice al compilador que la variable no puede cambiar.Principalmente trabajo en código funcional ahora, y desde esa perspectiva parece cegadoramente obvio. Los efectos secundarios crean una enorme carga mental para los programadores que intentan leer y comprender el código. No notas esa carga hasta que te liberas de ella por un tiempo, luego de repente tienes que leer el código con efectos secundarios nuevamente.
Considere este simple ejemplo:
En un lenguaje funcional, sé que
foo
todavía es 42. Ni siquiera tengo que mirar el código intermedio, mucho menos entenderlo, o mirar las implementaciones de las funciones que llama.Todo lo relacionado con la concurrencia, la paralelización y la optimización es bueno, pero eso es lo que los científicos informáticos ponen en el folleto. No tener que preguntarme quién está mutando su variable y cuándo es lo que realmente disfruto en la práctica diaria.
fuente
Pocos idiomas o ninguno hacen que sea imposible causar efectos secundarios. Los idiomas que eran completamente libres de efectos secundarios serían prohibitivamente difíciles de usar (casi imposibles), excepto en una capacidad muy limitada.
Porque hacen que sea mucho más difícil razonar sobre exactamente lo que hace un programa y demostrar que hace lo que usted espera que haga.
A un nivel muy alto, imagine probar un sitio web completo de 3 niveles con solo pruebas de recuadro negro. Claro, es factible, dependiendo de la escala. Pero ciertamente hay mucha duplicación. Y si no es un error (que se relaciona con un efecto secundario), entonces se podría potencialmente romper todo el sistema para su análisis posterior, hasta que el fallo se diagnostica y fija, y la solución se implementa en el entorno de prueba.
Beneficios
Ahora, reduzca eso. Si fuera bastante bueno escribiendo código libre de efectos secundarios, ¿cuánto más rápido sería razonando sobre lo que hizo algún código existente? ¿Cuánto más rápido podrías escribir pruebas unitarias? ¿Qué tan seguro se sentiría que el código sin efectos secundarios se garantiza libre de errores, y que los usuarios podrían limitar su exposición a cualquier error que no tiene?
Si el código no tiene efectos secundarios, el compilador también puede tener optimizaciones adicionales que podría realizar. Puede ser mucho más fácil implementar esas optimizaciones. Puede ser mucho más fácil incluso conceptualizar una optimización para el código libre de efectos secundarios, lo que significa que el proveedor del compilador podría implementar optimizaciones que son difíciles de imposibles en el código con efectos secundarios.
La concurrencia también es drásticamente más simple de implementar, generar automáticamente y optimizar cuando el código no tiene efectos secundarios. Esto se debe a que todas las piezas se pueden evaluar de forma segura en cualquier orden. Permitir que los programadores escriban código altamente concurrente se considera ampliamente el próximo gran desafío que la Ciencia de la Computación debe enfrentar, y uno de los pocos coberturas restantes contra la Ley de Moore .
fuente
Los efectos secundarios son como "filtraciones" en su código que deberán ser manejadas más tarde, ya sea por usted o por algún compañero desprevenido.
Los lenguajes funcionales evitan las variables de estado y los datos mutables como una forma de hacer que el código sea menos dependiente del contexto y más modular. La modularidad asegura que el trabajo de un desarrollador no afectará / socavará el trabajo de otro.
Escalar la tasa de desarrollo con el tamaño del equipo es hoy un "santo grial" del desarrollo de software. Cuando se trabaja con otros programadores, pocas cosas son tan importantes como la modularidad. Incluso el más simple de los efectos secundarios lógicos hace que la colaboración sea extremadamente difícil.
fuente
Bueno, en mi humilde opinión, esto es bastante hipócrita. A nadie le gustan los efectos secundarios, pero todos los necesitan.
Lo que es tan peligroso acerca de los efectos secundarios es que si llama a una función, esto posiblemente tenga un efecto no solo en la forma en que se comporta la función la próxima vez, sino que posiblemente tenga este efecto en otras funciones. Por lo tanto, los efectos secundarios introducen comportamientos impredecibles y dependencias no triviales.
Los paradigmas de programación como OO y funcional abordan este problema. OO reduce el problema al imponer una separación de preocupaciones. Esto significa que el estado de la aplicación, que consta de muchos datos mutables, se encapsula en objetos, cada uno de los cuales es responsable de mantener solo su propio estado. De esta forma, se reduce el riesgo de dependencias y los problemas están mucho más aislados y son más fáciles de rastrear.
La programación funcional adopta un enfoque mucho más radical, donde el estado de la aplicación es simplemente inmutable desde la perspectiva del programador. Esta es una buena idea, pero hace que el lenguaje sea inútil por sí solo. ¿Por qué? Porque CUALQUIER operación de E / S tiene efectos secundarios. Tan pronto como lea de cualquier flujo de entrada, es probable que cambie el estado de su aplicación, porque la próxima vez que invoque la misma función, es probable que el resultado sea diferente. Puede estar leyendo datos diferentes o, también una posibilidad, la operación podría fallar. Lo mismo es cierto para la salida. Incluso la salida es una operación con efectos secundarios. Esto no es nada de lo que se dé cuenta a menudo hoy en día, pero imagine que solo tiene 20K para su salida y si sale más, su aplicación se bloquea porque no tiene espacio en el disco o lo que sea.
Entonces, sí, los efectos secundarios son desagradables y peligrosos desde la perspectiva de un programador. La mayoría de los errores provienen de la forma en que ciertas partes del estado de la aplicación están entrelazadas de una manera casi oscura, a través de efectos secundarios no considerados y muchas veces innecesarios. Desde la perspectiva de un usuario, los efectos secundarios son el objetivo de usar una computadora. No les importa lo que sucede dentro o cómo está organizado. Hacen algo y esperan que la computadora CAMBIE en consecuencia.
fuente
Cualquier efecto secundario introduce parámetros adicionales de entrada / salida que deben tenerse en cuenta al realizar la prueba.
Esto hace que la validación del código sea mucho más compleja ya que el entorno no puede limitarse solo al código que se valida, sino que debe incorporar parte o la totalidad del entorno circundante (el global que se actualiza vive en ese código allí, lo que a su vez depende de eso código, que a su vez depende de vivir dentro de un servidor Java EE completo ...)
Al tratar de evitar los efectos secundarios, limita la cantidad de externalismo necesario para ejecutar el código.
fuente
En mi experiencia, un buen diseño en la programación orientada a objetos exige el uso de funciones que tienen efectos secundarios.
Por ejemplo, tome una aplicación de escritorio de IU básica. Es posible que tenga un programa en ejecución que tenga en su montón un gráfico de objetos que represente el estado actual del modelo de dominio de mi programa. Los mensajes llegan a los objetos en ese gráfico (por ejemplo, a través de llamadas a métodos invocadas desde el controlador de capa UI). El gráfico de objeto (modelo de dominio) en el montón se modifica en respuesta a los mensajes. Los observadores del modelo son informados de cualquier cambio, la interfaz de usuario y tal vez otros recursos son modificados.
Lejos de ser malo, la disposición correcta de estos efectos secundarios que modifican el montón y la pantalla son el núcleo del diseño OO (en este caso, el patrón MVC).
Por supuesto, eso no significa que sus métodos deban tener efectos secundarios arbitrarios. Y las funciones libres de efectos secundarios tienen un lugar para mejorar la lectura y, a veces, el rendimiento de su código.
fuente
El mal es un poco exagerado ... todo depende del contexto del uso del lenguaje.
Otra consideración para los ya mencionados es que hace que las pruebas de corrección de un programa sean mucho más simples si no hay efectos secundarios funcionales.
fuente
Como las preguntas anteriores han señalado, los lenguajes funcionales no impiden tanto que el código tenga efectos secundarios , sino que nos proporcionan herramientas para administrar qué efectos secundarios pueden ocurrir en un determinado código y cuándo.
Esto resulta tener consecuencias muy interesantes. Primero, y más obviamente, hay numerosas cosas que puede hacer con el código libre de efectos secundarios, que ya se han descrito. Pero también hay otras cosas que podemos hacer, incluso cuando trabajamos con código que sí tiene efectos secundarios:
fuente
En bases de código complejas, las interacciones complejas de los efectos secundarios son lo más difícil de lo que encuentro para razonar. Solo puedo hablar personalmente dada la forma en que funciona mi cerebro. Los efectos secundarios y los estados persistentes y las entradas de mutaciones, etc., me hacen pensar en "cuándo" y "dónde" suceden las cosas para razonar sobre la corrección, no solo "qué" está sucediendo en cada función individual.
No puedo concentrarme solo en "qué". No puedo concluir, después de probar exhaustivamente una función que causa efectos secundarios, que se extenderá un aire de confiabilidad a lo largo del código que lo usa, ya que las personas que llaman pueden usarlo incorrectamente al llamarlo en el momento incorrecto, desde el hilo incorrecto, en el incorrecto orden. Mientras tanto, una función que no causa efectos secundarios y solo devuelve una nueva salida dada una entrada (sin tocar la entrada) es prácticamente imposible de usar de manera incorrecta.
Pero creo que soy un tipo pragmático, o al menos trato de serlo, y no creo que necesariamente tengamos que eliminar todos los efectos secundarios al mínimo para razonar sobre la corrección de nuestro código (al menos Me resultaría muy difícil hacerlo en lenguajes como C). Donde encuentro muy difícil razonar sobre la corrección es cuando tenemos la combinación de flujos de control complejos y efectos secundarios.
Los flujos de control complejos para mí son de naturaleza gráfica, a menudo recursiva o recursiva (colas de eventos, por ejemplo, que no llaman directamente a eventos recursivamente pero son de naturaleza "recursiva"), tal vez haciendo cosas en el proceso de atravesar una estructura de gráfico vinculada real, o procesar una cola de eventos no homogénea que contiene una mezcla ecléctica de eventos para procesar que nos lleva a todo tipo de diferentes partes de la base de código y todos desencadenan diferentes efectos secundarios. Si intentara dibujar todos los lugares en los que finalmente terminará en el código, se parecería a un gráfico complejo y potencialmente con nodos en el gráfico que nunca esperó que hubieran estado allí en ese momento dado, y dado que están todos causando efectos secundarios,
Los lenguajes funcionales pueden tener flujos de control extremadamente complejos y recursivos, pero el resultado es tan fácil de comprender en términos de corrección porque no hay todo tipo de efectos secundarios eclécticos en el proceso. Es solo cuando los flujos de control complejos se encuentran con efectos secundarios eclécticos que encuentro que induce dolor de cabeza tratar de comprender la totalidad de lo que está sucediendo y si siempre hará lo correcto.
Entonces, cuando tengo esos casos, a menudo me resulta muy difícil, si no imposible, sentirme muy seguro acerca de la corrección de dicho código, y mucho menos muy seguro de que puedo hacer cambios a ese código sin tropezar con algo inesperado. Entonces, la solución para mí es simplificar el flujo de control o minimizar / unificar los efectos secundarios (al unificar, quiero decir que solo causo un tipo de efecto secundario a muchas cosas durante una fase particular del sistema, no dos o tres o un docena). Necesito que suceda una de esas dos cosas para permitir que mi cerebro simplón se sienta seguro acerca de la corrección del código que existe y la corrección de los cambios que introduzco. Es bastante fácil confiar en la exactitud del código que introduce efectos secundarios si los efectos secundarios son uniformes y simples junto con el flujo de control, de la siguiente manera:
Es bastante fácil razonar sobre la corrección de dicho código, pero principalmente porque los efectos secundarios son muy uniformes y el flujo de control es muy simple. Pero digamos que teníamos un código como este:
Entonces, este es un pseudocódigo ridículamente simplificado que típicamente implicaría muchas más funciones y bucles anidados y muchas más cosas que tendrían que continuar (actualizar múltiples mapas de textura, pesos óseos, estados de selección, etc.), pero incluso el pseudocódigo hace que sea tan difícil razón sobre la corrección debido a la interacción del flujo de control complejo tipo gráfico y los efectos secundarios que están ocurriendo. Entonces, una estrategia para simplificar eso es diferir el procesamiento y solo enfocarse en un tipo de efecto secundario a la vez:
... algo en este sentido como una iteración de simplificación. Eso significa que estamos pasando por los datos varias veces, lo que definitivamente está incurriendo en un costo computacional, pero a menudo encontramos que podemos multiprocesar dicho código resultante más fácilmente, ahora que los efectos secundarios y los flujos de control han adquirido esta naturaleza uniforme y más simple. Además, cada bucle se puede hacer más amigable con el caché que atravesar el gráfico conectado y causar efectos secundarios a medida que avanzamos (por ejemplo: use un conjunto de bits paralelos para marcar lo que debe atravesarse para que luego podamos hacer los pases diferidos en orden secuencial ordenado usando máscaras de bits y FFS). Pero lo más importante, encuentro que la segunda versión es mucho más fácil de razonar en términos de corrección y cambio sin causar errores. Así que eso'
Y después de todo, necesitamos que ocurran efectos secundarios en algún momento, o de lo contrario solo tendríamos funciones que generarán datos sin ningún lugar a donde ir. A menudo necesitamos grabar algo en un archivo, mostrar algo en una pantalla, enviar los datos a través de un socket, algo de este tipo, y todas estas cosas son efectos secundarios. Pero definitivamente podemos reducir la cantidad de efectos secundarios superfluos que ocurren, y también reducir la cantidad de efectos secundarios que ocurren cuando los flujos de control son muy complicados, y creo que sería mucho más fácil evitar errores si lo hiciéramos.
fuente
No es malo En mi opinión, es necesario distinguir los dos tipos de funciones, con efectos secundarios y sin ellos. La función sin efectos secundarios: - devuelve siempre lo mismo con los mismos argumentos, por lo que, por ejemplo, dicha función sin ningún argumento no tiene sentido. - Eso también significa que el orden en el que se llaman algunas de estas funciones no juega ningún papel: debe poder ejecutarse y puede depurarse solo (!), Sin ningún otro código. Y ahora, lol, mira lo que JUnit hace. Una función con efectos secundarios: - tiene una especie de "fugas", lo que se puede resaltar automáticamente - es muy importante al depurar y buscar errores, lo que generalmente es causado por los efectos secundarios. - Cualquier función con efectos secundarios también tiene una "parte" de sí misma sin efectos secundarios, que también se puede separar automáticamente. Tan malvados son esos efectos secundarios,
fuente