¿Los lenguajes de programación funcionales no permiten los efectos secundarios?

10

Según Wikipedia, los lenguajes de programación funcional , que son declarativos, no permiten efectos secundarios. Programación declarativa en general, intentos de minimizar o eliminar los efectos secundarios.

Además, según Wikipedia, un efecto secundario está relacionado con los cambios de estado. Entonces, los lenguajes de programación funcional, en ese sentido, en realidad eliminan los efectos secundarios, ya que no guardan ningún estado.

Pero, además, un efecto secundario tiene otra definición. Efecto secundario

tiene una interacción observable con sus funciones de llamada o el mundo exterior además de devolver un valor. Por ejemplo, una función particular podría modificar una variable global o una variable estática, modificar uno de sus argumentos, generar una excepción, escribir datos en una pantalla o archivo, leer datos o llamar a otras funciones de efectos secundarios.

En ese sentido, los lenguajes de programación funcional en realidad permiten efectos secundarios, ya que hay innumerables ejemplos de funciones que afectan a su mundo exterior, invocan otras funciones, generan excepciones, escriben en archivos, etc.

Entonces, finalmente, ¿los lenguajes de programación funcional permiten efectos secundarios o no?

O bien, no entiendo lo que califica como un "efecto secundario", por lo que los lenguajes imperativos los permiten y los declarativos no. De acuerdo con lo anterior y lo que obtengo, ningún lenguaje elimina los efectos secundarios, por lo que me falta algo sobre los efectos secundarios o la definición de Wikipedia es incorrectamente amplia.

codebot
fuente

Respuestas:

26

La programación funcional incluye muchas técnicas diferentes. Algunas técnicas están bien con efectos secundarios. Pero un aspecto importante es el razonamiento equitativo : si invoco una función con el mismo valor, siempre obtengo el mismo resultado. Entonces puedo sustituir una llamada de función con el valor de retorno y obtener un comportamiento equivalente. Esto facilita razonar sobre el programa, especialmente al depurar.

Si la función tiene efectos secundarios, esto no se cumple. El valor de retorno no es equivalente a la llamada a la función, porque el valor de retorno no contiene los efectos secundarios.

La solución es dejar de usar secundarios efectos y la codificación de estos efectos en el valor de retorno . Diferentes idiomas tienen diferentes sistemas de efectos. Por ejemplo, Haskell usa mónadas para codificar ciertos efectos como IO o mutación de estado. Los lenguajes C / C ++ / Rust tienen un sistema de tipos que puede impedir la mutación de algunos valores.

En un lenguaje imperativo, una print("foo")función imprimirá algo y no devolverá nada. En un lenguaje funcional puro como Haskell, una printfunción también toma un objeto que representa el estado del mundo exterior y devuelve un nuevo objeto que representa el estado después de haber realizado esta salida. Algo similar a newState = print "foo" oldState. Puedo crear tantos estados nuevos del estado anterior como quiera. Sin embargo, solo uno será utilizado por la función principal. Entonces necesito secuenciar los estados de múltiples acciones encadenando las funciones. Para imprimir foo bar, podría decir algo como print "bar" (print "foo" originalState).

Si no se usa un estado de salida, Haskell no realiza las acciones que conducen a ese estado, porque es un lenguaje vago. Por el contrario, esta pereza solo es posible porque todos los efectos están codificados explícitamente como valores de retorno.

Tenga en cuenta que Haskell es el único lenguaje funcional de uso común que utiliza esta ruta. Otros lenguajes funcionales incl. la familia Lisp, la familia ML y los lenguajes funcionales más nuevos, como Scala, desalientan pero permiten efectos secundarios: podrían denominarse lenguajes funcionales imperativos.

El uso de efectos secundarios para E / S probablemente esté bien. A menudo, la E / S (que no sea el registro) solo se realiza en el límite exterior de su sistema. No se produce comunicación externa dentro de su lógica empresarial. Entonces es posible escribir el núcleo de su software en un estilo puro, sin dejar de realizar E / S impuras en una capa externa. Esto también significa que el núcleo puede ser apátrida.

