¿Cómo se modelan las entidades con una identidad y un estado persistente mutable en un lenguaje de programación funcional?

8

En una respuesta a esta pregunta (escrita por Pete) hay algunas consideraciones sobre la POO versus la PF. En particular, se sugiere que los lenguajes FP no son muy adecuados para modelar objetos (persistentes) que tienen una identidad y un estado mutable.

Me preguntaba si esto es cierto o, en otras palabras, cómo se modelarían los objetos en un lenguaje de programación funcional. Desde mi conocimiento básico de Haskell, pensé que uno podría usar mónadas de alguna manera, pero realmente no sé lo suficiente sobre este tema para llegar a una respuesta clara.

Entonces, ¿cómo se modelan normalmente las entidades con una identidad y un estado persistente mutable en un lenguaje funcional?

Aquí hay algunos detalles adicionales para aclarar lo que tengo en mente. Tome una aplicación Java típica en la que pueda (1) leer un registro de una tabla de base de datos en un objeto Java, (2) modificar el objeto de diferentes maneras, (3) guardar el objeto modificado en la base de datos.

¿Cómo se implementaría esto, por ejemplo, en Haskell? Inicialmente leería el registro en un valor de registro (definido por una definición de datos), realizaría diferentes transformaciones aplicando funciones a este valor inicial (cada valor intermedio es una copia nueva y modificada del registro original) y luego escribiría el valor de registro final a la base de datos.

¿Es esto todo lo que hay que hacer? ¿Cómo puedo asegurarme de que en cada momento solo una copia del registro sea válida / accesible? Uno no quiere tener diferentes valores inmutables que representen diferentes instantáneas del mismo objeto para ser accesibles al mismo tiempo.

Giorgio
fuente
Depende de un idioma. Rich Hickey, el autor de Clojure recomienda que utilice estructuras de datos (listas, diccionarios, conjuntos, etc.) para representar sus datos. No estoy completamente convencido de la idea; Me gusta desreferenciar los campos por su nombre en lugar de índice o mnemotécnico y hacer que el compilador compruebe que no lo arruiné. F # tiene estructuras, que son como clases. msdn.microsoft.com/en-us/library/dd233233.aspx Incluso utilizando Java o C #, puede tener objetos inmutables que pueden "cambiar" creando un objeto completamente nuevo.
Trabajo
1
@gnat: Gracias por editar el título: objetos definitivamente no era el término más apropiado.
Giorgio

Respuestas:

7

La forma habitual de realizar cambios de estado en un lenguaje puro como Haskell es modelarlos como funciones que toman el estado anterior y devuelven una versión modificada. Incluso para objetos complejos, esto es eficiente debido a la estrategia de evaluación perezosa de Haskell: aunque está creando sintácticamente un nuevo objeto, no se copia en su totalidad; cada campo se evalúa solo cuando es necesario.

Si tiene más de unos pocos cambios de estado local, las cosas pueden volverse torpes, que es donde entran las mónadas. El paradigma de la mónada se puede utilizar para encapsular un estado y sus cambios; El ejemplo de libro de texto es la Statemónada que viene con una instalación estándar de Haskell. Sin embargo, tenga en cuenta que una mónada no es nada especial: es solo un tipo de datos que expone dos métodos ( >>=y return) y cumple con algunas expectativas (las 'leyes de la mónada'). Debajo del capó, la mónada del Estado hace exactamente lo mismo: tomar el estado anterior y devolver un estado modificado; solo la sintaxis es mejor.

tdammers
fuente
2
Creo que pereza es la palabra equivocada, es eficiente porque Haskell puede compartir subcomponentes no modificados del estado. Esto puede suceder en cualquier idioma que ponga la mutabilidad en el nivel de tipo.
Daniel Gratzer
@jozefg: aunque eso puede ser cierto, en Haskell al menos su implementación está inherentemente ligada a la pereza del lenguaje; simplemente funciona porque el valor completo de una estructura solo se calcula cuando es necesario, y la cadena de thunks que representan las actualizaciones se evalúa en ese punto y solo en la medida necesaria para determinar cuál es el último valor del miembro solicitado. Los lenguajes estrictos puros necesitan operaciones de actualización de registros como primitivos para ser eficientes; Haskell no lo hace por su pereza.
Julio
2

