Recientemente he estado aprendiendo sobre programación funcional (específicamente Haskell, pero también he visto tutoriales sobre Lisp y Erlang). Si bien encontré los conceptos muy esclarecedores, todavía no veo el lado práctico del concepto de "sin efectos secundarios". ¿Cuáles son las ventajas prácticas de esto? Estoy tratando de pensar en la mentalidad funcional, pero hay algunas situaciones que parecen demasiado complejas sin la capacidad de guardar el estado de una manera fácil (no considero que las mónadas de Haskell sean "fáciles").
¿Vale la pena continuar aprendiendo Haskell (u otro lenguaje puramente funcional) en profundidad? ¿Es la programación funcional o sin estado realmente más productiva que procesal? ¿Es probable que continúe usando Haskell u otro lenguaje funcional más tarde, o debería aprenderlo solo para entenderlo?
Me importa menos el rendimiento que la productividad. Así que principalmente pregunto si seré más productivo en un lenguaje funcional que en un procedimiento / orientado a objetos / lo que sea.
fuente
Cuantas más piezas de su programa sean apátridas, más formas habrá de armar piezas sin que nada se rompa . El poder del paradigma sin estado no reside en la apatridia (o la pureza) per se , sino en la capacidad que le brinda para escribir funciones poderosas y reutilizables y combinarlas.
Puede encontrar un buen tutorial con muchos ejemplos en el artículo de John Hughes Why Functional Programming Matters (PDF).
Usted será pegotes más productivo, especialmente si tienes que elegir un lenguaje funcional que también tiene tipos de datos algebraicos y la coincidencia de patrones (Caml, SML, Haskell).
fuente
Muchas de las otras respuestas se han centrado en el lado del rendimiento (paralelismo) de la programación funcional, lo que creo que es muy importante. Sin embargo, usted preguntó específicamente sobre la productividad, ya que en, ¿puede programar lo mismo más rápido en un paradigma funcional que en un paradigma imperativo?
De hecho, encuentro (por experiencia personal) que la programación en F # coincide con la forma en que pienso mejor, por lo que es más fácil. Creo que esa es la mayor diferencia. He programado tanto en F # como en C #, y hay mucho menos "lucha contra el lenguaje" en F #, que me encanta. No tiene que pensar en los detalles en F #. Aquí hay algunos ejemplos de lo que he encontrado que realmente disfruto.
Por ejemplo, aunque F # esté estáticamente tipado (todos los tipos se resuelven en tiempo de compilación), la inferencia de tipos determina qué tipos tiene, por lo que no tiene que decirlo. Y si no puede resolverlo, automáticamente convierte su función / clase / lo que sea genérico. Así que nunca tienes que escribir ningún genérico, todo es automático. Creo que eso significa que paso más tiempo pensando en el problema y menos cómo implementarlo. De hecho, cada vez que vuelvo a C #, encuentro que realmente extraño esta inferencia de tipo, nunca te das cuenta de lo molesto que es hasta que ya no tienes que hacerlo.
También en F #, en lugar de escribir bucles, se llaman funciones. Es un cambio sutil, pero significativo, porque ya no tienes que pensar en la construcción del bucle. Por ejemplo, aquí hay una pieza de código que pasaría y coincidiría con algo (no recuerdo qué, es de un proyecto de rompecabezas de Euler):
Me doy cuenta de que hacer un filtro y luego un mapa (que es una conversión de cada elemento) en C # sería bastante simple, pero hay que pensar en un nivel inferior. En particular, tendrías que escribir el ciclo en sí y tener tu propia declaración if explícita, y ese tipo de cosas. Desde que aprendí F #, me di cuenta de que me resulta más fácil codificar de manera funcional, donde si quieres filtrar, escribes "filter", y si quieres mapear, escribes "map", en lugar de implementar Cada uno de los detalles.
También me encanta el operador |>, que creo que separa F # de ocaml, y posiblemente otros lenguajes funcionales. Es el operador de tubería, le permite "canalizar" la salida de una expresión en la entrada de otra expresión. Hace que el código siga cómo pienso más. Al igual que en el fragmento de código anterior, que dice: "tome la secuencia de factores, filtre y luego asigne". Es un nivel de pensamiento muy alto, que no se obtiene en un lenguaje de programación imperativo porque estás muy ocupado escribiendo el bucle y las declaraciones if. Es lo único que más extraño cada vez que voy a otro idioma.
Entonces, en general, aunque puedo programar en C # y F #, me resulta más fácil usar F # porque puedes pensar en un nivel superior. Yo diría que debido a que los detalles más pequeños se eliminan de la programación funcional (al menos en F #), soy más productivo.
Editar : Vi en uno de los comentarios que solicitó un ejemplo de "estado" en un lenguaje de programación funcional. F # se puede escribir imperativamente, así que aquí hay un ejemplo directo de cómo puede tener un estado mutable en F #:
fuente
a |> b (p1, p2)
es solo azúcar sintáctico parab (a, p1, p2)
. Combina esto con la asociatividad correcta y lo tienes.Considere todos los errores difíciles que ha pasado mucho tiempo depurando.
Ahora, ¿cuántos de esos errores se debieron a "interacciones no intencionadas" entre dos componentes separados de un programa? (Casi todos los errores de subprocesos tienen esta forma: razas que implican escribir datos compartidos, puntos muertos, ... Además, es común encontrar bibliotecas que tienen algún efecto inesperado en el estado global, o leer / escribir el registro / entorno, etc.) I postularía que al menos 1 de cada 3 'errores duros' entran en esta categoría.
Ahora, si cambias a programación sin estado / inmutable / pura, todos esos errores desaparecen. En su lugar, se le presentan algunos desafíos nuevos (por ejemplo, cuando lo hace querer que diferentes módulos para interactuar con el medio ambiente), pero en un lenguaje como Haskell, esas interacciones quedan materializadas de forma explícita en el sistema de tipos, lo que significa que se puede simplemente mirar el tipo de una función y razón sobre el tipo de interacciones que puede tener con el resto del programa.
Esa es la gran victoria de la 'inmutabilidad' de la OMI. En un mundo ideal, todos diseñaríamos API fabulosas e incluso cuando las cosas fueran mutables, los efectos serían locales y bien documentados y las interacciones 'inesperadas' se mantendrían al mínimo. En el mundo real, hay muchas API que interactúan con el estado global de innumerables maneras, y estas son la fuente de los errores más perniciosos. Aspirar a la apatridia es aspirar a deshacerse de las interacciones no intencionadas / implícitas / detrás de escena entre los componentes.
fuente
Una ventaja de las funciones sin estado es que permiten el cálculo previo o el almacenamiento en caché de los valores de retorno de la función. Incluso algunos compiladores de C le permiten marcar explícitamente funciones como apátridas para mejorar su optimizabilidad. Como muchos otros han notado, las funciones sin estado son mucho más fáciles de paralelizar.
Pero la eficiencia no es la única preocupación. Una función pura es más fácil de probar y depurar ya que cualquier cosa que la afecte se indica explícitamente. Y cuando se programa en un lenguaje funcional, uno tiene la costumbre de hacer la menor cantidad de funciones "sucias" (con E / S, etc.) como sea posible. Separar las cosas con estado de esta manera es una buena manera de diseñar programas, incluso en lenguajes no tan funcionales.
Los lenguajes funcionales pueden tomar un tiempo para "entenderse", y es difícil de explicar a alguien que no ha pasado por ese proceso. Pero la mayoría de las personas que persisten el tiempo suficiente finalmente se dan cuenta de que el alboroto vale la pena, incluso si no terminan usando mucho los lenguajes funcionales.
fuente
sin(PI/3)
en su código, donde PI es una constante, el compilador podría evaluar esta función en tiempo de compilación e incrustar el resultado en el código generado.Sin estado, es muy fácil paralelizar automáticamente su código (ya que las CPU están hechas con más y más núcleos, esto es muy importante).
fuente
Las aplicaciones web sin estado son esenciales cuando comienza a tener un mayor tráfico.
Podría haber muchos datos de usuario que no desea almacenar en el lado del cliente por razones de seguridad, por ejemplo. En este caso, debe almacenarlo en el lado del servidor. Puede usar la sesión predeterminada de las aplicaciones web, pero si tiene más de una instancia de la aplicación, deberá asegurarse de que cada usuario esté siempre dirigido a la misma instancia.
Los equilibradores de carga a menudo tienen la capacidad de tener 'sesiones fijas' donde el equilibrador de carga sabe de algún modo a qué servidor enviar la solicitud de los usuarios. Sin embargo, esto no es ideal, por ejemplo, significa que cada vez que reinicia su aplicación web, todos los usuarios conectados perderán su sesión.
Un mejor enfoque es almacenar la sesión detrás de los servidores web en algún tipo de almacén de datos, en estos días hay un montón de excelentes productos nosql disponibles para esto (redis, mongo, elasticsearch, memcached). De esta manera, los servidores web no tienen estado, pero aún tiene un estado del lado del servidor y la disponibilidad de este estado se puede administrar eligiendo la configuración correcta del almacén de datos. Estos almacenes de datos generalmente tienen una gran redundancia, por lo que casi siempre debería ser posible realizar cambios en su aplicación web e incluso en el almacén de datos sin afectar a los usuarios.
fuente
Hace un tiempo escribí una publicación sobre este tema: Sobre la importancia de la pureza .
fuente