Manejo de problemas de estado en programación funcional

18

Aprendí a programar principalmente desde un punto de vista de OOP (como la mayoría de nosotros, estoy seguro), pero pasé mucho tiempo tratando de aprender a resolver problemas de manera funcional. Tengo una buena comprensión de cómo resolver problemas de cálculo con FP, pero cuando se trata de problemas más complicados, siempre me encuentro volviendo a necesitar objetos mutables. Por ejemplo, si estoy escribiendo un simulador de partículas, querré que se actualicen los "objetos" de partículas con una posición mutable. ¿Cómo se resuelven típicamente los problemas inherentemente "con estado" utilizando técnicas de programación funcional?

Andrew Martin
fuente
44
Es probable que el primer paso sea darse cuenta de que los problemas no son intrínsecamente significativos.
Telastyn
44
Algunos problemas tienen un estado intrínseco, como escribir en una base de datos o dibujar una interfaz gráfica de usuario. Tomando mi ejemplo de simulador de partículas, ¿cuál sería una forma alternativa de pensarlo? Devolver nuevas partículas cada vez que se actualiza su posición para evitar el estado me parece ineficiente, y no es un buen modelo del mundo real.
Andrew Martin
44
Excepto quizás por el ejemplo de la base de datos, esos problemas no son inherentemente con estado. Por ejemplo, para la programación de la GUI, realmente está utilizando el estado mutable como un modelo de tiempo pobre e implícito ; La programación reactiva funcional le permite modelar el tiempo explícitamente sin depender del estado al proporcionar secuencias de eventos que puede combinar.
Tikhon Jelvis
1
Hay una solución más simple: cuando se encuentra con un problema que no se modela fácilmente con técnicas de FP, no utilice la programación funcional para resolverlo. Herramienta adecuada para el trabajo y todo eso ...
Mason Wheeler
1
@AndrewMartin ¿No es un buen modelo del mundo real? Las matemáticas utilizadas en física para modelar el mundo real son puramente funcionales. Con un buen recolector de basura, asignar un objeto es tan barato como golpear un puntero, y el tiempo de recolección es proporcional al número de objetos vivos . En todo caso, apostaría que la principal fuente de ineficiencias en la programación funcional es usar estructuras de datos que no son eficientes en caché. Las listas vinculadas y los árboles binarios no son exactamente elementos secundarios de la eficacia de la memoria caché.
Doval

Respuestas:

20

Los programas funcionales manejan muy bien el estado, pero requieren una forma diferente de verlo. Para su ejemplo de posición, una cosa a considerar es que su posición sea una función del tiempo en lugar de un valor fijo . Esto funciona bien para partículas que siguen una ruta matemática fija, pero requiere una estrategia diferente para manejar un cambio en la ruta, como después de una colisión.

La estrategia básica aquí es crear funciones que tomen un estado y devuelvan el nuevo estado . Por lo tanto, un simulador de partículas sería una función que toma una Setde las partículas como entrada y devuelve una nueva Setde partículas después de un paso de tiempo. Luego, simplemente llama repetidamente a esa función con su entrada establecida en su resultado anterior.

Karl Bielefeldt
fuente
55
+1 Está bien tener estado en FP, simplemente no en estado mutable .
jhewlett
1
Gracias por esta idea. @Logc frustró mis preocupaciones sobre la ineficiencia; Los detalles técnicos de cómo se transforma el estado es un problema de implementación de bajo nivel que el lenguaje en sí mismo debe resolver. Vi a Rich Hickey explicar cómo hace esto con Clojure en un video.
Andrew Martin
1
@jhewlett: Para ser más precisos: FP tiene estado, incluso estado mutable, pero no lo representan usando variables mutables.
Giorgio
9

Como señaló @KarlBielefeldt, el enfoque funcional para tal problema es verlo como un nuevo estado de un estado anterior. Las funciones en sí no contienen ninguna información, por lo que siempre actualizarán el estado m al estado n .

Creo que esto le parece ineficiente porque supone que el estado anterior debe mantenerse en la memoria mientras se calcula el nuevo estado. Tenga en cuenta que la elección entre escribir un estado completamente nuevo o reescribir el antiguo en su lugar es un detalle de implementación desde el punto de vista de un lenguaje funcional.

Por ejemplo, supongamos que tengo una lista de un millón de enteros y quiero aumentar la décima en una unidad. Copiar toda la lista con un nuevo número en su décima posición es un desperdicio, tiene razón; pero es solo la forma conceptual de describir la operación al compilador o intérprete de lenguaje. El compilador o intérprete es libre de tomar la primera lista y simplemente sobrescribir la décima posición.

La ventaja de describir la operación de esta manera es que el compilador puede razonar sobre la situación en la que muchos hilos quieren actualizar la misma lista en diferentes posiciones. Si la operación se describe como "vaya a esta posición y sobrescriba lo que encuentre", entonces es el programador, no el compilador, quien se encarga de asegurarse de que las sobrescrituras no colisionen.

Dicho todo esto, incluso en Haskell hay una mónada estatal que ayuda a modelar situaciones en las que "mantener el estado" es una solución más intuitiva para un problema. Pero también tenga en cuenta que algunos problemas que encuentra " intrínsecamente con estado, como escribir en una base de datos " tienen soluciones inmutables como Datomic . Esto puede ser sorprendente hasta que comprenda que es un concepto, no necesariamente su realización.

logc
fuente
44
Creo que el fragmento sobre la actualización de una lista grande es engañoso; No conozco ningún compilador que realmente realice esa optimización por usted. Incluso si el compilador pudiera hacer eso, solo es posible en casos en los que no se aferra a versiones anteriores de la lista. La solución real es usar una estructura de datos de lista que no requiera copiar todo para cambiar un solo elemento.
Doval
@Doval: "Incluso si el compilador pudiera hacer eso, solo es posible en los casos en que no se aferre a versiones anteriores de la lista": Esto me recuerda los tipos únicos en Clean.
Giorgio
4

Suscribirse al modelo mental correcto ayuda a pensar y gestionar mejor el estado. En mi opinión, el mejor modelo mental es el libro animado . Una vez que haga clic, comprenderá que FP se apoya fuertemente en estructuras de datos persistentes que capturan el estado del mundo y que las funciones se utilizan para hacer la transición de ese estado sin ninguna mutación.

Rich Hickey ilumina estas ideas:

Hay otras conversaciones, pero esto debería enviarlo en la dirección correcta.

Mario T. Lanza
fuente
3

Al escribir aplicaciones grandes y moderadamente grandes, a menudo he encontrado útil diferenciar entre las secciones de la aplicación que tienen estado y las que no tienen estado.

Las clases / estructuras de datos en la sección con estado almacenan los datos de la aplicación y las funciones en esta sección funcionan con conocimiento implícito de los datos de la aplicación.

Las clases / estructuras de datos / funciones en la sección sin estado están ahí para soportar los aspectos puramente algorítmicos de la aplicación. No tienen conocimiento implícito de los datos de la aplicación. Trabajan en una naturaleza puramente funcional. Las partes con estado de la aplicación pueden experimentar un cambio de estado como efecto secundario de las funciones en ejecución en la sección sin estado de la aplicación.

La parte más difícil es descubrir qué clases / funciones colocar en la sección sin estado y qué clases / funciones colocar en la sección con estado, y tener la disciplina para colocarlas en archivos / bibliotecas separadas.

R Sahu
fuente
¿Cómo responde esto a la pregunta? (sin voto negativo)
kravemir
@kravemir, ya sea que escriba una aplicación usando OOP o FP, debe comprender dónde se encuentra el estado de la aplicación.
R Sahu