Estoy tratando de entender el manejo de datos inmutables en FP (específicamente en F #, pero otros FP también están bien) y romper el viejo hábito del pensamiento completo (estilo OOP). Una parte de la respuesta seleccionada a la pregunta aquí reiteró mi búsqueda de cualquier crítica sobre problemas que se resuelven mediante representaciones con estado en OOP con inmutables en FP (por ejemplo: una cola con productores y consumidores). ¿Alguna idea o enlaces son bienvenidos? Gracias por adelantado.
Editar : para aclarar un poco más la pregunta, ¿cómo se compartirían las estructuras inmutables (por ejemplo, cola) simultáneamente en varios subprocesos (por ejemplo, productor y consumidor) en FP
f#
functional-programming
immutability
Venkram
fuente
fuente
Respuestas:
Aunque a veces se expresa de esa manera, la programación funcional¹ no impide los cálculos con estado. Lo que hace es forzar al programador a hacer explícito el estado.
Por ejemplo, tomemos la estructura básica de algún programa usando una cola imperativa (en algunos pseudolenguaje):
La estructura correspondiente con una estructura de datos de cola funcional (todavía en un lenguaje imperativo, para abordar una diferencia a la vez) se vería así:
Como la cola ahora es inmutable, el objeto en sí no cambia. En este pseudocódigo, en
q
sí mismo es una variable; las asignacionesq := Queue.add(…)
yq := tail
hacer que apunte a un objeto diferente. La interfaz de las funciones de la cola ha cambiado: cada una debe devolver el nuevo objeto de la cola que resulta de la operación.En un lenguaje puramente funcional, es decir, en un lenguaje sin efectos secundarios, debe hacer explícito todo el estado. Dado que el productor y el consumidor presumiblemente están haciendo algo, su estado también debe estar en la interfaz de su interlocutor.
Observe cómo ahora cada parte del estado se administra explícitamente. Las funciones de manipulación de colas toman una cola como entrada y producen una nueva cola como salida. El productor y el consumidor también pasan su estado.
La programación concurrente no encaja muy bien dentro de la programación funcional, pero se adapta muy bien a la programación funcional. La idea es ejecutar un grupo de nodos de computación separados y dejarlos intercambiar mensajes. Cada nodo ejecuta un programa funcional y su estado cambia a medida que envía y recibe mensajes.
Continuando con el ejemplo, dado que hay una sola cola, es administrada por un nodo en particular. Los consumidores envían a ese nodo un mensaje para obtener un elemento. Los productores envían a ese nodo un mensaje para agregar un elemento.
El único lenguaje "industrializado" que acerta la concurrencia³ es Erlang . Aprender Erlang es definitivamente el camino hacia la iluminación⁴ sobre la programación concurrente.
¡Todos cambien a idiomas libres de efectos secundarios ahora!
¹ Este término tiene varios significados; aquí creo que lo estás usando para significar programación sin efectos secundarios, y ese es el significado que también estoy usando.
² La programación con estado implícito es una programación imperativa ; La orientación a objetos es una preocupación completamente ortogonal.
³ Inflamatorio, lo sé, pero lo digo en serio. Los subprocesos con memoria compartida son el lenguaje ensamblador de la programación concurrente. La transmisión de mensajes es mucho más fácil de entender, y la falta de efectos secundarios realmente brilla tan pronto como se introduce la concurrencia.
⁴ Y esto viene de alguien que no es fanático de Erlang, pero por otras razones.
fuente
El comportamiento con estado en un lenguaje FP se implementa como una transformación de un estado anterior a un estado nuevo. Por ejemplo, en cola sería una transformación de una cola y un valor a una nueva cola con el valor en cola. Dequeue sería una transformación de una cola a un valor y una nueva cola con el valor eliminado. Se han ideado construcciones como mónadas para abstraer esta transformación de estado (y otros resultados de cálculo) de maneras útiles
fuente
Su pregunta es lo que se llama un "problema XY". Específicamente, el concepto que cita (hacer cola con productores y consumidores) es en realidad una solución y no un "problema" como usted describe. Esto presenta una dificultad porque está solicitando una implementación puramente funcional de algo que es inherentemente impuro. Entonces mi respuesta comienza con una pregunta: ¿cuál es el problema que estás tratando de resolver?
Hay muchas formas para que múltiples productores envíen sus resultados a un solo consumidor compartido. Quizás la solución más obvia en F # es hacer del consumidor un agente (también conocido como
MailboxProcessor
) y hacer que los productores denPost
sus resultados al agente de consumo. Esto utiliza una cola internamente y no es pura (enviar mensajes en F # es un efecto secundario no controlado, una impureza).Sin embargo, es bastante probable que el problema subyacente sea algo más parecido al patrón de dispersión-reunión de la programación paralela. Para resolver este problema, puede crear una matriz de valores de entrada y luego
Array.Parallel.map
sobre ellos y recopilar los resultados utilizando una serieArray.reduce
. Alternativamente, puede usar funciones delPSeq
módulo para procesar los elementos de secuencias en paralelo.También debo enfatizar que no hay nada malo con el pensamiento con estado. La pureza tiene ventajas, pero ciertamente no es una panacea y usted también debe darse cuenta de sus defectos. De hecho, esta es precisamente la razón por la que F # no es un lenguaje funcional puro: por lo tanto, puede usar impurezas cuando sea preferible.
fuente
Clojure tiene un concepto muy bien pensado de estado e identidad, que está estrechamente relacionado con la concurrencia. La inmutabilidad juega un papel importante, todos los valores en Clojure son inmutables y se puede acceder a ellos a través de referencias. Las referencias son más que simples punteros. Administran el acceso al valor, y hay varios tipos de ellos con diferentes semánticas. Se puede modificar una referencia para apuntar a un nuevo valor (inmutable), y se garantiza que dicho cambio será atómico. Sin embargo, después de la modificación, todos los otros subprocesos siguen funcionando en el valor original, al menos hasta que vuelvan a acceder a la referencia.
Le recomiendo que lea un excelente artículo sobre el estado y la identidad en Clojure , explica los detalles mucho mejor de lo que podría.
fuente