Ventajas de la progtwigción sin estado?

Recientemente he estado aprendiendo sobre progtwigción funcional (específicamente Haskell, pero también he asistido a 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 posibilidad 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? ¿La progtwigción funcional o sin estado es más productiva que la de procedimiento? ¿Es probable que continuaré usando Haskell u otro lenguaje funcional más adelante, o debo aprenderlo solo para entenderlo?

Me importa menos el rendimiento que la productividad. Así que principalmente estoy preguntando si seré más productivo en un lenguaje funcional que en un procedimiento / orientado a objetos / lo que sea.

Lea la progtwigción funcional en pocas palabras .

La progtwigción sin estado ofrece muchas ventajas, entre las que destaca el código multiproceso y simultáneo. Para decirlo sin rodeos, el estado mutable es enemigo del código multiproceso. Si los valores son inmutables por defecto, los progtwigdores no tienen que preocuparse de que un hilo mute el valor del estado compartido entre dos hilos, por lo que elimina una clase completa de errores de subprocesamiento múltiple relacionados con las condiciones de carrera. Como no hay condiciones de carrera, tampoco hay ninguna razón para usar lockings, por lo que la inmutabilidad elimina también toda otra clase de errores relacionados con los lockings.

Esa es la gran razón por la cual la progtwigción funcional es importante, y probablemente la mejor para saltar en el tren de progtwigción funcional. También hay muchos otros beneficios, incluida la depuración simplificada (es decir, las funciones son puras y no cambian el estado en otras partes de una aplicación), un código más escueto y expresivo, menos un código repetitivo en comparación con los idiomas que dependen en gran medida de los patrones de diseño y el comstackdor puede optimizar su código de forma más agresiva.

Cuantas más piezas de su progtwig son apátridas, más formas hay de armar las piezas sin romper nada . El poder del paradigma sin estado radica no en la apatridia (o pureza) per se , sino en la capacidad que le brinda para escribir funciones potentes y reutilizables y combinarlas.

Puede encontrar un buen tutorial con muchos ejemplos en el documento de John Hughes Why Functional Programming Matters (PDF).

Serás mucho más productivo, especialmente si eliges un lenguaje funcional que también tenga tipos de datos algebraicos y coincidencia de patrones (Caml, SML, Haskell).

Muchas de las otras respuestas se han centrado en el lado del rendimiento (paralelismo) de la progtwigción funcional, que creo que es muy importante. Sin embargo, usted específicamente preguntó acerca de la productividad, como en, ¿puede progtwigr lo mismo más rápido en un paradigma funcional que en un paradigma imperativo.

De hecho, encuentro (por experiencia personal) que la progtwigción en F # coincide con la forma en que pienso mejor, y por eso es más fácil. Creo que esa es la mayor diferencia. He progtwigdo tanto en F # como en C #, y hay mucho menos “pelear el idioma” en F #, lo cual me encanta. No tiene que pensar en los detalles en F #. Aquí hay algunos ejemplos de lo que descubrí que realmente disfruto.

Por ejemplo, aunque F # está tipado estáticamente (todos los tipos se resuelven en tiempo de comstackción), la inferencia de tipo determina qué tipos tiene, por lo que no tiene que decirlo. Y si no puede resolverlo, automáticamente hace que su función / clase / lo que sea sea genérica. Entonces nunca tienes que escribir ningún genérico, es automático. Encuentro 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 #, descubro que realmente extraño esta inferencia de tipo, nunca te das cuenta de lo distraída que es hasta que ya no necesites hacerlo.

También en F #, en lugar de escribir bucles, llamas a funciones. Es un cambio sutil, pero significativo, porque ya no tienes que pensar en la construcción del lazo. Por ejemplo, aquí hay una pieza de código que pasaría y coincidiría con algo (no puedo recordar qué, es de un rompecabezas de Euler de proyecto):

let matchingFactors = factors |> Seq.filter (fun x -> largestPalindrome % x = 0) |> Seq.map (fun x -> (x, largestPalindrome / x)) 

Me doy cuenta de que hacer un filtro y luego un mapa (eso es una conversión de cada elemento) en C # sería bastante simple, pero hay que pensar en un nivel inferior. Particularmente, tendría que escribir el bucle en sí mismo, y tener su propia statement explícita si, y ese tipo de cosas. Desde que aprendí F #, me di cuenta de que me resulta más fácil codificar de forma funcional, donde si quieres filtrar, escribes “filter”, y si quieres hacer un mapa, 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ías, te permite “conectar” la salida de una expresión a la entrada de otra expresión. Hace que el código siga cómo pienso más. Como en el fragmento de código anterior, eso significa: “tomar la secuencia de factores, filtrarla y luego asignarla”. Es un nivel muy alto de pensamiento, que no se obtiene en un lenguaje de progtwigción imperativo porque estás tan ocupado escribiendo las instrucciones loop y if. Es lo que más extraño cuando entro a otro idioma.

Entonces, en general, aunque puedo progtwigr en C # y F #, me resulta más fácil usar F # porque se puede pensar en un nivel superior. Yo diría que debido a que los detalles más pequeños se eliminan de la progtwigció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 progtwigción funcional. F # se puede escribir de manera imperativa, así que aquí hay un ejemplo directo de cómo puede tener el estado mutable en F #:

 let mutable x = 5 for i in 1..10 do x < - x + i 

Considera todos los errores difíciles que has pasado durante mucho tiempo depurando.

Ahora, ¿cuántos de esos errores se debieron a “interacciones involuntarias” entre dos componentes separados de un progtwig? (Casi todos los errores de subprocesamiento tienen esta forma: carreras que implican la escritura de datos compartidos, interlockings, … 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 plantearía que al menos 1 de cada 3 ‘bichos duros’ entran en esta categoría.

Ahora si cambias a progtwigción sin estado / inmutable / pura, todos esos errores desaparecen. En su lugar, se le presentan algunos desafíos nuevos (por ejemplo, cuando desea que diferentes módulos interactúen con el entorno), pero en un lenguaje como Haskell, esas interacciones se vuelven explícitamente reificadas en el sistema de tipos, lo que significa que simplemente puede ver el tipo de una función y una razón sobre el tipo de interacciones que puede tener con el rest del progtwig.

Esa es la gran victoria de la IMO “inmutabilidad”. En un mundo ideal, todos diseñaríamos excelentes API 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 involuntarias / implícitas / entre bastidores entre los componentes.

Una de las ventajas de las funciones sin estado es que permiten el precalculado o el almacenamiento en caché de los valores de retorno de la función. Incluso algunos comstackdores C le permiten marcar explícitamente funciones como sin estado para mejorar su capacidad de optimización. Como muchos otros han notado, las funciones sin estado son mucho más fáciles de paralelar.

Pero la eficiencia no es la única preocupación. Una función pura es más fácil de probar y depurar ya que todo lo que la afecta está explícitamente establecido. Y cuando se progtwig en un lenguaje funcional, uno adquiere el hábito de hacer tan pocas funciones “sucias” (con E / S, etc.) como sea posible. Separar las cosas con estado de esta manera es una buena forma de diseñar progtwigs, incluso en lenguajes no tan funcionales.

Los lenguajes funcionales pueden tomar un tiempo para “obtener”, y es difícil de explicar a alguien que no ha pasado por ese proceso. Pero la mayoría de las personas que persisten lo suficiente finalmente se dan cuenta de que el alboroto vale la pena, incluso si no terminan usando mucho los lenguajes funcionales.

Sin estado, es muy fácil paralelizar automáticamente su código (dado que las CPU están hechas con más y más núcleos, esto es muy importante).

Hace un tiempo escribí una publicación sobre este tema: Sobre la importancia de la pureza .

Las aplicaciones web sin estado son esenciales cuando comienza a tener un mayor tráfico.

Podría haber una gran cantidad de 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 servidor. Puede utilizar 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 siempre se dirija a la misma instancia.

Los equilibradores de carga a menudo tienen la capacidad de tener “sesiones adhesivas” donde el equilibrador de carga sabe cómo a qué servidor enviar la solicitud de los usuarios. Sin embargo, esto no es ideal, por ejemplo, significa que cada vez que reinicie 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, estos días hay una gran cantidad de excelentes productos nosql disponibles para esto (redis, mongo, elasticsearch, memcached). De esta manera, los servidores web son apátridas, pero usted todavía tiene el lado del servidor del estado y la disponibilidad de este estado se puede gestionar eligiendo la configuración correcta del almacén de datos. Estas tiendas de datos generalmente tienen una gran redundancia por lo que casi siempre es posible realizar cambios en su aplicación web e incluso en el almacén de datos sin afectar a los usuarios.