data-structures haskell record lenses

data structures - lentes, fclabels, data-accessor-qué biblioteca para el acceso a la estructura y la mutación es mejor



data-structures haskell (1)

Hay al menos tres bibliotecas populares para acceder y manipular campos de registros. Los que conozco son: datos-accessor, fclabels y lentes.

Personalmente comencé con el acceso a datos y los estoy usando ahora. Sin embargo, recientemente en Haskell-Cafe hubo una opinión de que fclabels era superior.

Por lo tanto, estoy interesado en comparar esas tres (y quizás más) bibliotecas.


Hay al menos 4 bibliotecas que conozco que proporcionan lentes.

La noción de una lente es que proporciona algo isomorfo a

data Lens a b = Lens (a -> b) (b -> a -> a)

proporcionando dos funciones: un getter y un setter

get (Lens g _) = g put (Lens _ s) = s

sujeto a tres leyes:

Primero, si pones algo, puedes sacarlo de nuevo

get l (put l b a) = b

Segundo, que obtener y luego establecer no cambia la respuesta

put l (get l a) a = a

Y tercero, poner dos veces es lo mismo que poner una vez, o mejor dicho, que la segunda jugada gana.

put l b1 (put l b2 a) = put l b1 a

Tenga en cuenta que el sistema de tipo no es suficiente para verificar estas leyes por usted, por lo que debe asegurarse de hacerlo usted mismo sin importar la implementación de lentes que use.

Muchas de estas bibliotecas también ofrecen un conjunto de combinadores adicionales en la parte superior y, por lo general, algún tipo de plantilla de maquinaria para generar automáticamente lentes para los campos de tipos de registros simples.

Con eso en mente, podemos recurrir a las diferentes implementaciones:

Implementaciones

fclabels

fclabels es tal vez la fclabels más fácil de las bibliotecas de lentes, porque es a :-> b se puede traducir directamente al tipo anterior. Proporciona una instancia de Category para (:->) que es útil, ya que le permite componer lentes. También proporciona un tipo de Point sin ley que generaliza la noción de una lente utilizada aquí, y algo de fontanería para tratar con isomorfismos.

Un obstáculo para la adopción de fclabels es que el paquete principal incluye la plantilla de plomería de Haskell, por lo que el paquete no es Haskell 98, y también requiere la extensión TypeOperators (bastante controvertida).

acceso a datos

[Editar: data-accessor ya no está utilizando esta representación, pero se ha movido a una forma similar a la de data-lens de data-lens . Me quedo con este comentario, sin embargo]

data-accessor es algo más popular que fclabels , en parte porque es Haskell 98. Sin embargo, su elección de representación interna me hace vomitar en mi boca un poco.

El tipo T que utiliza para representar una lente se define internamente como

newtype T r a = Cons { decons :: a -> r -> (a, r) }

En consecuencia, para get el valor de una lente, debe enviar un valor indefinido para el argumento ''a''. Esto me parece una implementación increíblemente fea y ad hoc.

Dicho esto, Henning ha incluido la plomería template-haskell para generar automáticamente los accessors en un paquete separado '' data-accessor-template ''.

Tiene el beneficio de un conjunto decentemente grande de paquetes que ya lo emplean, siendo Haskell 98, y proporciona la instancia de Category importante, por lo que si no se presta atención a cómo se hace la salchicha, este paquete es realmente bastante razonable elección.

lentes

A continuación, está el paquete de lenses , que observa que una lente puede proporcionar un homomorfismo de mónada de estado entre dos mónadas de estado, definiendo las lentes directamente como tales homomorfismos de mónada.

Si realmente se molestara en proporcionar un tipo para sus lentes, tendrían un tipo de rango 2 como:

newtype Lens s t = Lens (forall a. State t a -> State s a)

Como resultado, no me gusta este enfoque, ya que innecesariamente te saca de Haskell 98 (si quieres que un tipo lo proporcione a tus lentes en abstracto) y te priva de la instancia de Category para lentes, lo que te dejaría los compones con . . La implementación también requiere clases de tipo multiparámetro.

Tenga en cuenta que todas las otras bibliotecas de lentes mencionadas aquí proporcionan algún combinador o se pueden usar para proporcionar el mismo efecto de focalización de estado, de modo que no se gana nada codificando su lente directamente de esta manera.

Además, las condiciones secundarias indicadas al principio no tienen realmente una buena expresión en esta forma. Al igual que con ''fclabels'', esto proporciona el método template-haskell para generar automáticamente lentes para un tipo de registro directamente en el paquete principal.

