haskell clojure functional-programming immutability lens

Al manipular estructuras de datos inmutables, ¿cuál es la diferencia entre la asociación de Clojure y las lentes de Haskell?



functional-programming immutability (4)

Estás hablando de dos cosas muy diferentes.

Puede usar lens para resolver problemas similares como assoc-in, donde está usando tipos de colección ( Data.Map , Data.Vector ) que coinciden con la semántica, pero hay diferencias.

En idiomas no tipificados como Clojure, es común estructurar los datos de su dominio en términos de colecciones que tienen contenidos no estáticos (hash-maps, vectores, etc.) incluso cuando se trata de datos de modelado que son convencionalmente estáticos.

En Haskell, estructuraría sus datos utilizando un registro y ADT, donde, si bien puede expresar contenidos que pueden o no existir (o envolver una colección), el contenido predeterminado es el contenido conocido de forma estática.

Una biblioteca para consultar sería http://hackage.haskell.org/package/lens-aeson donde tiene documentos JSON que tienen contenidos posiblemente variables.

Los ejemplos demuestran que cuando su ruta y tipo no coinciden con la estructura / los datos, arroja un valor Nothing lugar de Just a .

La lente no hace nada más que proporcionar un comportamiento de captador / ajustador de sonido. No expresa una expectativa particular acerca de cómo se ven sus datos, mientras que assoc-in solo tiene sentido con colecciones asociativas con contenidos posiblemente no deterministas.

Otra diferencia aquí es la pureza y la pereza frente a la semántica estricta e impura. En Haskell, si nunca usó los estados "más antiguos", y solo el más reciente, entonces solo se realizará ese valor.

Las lentes dr, tal como se encuentran en Lens y otras bibliotecas similares, son más generales, más útiles, de tipo seguro y especialmente agradables en lenguajes PF perezosos / puros.

Necesito manipular y modificar colecciones inmutables profundamente anidadas (mapas y listas), y me gustaría entender mejor los diferentes enfoques. Estas dos bibliotecas resuelven más o menos el mismo problema, ¿verdad? ¿En qué se diferencian? ¿Para qué tipo de problemas es más adecuado un enfoque que el otro?

assoc-in Clojure
lens de lens


Esta pregunta es algo similar a preguntar cuál es la diferencia entre las mónadas de Clojure y Haskell. Voy a imitar las respuestas hasta ahora: seguro es como una mónada de la List , pero las mónadas son mucho más genéricas y poderosas.

Pero, esto es un poco tonto, ¿verdad? Las mónadas se han implementado en Clojure. ¿Por qué no se usan todo el tiempo? Clojure tiene en su núcleo una filosofía diferente acerca de cómo manejar el estado, pero aún se siente libre para tomar prestadas buenas ideas de grandes idiomas como Haskell en sus bibliotecas.

Entonces, seguro, assoc-in , get-in , update-in , etc. son una especie de lentes para estructuras de datos asociativos. Y hay implementaciones de lentes en general en Clojure por ahí. ¿Por qué no se usan todo el tiempo? Es una diferencia en filosofía (y tal vez la extraña sensación de que con todos los setters y getters estaríamos haciendo otro Java dentro de Clojure y de alguna manera terminaríamos casándonos con nuestra madre). Pero, Clojure se siente libre de tomar prestadas buenas ideas, y puedes ver enfoques inspirados en lentes que se abren camino en proyectos geniales como Om y Enliven.

Debes tener cuidado al hacer tales preguntas, porque como los medio hermanos que ocupan parte del mismo espacio, Clojure y Haskell están obligados a pedir prestado unos a otros y discutir un poco acerca de quién tiene la razón.


La assoc-in de Clojure le permite especificar una ruta a través de una estructura de datos anidada usando enteros y palabras clave e introducir un nuevo valor en esa ruta. Tiene socios dissoc-in , get-in y update-in que eliminan elementos, los obtienen sin eliminarlos o los modifican respectivamente.

Las lentes son una noción particular de la programación bidireccional donde se especifica un enlace entre dos fuentes de datos y ese enlace le permite reflejar las transformaciones de una a otra. En Haskell, esto significa que puede crear lentes o valores similares a lentes que conectan una estructura de datos completa a algunas de sus partes y luego usarlas para transmitir cambios de las partes a la totalidad.

Aquí hay una analogía. Si nos fijamos en un uso de assoc-in está escrito como

(assoc-in whole path subpart)

y podríamos obtener cierta información al pensar en el path como una lente y assoc-in como un combinador de lentes. De forma similar, puede escribir (utilizando el paquete de lens Haskell)

set lens subpart whole

para que conectemos assoc-in con set y path con lens . También podemos completar la tabla.

set assoc-in view get-in over update-in (unneeded) dissoc-in -- this is special because `at` and `over` -- strictly generalize dissoc-in

Eso es un comienzo para las similitudes, pero también hay una gran diferencia. En muchos sentidos, la lens es mucho más genérica que la familia *-in de funciones de Clojure. Normalmente, esto no es un problema para Clojure porque la mayoría de los datos de Clojure se almacenan en estructuras anidadas hechas de listas y diccionarios. Haskell usa muchos más tipos personalizados muy libremente y su sistema de tipos refleja información sobre ellos. Las lentes generalizan la familia de funciones *-in porque funcionan sin problemas en un dominio mucho más complejo.

