2012-05-28 20 views
33

Parece que no encuentro ninguna explicación sobre el uso de lentes en ejemplos prácticos. Este breve párrafo de la página de Hackage es el más cercano que he encontrado:¿Para qué sirven las lentes /?

Este módulo proporciona una forma cómoda de acceder y actualizar los elementos de una estructura. Es muy similar a Data.Accessors, pero es un poco más genérico y tiene menos dependencias. Particularmente me gusta cuán limpiamente maneja las estructuras anidadas en las mónadas estatales.

Entonces, ¿para qué se utilizan? ¿Qué beneficios y desventajas tienen sobre otros métodos? ¿Por qué son necesarios?

+2

Puede disfrutar viendo las [Lentes: un imperativo funcional] de Edward Kmett (http://www.youtube.com/watch?v=efv0SQNde5Q) hablar. Se presenta en Scala, pero la traducción a la utilidad de las lentes en Haskell debe ser clara. –

Respuesta

45

Ofrecen una abstracción clara sobre las actualizaciones de datos, y nunca son realmente "necesarias". Simplemente te dejan razonar sobre un problema de una manera diferente.

En algunos lenguajes de programación imperativos/"orientados a objetos" como C, tiene el concepto familiar de una colección de valores (llamémoslos "estructuras") y formas de etiquetar cada valor en la colección (las etiquetas son típicamente llamado "campos").Esto conduce a una definición como esta:

typedef struct { /* defining a new struct type */ 
    float x; /* field */ 
    float y; /* field */ 
} Vec2; 

typedef struct { 
    Vec2 col1; /* nested structs */ 
    Vec2 col2; 
} Mat2; 

continuación, puede crear valores de este tipo que acaba de definir de este modo:

Vec2 vec = { 2.0f, 3.0f }; 
/* Reading the components of vec */ 
float foo = vec.x; 
/* Writing to the components of vec */ 
vec.y = foo; 

Mat2 mat = { vec, vec }; 
/* Changing a nested field in the matrix */ 
mat.col2.x = 4.0f; 

mismo modo en Haskell, tenemos tipos de datos:

data Vec2 = 
    Vec2 
    { vecX :: Float 
    , vecY :: Float 
    } 

data Mat2 = 
    Mat2 
    { matCol1 :: Vec2 
    , matCol2 :: Vec2 
    } 

Este tipo de datos se usa así:

let vec = Vec2 2 3 
    -- Reading the components of vec 
    foo = vecX vec 
    -- Creating a new vector with some component changed. 
    vec2 = vec { vecY = foo } 

    mat = Mat2 vec2 vec2 

Sin embargo, en Haskell, no hay una manera fácil de cambiar los campos anidados en una estructura de datos. Esto se debe a que necesita volver a crear todos los objetos de envoltura alrededor del valor que está cambiando, porque los valores de Haskell son inmutables. Si usted tiene una matriz como la anterior en Haskell, y desea cambiar la celda superior derecha de la matriz, que tener que escribir esto:

mat2 = mat { matCol2 = (matCol2 mat) { vecX = 4 } } 

Funciona, pero parece torpe. Entonces, lo que se le ocurrió a alguien es básicamente esto: si agrupa dos cosas juntas: el "captador" de un valor (como vecX y matCol2 anterior) con una función correspondiente que, dada la estructura de datos a la que pertenece el captador, crea una nueva estructura de datos con ese valor cambiado, puedes hacer muchas cosas ordenadas. Por ejemplo:

data Data = Data { member :: Int } 

-- The "getter" of the member variable 
getMember :: Data -> Int 
getMember d = member d 

-- The "setter" or more accurately "updater" of the member variable 
setMember :: Data -> Int -> Data 
setMember d m = d { member = m } 

memberLens :: (Data -> Int, Data -> Int -> Data) 
memberLens = (getMember, setMember) 

Existen muchas formas de implementar lentes; para este texto, digamos que una lente es igual que el anterior:

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

es decir, es la combinación de un getter y un setter para algún tipo a que tiene un campo de tipo b, entonces memberLens arriba sería Lens Data Int. ¿Qué nos permite esto?

Bueno, primero vamos a hacer dos funciones simples que extraen los captadores y definidores de una lente:

getL :: Lens a b -> a -> b 
getL (getter, setter) = getter 

setL :: Lens a b -> a -> b -> a 
setL (getter, setter) = setter 

Ahora, podemos empezar abstracción sobre la materia. Vayamos a la situación anterior de nuevo, que queremos modificar un valor "de dos pisos de profundidad". Añadimos una estructura de datos con otra lente:

data Foo = Foo { subData :: Data } 

subDataLens :: Lens Foo Data 
subDataLens = (subData, \ f s -> f { subData = s }) -- short lens definition 

Ahora, vamos a añadir una función que compone dos lentes:

(#) :: Lens a b -> Lens b c -> Lens a c 
(#) (getter1, setter1) (getter2, setter2) = 
    (getter2 . getter1, combinedSetter) 
    where 
     combinedSetter a x = 
     let oldInner = getter1 a 
      newInner = setter2 oldInner x 
     in setter1 a newInner 

El código es una especie de forma rápida escrito, pero creo que está claro que lo que hace : los getters son simplemente compuestos; obtienes el valor interno de los datos, y luego lees su campo. El colocador, cuando se supone que altera algún valor a con el nuevo valor de campo interno x, primero recupera la estructura de datos interna anterior, establece su campo interno y luego actualiza la estructura de datos externa con la nueva estructura de datos interna.

Ahora, vamos a hacer una función que simplemente incrementa el valor de una lente:

increment :: Lens a Int -> a -> a 
increment l a = setL l a (getL l a + 1) 

Si tenemos este código, se pone de manifiesto lo que hace:

d = Data 3 
print $ increment memberLens d -- Prints "Data 4", the inner field is updated. 

Ahora, debido a que puede componer lentes, también podemos hacer esto:

f = Foo (Data 5) 
print $ increment (subDataLens#memberLens) f 
-- Prints "Foo (Data 6)", the innermost field is updated. 

Lo que todos los paquetes de lentes hacen es esencialmente w rap este concepto de lentes, la agrupación de un "setter" y un "getter", en un paquete ordenado que los hace fáciles de usar. En una implementación lente en particular, uno sería capaz de escribir:

with (Foo (Data 5)) $ do 
    subDataLens . memberLens $= 7 

Así, se obtiene muy cerca de la versión C del código; se vuelve muy fácil modificar los valores anidados en un árbol de estructuras de datos.

Las lentes no son más que esto: una manera fácil de modificar partes de algunos datos. Debido a que cada vez es más fácil razonar sobre ciertos conceptos debido a ellos, ven un amplio uso en situaciones en las que tiene grandes conjuntos de estructuras de datos que tienen que interactuar entre sí de varias maneras.

Para conocer los pros y los contras de las lentes, consulte a recent question here on SO.

+2

Un punto importante que le falta a su respuesta es que los lentes son * de primera clase *, por lo que puede construir otras abstracciones de ellos. La sintaxis del registro incorporado falla en ese sentido. – jberryman

+2

Además, escribí una publicación de blog sobre lentes que podrían ser útiles para el OP: http://www.haskellforall.com/2012/01/haskell-for-mainstream-programmers_28.html –

12

Las lentes proporcionan formas convenientes de editar estructuras de datos, de una manera uniforme y compositiva.

Muchos programas se construyen alrededor de las siguientes operaciones:

  • visualización de un componente de un (posiblemente anidada) estructura de datos
  • campos de actualización de (posiblemente anidados) estructuras de datos

lentes proporcionan soporte de idiomas para ver y editar estructuras de manera que garantice que sus ediciones sean consistentes; que las ediciones se pueden componer fácilmente; y que el mismo código se puede usar para ver partes de una estructura, como para actualizar las partes de la estructura.

Las lentes facilitan la escritura de programas desde las vistas hacia las estructuras; y desde las estructuras a las vistas (y editores) para esas estructuras. Ellos limpian gran parte del desorden de los usuarios y instaladores de registros.

Pierce et al. lentes popularizadas, p. en su documento Quotient Lenses paper, y las implementaciones para Haskell son ahora ampliamente utilizadas (por ejemplo, fclabels y acceso a datos).

Para los casos de uso concretos, considere:

  • interfaces gráficas de usuario, donde un usuario está editando la información de una manera estructurada
  • analizadores e impresoras bonitas
  • compiladores
  • sincronización actualización de estructuras de datos
  • bases de datos y esquemas

y muchas otras situaciones en las que tiene un modelo de estructura de datos del mundo y una vista editable de esos datos.

6

Como nota adicional, a menudo se pasa por alto que las lentes implementan una noción muy genérica de "acceso de campo y actualización". Las lentes se pueden escribir para todo tipo de cosas, incluidos los objetos similares a funciones. Se requiere un poco de pensamiento abstracto a apreciar esto, así que voy a mostrar un ejemplo de la potencia de las lentes:

at :: (Eq a) => a -> Lens (a -> b) b 

Usando at en realidad se puede acceder y manipular las funciones con varios argumentos en función de los argumentos anteriores. Solo tenga en cuenta que Lens es una categoría. Esta es una expresión muy útil para ajustar localmente funciones u otras cosas.

También se puede acceder a los datos de propiedades o representaciones alternativas:

polar :: (Floating a, RealFloat a) => Lens (Complex a) (a, a) 
mag :: (RealFloat a) => Lens (Complex a) a 

Usted puede ir escribiendo lentes para acceder a las bandas individuales de una señal transformada de Fourier y mucho más allá.

Cuestiones relacionadas