Debido a la falta de instancia de Category , la codificación barroca y el requisito de template-haskell en el paquete principal, esta es la implementación menos preferida.

lente de datos

[Editar: A partir de 1.8.0, estos han pasado del paquete comonad-transformers al lente de datos]

Mi paquete de lentes de data-lens proporciona lentes en términos de la comonad de la Store .

newtype Lens a b = Lens (a -> Store b a)

dónde

data Store b a = Store (b -> a) b

Ampliado esto es equivalente a

newtype Lens a b = Lens (a -> (b, b -> a))

Puedes ver esto como factorizar el argumento común del getter y el setter para devolver un par que consiste en el resultado de recuperar el elemento, y un setter para poner un nuevo valor nuevamente. Esto ofrece el beneficio computacional que el ''setter'' aquí puede reciclar parte del trabajo utilizado para obtener el valor, lo que permite una operación de ''modificación'' más eficiente que en la definición de fclabels , especialmente cuando los accessors están encadenados.

También hay una buena justificación teórica para esta representación, porque el subconjunto de valores de "Lente" que satisfacen las 3 leyes establecidas al principio de esta respuesta son precisamente aquellos lentes para los que la función envuelta es una "coalgebra comonad" para la tienda comonad . Esto transforma 3 leyes peludas para una lente l hasta 2 equivalentes de Pointless aptos:

extract . l = id duplicate . l = fmap l . l

Este enfoque se notó por primera vez y se describe en Functor Russell O''Connor es Lens como Applicative es para Biplate : Introducción a Multiplate y se blogueó sobre la base de una preimpresión de Jeremy Gibbons.

También incluye una serie de combinadores para trabajar con lentes estrictamente y algunas lentes estándar para contenedores, como Data.Map .

Entonces las lentes en data-lens forman una Category (a diferencia del paquete de lenses ), son Haskell 98 (a diferencia de fclabels / fclabels ), son sanas (a diferencia del back-end del data-accessor ) y proporcionan una implementación un poco más eficiente, data-lens-fd proporciona la funcionalidad para trabajar con MonadState para aquellos dispuestos a salir de Haskell 98, y la maquinaria template-haskell ahora está disponible a través de data-lens-template .

Actualización 28/06/2012: Otras estrategias de implementación de lentes

Lentes de Isomorfismo

Hay otras dos codificaciones de lentes que vale la pena considerar. El primero ofrece una buena forma teórica de ver una lente como una forma de dividir una estructura en el valor del campo y "todo lo demás".

Dado un tipo para isomorfismos

data Iso a b = Iso { hither :: a -> b, yon :: b -> a }

de tal manera que los miembros válidos satisfacen hither . yon = id hither . yon = id , y yon . hither = id yon . hither = id

Podemos representar una lente con:

data Lens a b = forall c. Lens (Iso a (b,c))

Estos son principalmente útiles como una forma de pensar sobre el significado de los lentes, y podemos usarlos como una herramienta de razonamiento para explicar otros lentes.

Lentes Van Laarhoven

Podemos modelar lentes de modo que se puedan componer con (.) id , incluso sin una instancia de Category utilizando

type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a

como el tipo de nuestras lentes.

Entonces, definir una lente es tan fácil como:

_2 f (a,b) = (,) a <$> f b

y puedes validar por ti mismo que la composición de la función es la composición de la lente.

Recientemente escribí sobre cómo puede generalizar aún más las lentes van Laarhoven para obtener familias de lentes que puedan cambiar los tipos de campos, simplemente generalizando esta firma para

type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b

Esto tiene la desafortunada consecuencia de que la mejor manera de hablar sobre lentes es usar el polimorfismo de rango 2, pero no es necesario que use esa firma directamente al definir los lentes.

La Lens I que LensFamily definir para _2 es en realidad una LensFamily .

_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)

He escrito una biblioteca que incluye lentes, familias de lentes y otras generalizaciones, como getters, setters, pliegues y recorridos. Está disponible en hackage como el paquete de lens .

Una vez más, una gran ventaja de este enfoque es que los mantenedores de la biblioteca pueden crear lentes con este estilo en sus bibliotecas sin incurrir en ninguna dependencia de biblioteca de lentes, simplemente suministrando funciones con el tipo Functor f => (b -> fb) -> a -> fa , para sus tipos particulares ''a'' y ''b''. Esto reduce en gran medida el costo de la adopción.

Como no es necesario que use realmente el paquete para definir nuevas lentes, se quita mucha presión de mis preocupaciones anteriores acerca de mantener la biblioteca Haskell 98.