No soy un desarrollador de lenguaje funcional, así que por favor derribenme en llamas si me equivoco, pero si es correcto, podría ser una analogía interesante.

Una vez me dijeron que Excel es esencialmente un lenguaje funcional. No estoy hablando de VBA y demás, estoy hablando de lo que sucede en la hoja.

Entonces tiene entradas, que pueden ser tabulares, celdas individuales o rangos con nombre, y así sucesivamente, y a través de una cadena de operaciones termina con un resultado, o muchos resultados. Cambie una entrada e inmediatamente fluirá.

¿Esa analogía retiene el agua?

Ian
fuente
1
Por argumento de la autoridad , tienes razón;)
phant0m
1
Creo que usar Excel así es más parecido a la programación de flujo de datos , pero hay un margen de maniobra en el que Excel podría encajar. Personalmente, no estoy seguro de llamarlo un idioma en primer lugar ...
Izkata
1
Tienes un punto ahí. Pero aún así, Excel es funcional en ese sentido. PD: Te recomiendo que eches un vistazo a la presentación, creo que es bastante interesante.
phant0m
@Izkata Excel representaría un caso especial de programación de flujo de datos: programación reactiva
itsbruce
2

Supongo que estás hablando de la característica sin estado del lenguaje funcional puro.

Toma el estado inicial como entrada, devuelve el estado final como salida. Nada fundamentalmente diferente de lo que haces en un idioma con una noción de estado, excepto que eres más explícito al respecto y eso puede ser tedioso y menos eficiente (*).

Tenga en cuenta que no necesariamente tiene que modificar todos los lugares que hacen referencia a su objeto: pueden contener un token y luego solo tiene que modificar la estructura de datos que asigna los tokens a su estado actual. Para entonces, está implementando un sistema completo con un lenguaje sin estado y recupera los problemas de los idiomas completos.


(*) Por ejemplo, he buscado, y no he encontrado, la estructura de datos que me permite devolver en O (1) una copia modificada de una estructura de datos indexable por enteros consecutivos en O (1) también.

Un programador
fuente
+1. Sí, me refería a modelar una entidad con estado como un objeto (que tiene una identidad y un historial de estados posteriores) en un lenguaje sin estado. ¿Cuál sería la contrapartida de su ejemplo (marcado con (*)) en un lenguaje como Java? ¿Una matriz? ¿Una serie de objetos?
Giorgio
@ Jorge, sí. Sé cómo hacer esto en O (log n), pero no en O (1), y luego el factor constante también es un éxito. Tenga en cuenta que he usado estructuras de datos diseñadas para lenguaje funcional puro como una forma de implementar deshacer en lenguajes con estado completo.
Programador
Por cierto, un trie es técnicamente O (1) bajo los mismos supuestos simplificadores que se usan a menudo cuando se discuten tales cosas, siendo una colección de tamaño variable con operaciones de inserción / eliminación cuya complejidad temporal no depende del número de elementos presentes. Sin embargo, no son realmente comparables a una matriz mutable.
CA McCann
Una lista de asociación normal [(Int, v)]le permitirá reemplazar el valor en cualquier índice en tiempo constante simplemente consiguiendo el nuevo valor. Desventajas: el valor anterior todavía está tomando RAM y la búsqueda es lineal.
singpolyma
1

En resumen, cada estado de una entidad es su propia entidad. Entonces, en su lógica comercial clásica de "ordenar", hay una Orderentidad y una OrderVersionentidad. Ambos son inmutables. La lógica que agrega una línea de pedido toma la versión anterior y una nueva OrderLineItemVersioncomo entrada y devuelve una nueva OrderVersionentidad.

Facilita algunas cosas (particularmente la funcionalidad "deshacer"), pero algunas cosas son más difíciles.

Scott Whitlock
fuente