¿Por qué es bueno no confiar en el cambio de estado?

16

Esta pregunta surge de la pregunta /software/25569/is-haskell-worth-learning

En general, se hacen algunas declaraciones a menudo repetidas sobre cómo Haskell mejora sus habilidades de codificación en otros idiomas y, además, esto se debe a que Haskell no tiene estado y eso es algo bueno.

¿Por qué?

He visto a alguien comparar esto con solo escribir con la mano izquierda, o tal vez cerrar los ojos por un día y solo confiar en el tacto. ¿Seguramente hay más que eso?

¿Se relaciona con el acceso a la memoria del hardware o algo más que es una gran ganancia de rendimiento?

ocodo
fuente
2
Haskell es académico. Vería algunas conferencias de Rich Hickey sobre Clojure; allí hace argumentos pragmáticos asesinos (similares a los 3 puntos de Javier, pero también de una manera simple).
Trabajo
2
Sí, ver clojure.org/state
LennyProgrammers
66
El hecho de que Haskell sea "académico" no significa que no sea práctico o que no sea pragmático.
Tikhon Jelvis

Respuestas:

17

Hay al menos tres grandes ventajas fuera de mi cabeza:

  1. hace que los programas estén más cerca de las expresiones matemáticas. En matemáticas, xno cambia, simplemente no sabes lo que es hasta que resuelves la ecuación.

  2. Al final, no es cambio de estado (después de todo, así es como el equipo trabaja a bajo nivel); pero está limitado por el idioma a lugares específicos. eso le permite al compilador grandes oportunidades para mover el código para optimizarlo, ya que sabe que no cambia nada de lo que depende otro código.

  3. El código concurrente no necesita sincronizarse para acceder a los datos que no cambian, por lo que se mejora la concurrencia, tanto en los sistemas de memoria compartida SMP (todos los sistemas multinúcleo de hoy) como en clústeres sueltos.

Javier
fuente
3
Los defensores de la programación funcional ponen más énfasis en el n. ° 3, es decir, una programación de concurrencia más fácil.
Mchl
44
@Mchl: En mi experiencia, ponen más énfasis en "Es más fácil de entender y razonar sobre", lo que corresponde a 1. Aunque supongo que eso puede diferir entre las comunidades lingüísticas.
sepp2k
+1, respuesta muy completa. @ sepp2k: ambos son importantes, supongo. El razonamiento sobre un programa es lo que hacemos diariamente, y es cierto que si no tiene que verificar que el estado no ha cambiado en alguna función profundamente oculta, es mucho más fácil leer solo métodos de alto nivel y captar lo que está sucediendo. En cuanto al hardware: dado que nos estamos moviendo hacia procesadores de múltiples núcleos y procesadores cada vez más (aunque supongo que pasará un tiempo antes de que las computadoras domésticas obtengan multiprocesadores), tener un lenguaje que facilite la programación concurrente es una ventaja.
Matthieu M.
Buena respuesta, # 2 fue lo que supuse que era la respuesta, y aunque he leído a menudo sobre las ventajas del multiprocesamiento, rara vez se dice claramente, un gran trabajo.
Ocodo
1
@Yttrill, los lenguajes funcionales no son únicos en su dependencia de la recolección de basura y la recolección de basura es mucho más fácil cuando se trata de datos inmutables. Hacer cualquier cálculo en una arquitectura basada en instrucciones significa modificar el estado, eso no significa que los lenguajes funcionales sean de alguna manera más difíciles de paralelizar. Los lenguajes funcionales se balancean en el paralelismo; Búsqueda de datos paralela a Haskell, es una característica que sería casi imposible de agregar a un lenguaje imperativo.
dan_waterworth
4

Aquí hay otra ventaja: acoplamiento reducido. Si tienes un código como:

 function doStuff(x) { return x + y;}

y en otro lugar tienes:

 function doOtherStuff(x) { y++; return y + x;}

entonces las dos funciones dependen implícitamente . No hay una manera fácil de saber que las llamadas doStuffse ven afectadas por las llamadas doOtherStuff. Sin estado mutable, tendría que hacer la conexión explícita.