Primero, incrustemos tipos de Clojure en Haskell y escribamos la familia de funciones *-in .

type Dict a = Map String a data Clj = CljVal -- Dynamically typed Clojure value, -- not an array or dictionary | CljAry [Clj] -- Array of Clojure types | CljDict (Dict Clj) -- Dictionary of Clojure types makePrisms ''''Clj

Ahora podemos usar set as assoc-in casi directamente.

(assoc-in whole [1 :foo :bar 3] part) set ( _CljAry . ix 1 . _CljDict . ix "foo" . _CljDict . ix "bar" . _CljAry . ix 3 ) part whole

Obviamente, esto tiene mucho más ruido sintáctico, pero denota un grado más alto de testimonio explícito sobre lo que significa el "camino" hacia un tipo de datos, en particular, denota si estamos descendiendo a una matriz o un diccionario. Podríamos, si quisiéramos, eliminar parte de ese ruido adicional al crear Clj instancia de Clj en la clase de Ixed Haskell Ixed , pero en este punto apenas vale la pena.

En cambio, lo que se debe hacer es que la assoc-in está aplicando a un tipo muy particular de descenso de datos. Es más general que los tipos que IFn anteriormente debido a la tipificación dinámica y la sobrecarga de IFn de IFn , pero una estructura fija muy similar como esa podría incrustarse en Haskell con poco esfuerzo.

Sin embargo, las lentes pueden ir mucho más lejos y hacerlo con mayor seguridad de tipo. Por ejemplo, el ejemplo anterior en realidad no es un verdadero "Lens", sino un "Prisma" o "Traversal" que permite al sistema de tipos identificar de manera estática la posibilidad de no realizar ese recorrido. Nos obligará a pensar en condiciones de error como esas (incluso si optamos por ignorarlas).

Lo que es importante es que podemos estar seguros de que, cuando tenemos una lente verdadera, el descenso del tipo de datos no puede fallar, ese tipo de garantía es imposible de hacer en Clojure.

Podemos definir tipos de datos personalizados y hacer lentes personalizados que desciendan a ellos de una manera segura.

data Point = Point { _latitude :: Double , _longitude :: Double , _meta :: Map String String } deriving Show makeLenses ''''Point > let p0 = Point 0 0 > let p1 = set latitude 3 p0 > view latitude p1 3.0 > view longitude p1 0.0 > let p2 = set (meta . ix "foo") "bar" p1 > preview (meta . ix "bar") p2 Nothing > preview (meta . ix "foo") p2 Just "bar"

También podemos generalizar a Lenses (realmente Traversals) que se dirigen a múltiples subpartes similares a la vez

dimensions :: Lens Point Double > let p3 = over dimensions (+ 10) p0 > get latitude p3 10.0 > get longitude p3 10.0 > toListOf dimensions p3 [10.0, 10.0]

O incluso apuntar a subpartes simuladas que en realidad no existen pero aún forman una descripción equivalente de nuestros datos

eulerAnglePhi :: Lens Point Double eulerAngleTheta :: Lens Point Double eulerAnglePsi :: Lens Point Double

En términos generales, los lentes generalizan el tipo de interacción basada en la trayectoria entre valores enteros y subpartes de valores que la familia de funciones Clojure *-in . Puede hacer mucho más en Haskell porque Haskell tiene una noción mucho más desarrollada de tipos y lentes, ya que los objetos de primera clase generalizan ampliamente las nociones de obtención y configuración que simplemente se presentan con las funciones *-in .


assoc-in puede ser más versátil que la lens en algunos casos, porque puede crear niveles en la estructura si no existen.

lens ofrece Folds , que derriban la estructura y devuelven un resumen de los valores contenidos, y Traversals que modifican los elementos de la estructura (posiblemente apuntan a varios elementos a la vez, posiblemente no hacen nada si los elementos objetivo no están presentes) mientras se mantiene La "forma" general de la estructura. Pero creo que sería difícil crear niveles intermedios utilizando lens .

Otra diferencia que veo con las assoc-in en Clojure es que parece que solo tienen que ver con la obtención y configuración de valores, mientras que la definición misma de una lente es compatible con "hacer algo con el valor", que algo posiblemente involucre a los lados. efectos

Por ejemplo, supongamos que tenemos una tupla (1,Right "ab") . El segundo componente es un tipo de suma que puede contener una cadena. Queremos cambiar el primer carácter de la cadena leyéndolo desde la consola. Esto se puede hacer con lentes de la siguiente manera:

(_2._Right._Cons._1) (/_ -> getChar) (1,Right "ab") -- reads char from console and returns the updated structure

Si la cadena no está presente, o está vacía, no se hace nada:

(_2._Right._Cons._1) (/_ -> getChar) (1,Left 5) -- nothing read (_2._Right._Cons._1) (/_ -> getChar) (1,Right "") -- nothing read