La apatridia tiene una serie de ventajas prácticas, como una mayor razonabilidad y escalabilidad. Esto es muy popular para los backends de aplicaciones web. Cualquier estado se mantiene afuera, en una base de datos compartida. Esto facilita el equilibrio de carga: no tengo que pegar sesiones a un servidor específico. ¿Qué pasa si necesito más servidores? Simplemente agregue otro, porque está usando la misma base de datos. ¿Qué pasa si un servidor falla? Puedo rehacer cualquier solicitud pendiente en otro servidor. Por supuesto, todavía hay estado - en la base de datos. Pero lo hice explícito y lo extraje, y podría usar un enfoque funcional puro internamente si quisiera.

amon
fuente
Gracias por la respuesta detallada. Lo que mantengo como conclusiones es que los efectos secundarios no afectan el valor de la función, debido al razonamiento equitativo, es por eso que "los lenguajes funcionales no permiten / minimizan los efectos secundarios". Los efectos incrustados en los valores de la función afectan y cambian el estado que alguna vez se guarda o se guarda fuera del núcleo del programa. Además, la E / S ocurre en el límite externo de la lógica de negocios.
codebot
3
@codebot, no del todo, en mi opinión. Si se implementa correctamente, los efectos secundarios en la programación funcional deben reflejarse en el tipo de retorno de la función. Por ejemplo, si una función puede fallar (si un archivo en particular no existe o no se puede establecer una conexión de base de datos), entonces el tipo de retorno de la función debería encapsular la falla, en lugar de hacer que la función arroje una excepción. Eche un vistazo a la programación orientada al ferrocarril para ver un ejemplo ( fsharpforfunandprofit.com/posts/recipe-part2 ).
Aaron M. Eshbach
"... podrían llamarse lenguajes imperativos-funcionales": Simon Peyton Jones escribió "... Haskell es el mejor lenguaje de programación imperativo del mundo".
Giorgio
5

Ningún lenguaje de programación elimina los efectos secundarios. Creo que es mejor decir que los lenguajes declarativos contienen efectos secundarios, mientras que los lenguajes imperativos no. Sin embargo, no estoy tan seguro de que nada de esta charla sobre los efectos secundarios llegue a la diferencia fundamental entre los dos tipos de idiomas y que realmente parezca lo que está buscando.

Creo que ayuda ilustrar la diferencia con un ejemplo.

a = b + c

La línea de código anterior podría escribirse en prácticamente cualquier idioma, entonces, ¿cómo podemos determinar si estamos usando un lenguaje imperativo o declarativo? ¿En qué se diferencian las propiedades de esa línea de código en las dos clases de lenguaje?

En un lenguaje imperativo (C, Java, Javascript, etc.), esa línea de código simplemente representa un paso en un proceso. No nos dice nada sobre la naturaleza fundamental de ninguno de los valores. Nos dice que en el momento después de esta línea de código (pero antes de la siguiente línea) aserá igual a bmás, cpero no nos dice nada sobre ael sentido más amplio.

En un lenguaje declarativo (Haskell, Scheme, Excel, etc.) esa línea de código dice mucho más. Establece una relación invariante entre ay los otros dos objetos de tal manera que siempre será el caso aigual a bmás c. Tenga en cuenta, que incluí Excel en la lista de lenguajes declarativos porque incluso si bo ccambios de valor, el hecho será aún permanecen que aserá igual a su suma.

En mi opinión , esto , no los efectos secundarios o el estado, es lo que hace que los dos tipos de idiomas sean diferentes. En un lenguaje imperativo, cualquier línea de código en particular no le dice nada sobre el significado general de las variables en cuestión. En otras palabras, a = b + csolo significa que por un breve momento en el tiempo, apasó a ser igual a la suma de by c.

Mientras tanto, en los lenguajes declarativos, cada línea de código establece una verdad fundamental que existirá durante toda la vida útil del programa. En estos idiomas, a = b + cle dice que pase lo que pase en cualquier otra línea de código asiempre será igual a la suma de by c.

Daniel T.
fuente