Por supuesto, este no es un problema con todos los estados mutables: el problema es con el estado mutable generalizado. La solución real es tener la inmutabilidad por defecto y alguna forma de "marcar" y restringir el estado mutable justo donde lo necesita.

Tikhon Jelvis
fuente
+1. Muchos programadores experimentados saben que no deben escribir código como el anterior y no es un gran paso pasar de "el estado mutable es malo en esta situación" a "reduzcamos seriamente la cantidad de estado mutable y escribamos más funcionalmente", pero es un paso que decepcionantemente pocos hacen.
dan_waterworth
2

Una respuesta simplificada es: cuando ve un nombre en un lenguaje puramente funcional, sabe cuál es el valor asociado mediante una simple búsqueda de su definición. Si tiene variables mutables, solo puede determinar por cuál de las varias asignaciones se ejecutó por última vez, por lo que también debe analizar el flujo de control, que a su vez puede ser condicional, dejándolo con múltiples posibilidades. Para obtener una explosión exponencial, solo debe tener en cuenta que el RHS de las asignaciones dependen de las variables, por lo que también debe analizarlas de forma recursiva.

La conclusión en el análisis anterior es que es insostenible sin comentarios que expliquen la intención, los invariantes y la semántica: estos pueden ser difíciles de interpretar y puede ser difícil verificar que la semántica se cumpla en el código real.

Esta respuesta es básicamente una expansión del punto 1 de @ Javier.

Creo que también es una explicación de la popularidad del régimen fraudulento de OO: con OO, el estado mutable se encapsula, lo que facilita mucho el análisis al localizar las mutaciones hasta cierto punto, y permite una expresión y verificación de la semántica mucho más robusta.

Habiendo notado eso, la programación funcional no es la respuesta. La respuesta correcta es un sistema que admite programación inductiva (funcional) y coinductiva (de procedimiento), por lo que las herramientas adecuadas pueden manejar tanto la programación sin estado como la de estado. Es solo que la teoría constructiva (funcional) está bien establecida, mientras que la teoría de la gestión estatal todavía está en pañales.

Yttrill
fuente
Mezclar programación funcional e imperativa es exactamente lo que hace Haskell: las funciones normales son funcionales, mientras que los cálculos con estado se pueden expresar con notación que le permite aislar y controlar cuidadosamente el estado mutable (entre otras cosas). Es por eso que el lenguaje con la implementación STM más práctica es Haskell .
Tikhon Jelvis
Do-notation realmente no tiene nada que hacer computación con estado per se . Do-notation es solo una sintaxis más simple en la parte superior de las canalizaciones de funciones monádicas. El cálculo con estado es simplemente una de las cosas que se puede expresar usando mónadas y, por lo tanto, la anotación.
Jonathan Sterling el
La programación funcional e imperativa mixta es lo que hace casi todos los lenguajes existentes. Haskell puede haber proporcionado una forma de aislar las partes con estado, pero eso no lo convierte en una combinación adecuada: la caridad es más como debería ser (en mi humilde opinión).
Yttrill
2

Como autor de Siege , un DBMS escrito en Haskell, algunos pueden considerar mi opinión sobre el estado mutable en conflicto. Espero mostrar lo contrario.

El propósito del estado mutable es describir el estado actual en el que se encuentra un sistema. Supongamos que tiene un blog y está respaldado por una base de datos, la base de datos describe las publicaciones que tiene en su blog en el momento en que realiza la consulta. eso. ¿Cuántas publicaciones existen en este momento?

Compare esto con el estado inmutable que se utiliza para transmitir hechos. ¿Cuántas publicaciones hubo el 12 de agosto?

Los hechos son fáciles de razonar, el estado mutable no lo es. Sin embargo, el estado mutable no es un efecto impuro malvado que debería ser desterrado del alcance de nuestras mentes; a menudo necesitamos que coexista en el mundo mutable en el que vivimos, solo necesitamos usarlo con más moderación.

dan_waterworth
fuente