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

173

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

Personalmente comencé con el acceso a datos y los estoy usando ahora. Sin embargo, recientemente en Haskell-Cafe había una opinión de que las etiquetas eran superiores.

Por lo tanto, estoy interesado en la comparación de esas tres (y tal vez más) bibliotecas.

Tener
fuente
3
A partir de hoy, el lenspaquete tiene la funcionalidad y la documentación más ricas, por lo que si no le importa su complejidad y dependencias, es el camino a seguir.
modular

Respuestas:

200

Hay al menos 4 bibliotecas que sé 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 captador y un definidor

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

sujeto a tres leyes:

Primero, que si pones algo, puedes recuperarlo

get l (put l b a) = b 

Segundo, obtener y luego configurar no cambia la respuesta

put l (get l a) a = a

Y tercero, poner dos veces es lo mismo que poner una vez, o más bien, que gana el segundo puesto.

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

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

Muchas de estas bibliotecas también proporcionan un montón de combinadores adicionales en la parte superior y, por lo general, alguna forma de maquinaria de plantilla haskell 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 quizás el más fácil de razonar sobre las bibliotecas de lentes, ya que a :-> bse puede traducir directamente al tipo anterior. Proporciona una instancia de Categoría para la (:->)cual es útil ya que le permite componer lentes. También proporciona un Pointtipo sin ley que generaliza la noción de una lente utilizada aquí, y algunas tuberías para tratar los isomorfismos.

Un obstáculo para la adopción fclabelses que el paquete principal incluye la plomería template-haskell, por lo que el paquete no es Haskell 98, y también requiere la TypeOperatorsextensión (bastante no controvertida) .

acceso a datos

[Editar: data-accessorya no usa esta representación, pero se ha movido a una forma similar a la de data-lens. Sin embargo, mantengo este comentario.]

el acceso a datos 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 un poco en mi boca.

El tipo Tque utiliza para representar una lente se define internamente como

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

En consecuencia, para getel 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 accesos para usted en un paquete separado ' data-accessor-template '.

Tiene el beneficio de un conjunto de paquetes decentemente grandes que ya lo emplean, siendo Haskell 98 y proporcionando la Categoryinstancia más importante , por lo que si no prestas atención a cómo se hace la salchicha, este paquete es en realidad una opción bastante razonable .

lentes

A continuación, está el paquete de lentes , que observa que una lente puede proporcionar un homomorfismo de mónada de estado entre dos mónadas de estado, definiendo 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, prefiero este enfoque, ya que innecesariamente te saca de Haskell 98 (si quieres que un tipo proporcione a tus lentes en abstracto) y te priva de la Categoryinstancia de lentes, lo que te permitiría componerlos con .. La implementación también requiere clases de tipo multiparamétricas.

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

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

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

lente de datos

[Editar: a partir de 1.8.0, estos han pasado del paquete de transformadores comonad a lentes de datos]

Mi data-lenspaquete proporciona lentes en términos de la tienda Comonad.

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))

Puede 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 volver a poner un nuevo valor. 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 fclabelsdefinición, especialmente cuando los accesores 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 comienzo de esta respuesta son precisamente aquellas lentes para las cuales la función envuelta es un 'comonad coalgebra' para la tienda comonad . Esto transforma 3 leyes peludas para una lente len 2 equivalentes sin puntos:

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

Este enfoque se observó y describió por primera vez en Russell O'Connor's Functores Lenscomo Applicativees Biplate: Presentación de Multiplate y se escribió en un blog basado en una preimpresión de Jeremy Gibbons.

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

Por lo tanto, las lentes en data-lensforma a Category(a diferencia del lensespaquete), son Haskell 98 (a diferencia de fclabels/ lenses), son cuerdas (a diferencia del extremo posterior de data-accessor) y proporcionan una implementación un poco más eficiente, data-lens-fdproporciona 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 }

tal que los miembros válidos satisfagan hither . yon = id, yyon . 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 las lentes, y podemos usarlos como una herramienta de razonamiento para explicar otras lentes.

Lentes van Laarhoven

Podemos modelar lentes de manera que puedan componerse (.)e id, incluso sin una Categoryinstancia, 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 puede validar por sí mismo que la composición de la función es la composición de la lente.

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

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 polimorfismo de rango 2, pero no es necesario usar esa firma directamente al definir lentes.

El Lensque definí anteriormente para _2es en realidad un 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 que incluyen captadores, colocadores, pliegues y recorridos. Está disponible en hackage como el lenspaquete.

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

Como no es necesario utilizar el paquete para definir nuevas lentes, me quita mucha presión de mis preocupaciones anteriores sobre mantener la biblioteca Haskell 98.

Edward KMETT
fuente
28
Me gustan los fclabels por su enfoque optimista:->
Tener
3
Los artículos Inessential Guide to data-accessor y Inessential Guide to fclabels pueden ser notables
hvr
10
¿Es importante ser compatible con Haskell 1998? ¿Porque facilita el desarrollo del compilador? ¿Y no deberíamos pasar a hablar de Haskell 2010 en su lugar?
yairchu
55
¡Oh no! Fui el autor original de data-accessor, y luego se lo pasé a Henning y dejé de prestarle atención. La a -> r -> (a,r)representación también me hace sentir incómodo, y mi implementación original fue igual que su Lenstipo. Heeennnninngg !!
luqui
55
Yairchu: es principalmente para que su biblioteca tenga la oportunidad de trabajar con un compilador que no sea ghc. Nadie más tiene la plantilla Haskell. 2010 no agrega nada relevante aquí.
Edward KMETT el