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 :-> b
se 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 Point
tipo 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 fclabels
es que el paquete principal incluye la plomería template-haskell, por lo que el paquete no es Haskell 98, y también requiere la TypeOperators
extensión (bastante no controvertida) .
acceso a datos
[Editar: data-accessor
ya 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 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 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 Category
instancia 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 Category
instancia 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 Category
instancias, 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-lens
paquete 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 fclabels
definició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 l
en 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 Functor
es Lens
como Applicative
es 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-lens
forma a Category
(a diferencia del lenses
paquete), 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-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 }
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 Category
instancia, 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 Lens
que definí anteriormente para _2
es 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 lens
paquete.
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.
lens
paquete 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.