haskell lens lenses

haskell - ¿Podría alguien explicar el diagrama sobre la biblioteca `lens`?



lenses (2)

El gráfico es un flujo de lo que puede hacer con una relación débil entre dos tipos, a lo que puede hacer con una relación fuerte.

En el más débil, puede plegar los "elementos" de tipo a dentro de un tipo s , o puede configurar la a a a b dentro de una s , cambiándola a t (donde, por supuesto, a y b podrían ser lo mismo, como con s y t ). En el fondo, tienes igualdad; Un paso más arriba, tienes isomorfismo; Luego lentes y prismas, etc.

Visto de otra manera, fluye desde la mayoría aplicable hasta la menos aplicable, debido a los requisitos de la relación: hay muchos tipos que se pueden plegar en términos de algunos conceptualmente "dentro" de ellos; mientras que muchas menos cosas serán iguales o isomorfas a a .

Si navega a través de la entrada de Lens en hackage, el repositorio de Lens Github, o incluso en Google sobre Lens, encontrará muchas referencias parciales como tutoriales / videos introductorios, ejemplos, resúmenes, etc. Como ya conozco la mayoría de los conceptos básicos, busco una referencia más completa que me ayude a obtener más conocimientos sobre las funciones avanzadas. En otras palabras, todavía no tengo idea de lo que this significa y no pude encontrar un recurso lo suficientemente completo como para explicar este gráfico en su totalidad. Ideas?


Los eglefinos son el mejor recurso en profundidad. Contienen todo, pero pueden ser un poco difíciles de navegar al principio. Solo navega por los diferentes módulos y toma notas mentales sobre qué es dónde, y pronto comenzarás a encontrar tu camino. El diagrama al que se vincula también es un muy buen mapa de los módulos.

Sin embargo, como dice que no entiende el gráfico, voy a suponer que no desea una referencia avanzada o completa. El diagrama es en realidad una descripción muy básica y de alto nivel de las partes del paquete de lens . Si no entiendes el diagrama, probablemente deberías esperar un poco con las cosas avanzadas.

Al leer esto, tenga en cuenta que aunque el paquete de lens comenzó como un paquete de lentes, ahora hay muchos tipos de ópticas en el paquete que obedecen a diferentes leyes y se usan para diferentes cosas. "Óptica" es el nombre general de las cosas similares a lentes que utiliza para golpear estructuras de datos.

De todos modos, así es como leo el diagrama.

Arreglo de las cajas

Por ahora, solo mire en la parte superior de las casillas donde se escribe el nombre de la óptica.

  1. El nivel superior, el Fold y el Setter , son las ópticas básicas de la lens . Como puedes imaginar, un Setter es una óptica que establece el valor de algún campo. Voy a comentar un montón de ejemplos aquí, ya que dice que conoce la mayoría de los conceptos básicos, pero esencialmente, cuando lo hace.

    λ> ellen & age .~ 35 Person { _name = "Ellen", _age = 35 }

    entonces has usado la age como un Setter .

  2. Un Getter es un tipo especial de Fold que le permite obtener valores de las estructuras de datos. (Un Fold sí mismo solo le permite combinar de alguna manera los valores con un nuevo valor, no eliminarlos como están). Que un Getter es un tipo especial de Fold se marca en el gráfico con la flecha que apunta de Getter a Fold .

  3. Si combina un Fold y un Setter (es decir, si combina una forma de hacer un bucle sobre un conjunto de valores y una forma de establecer valores individuales), obtendrá un Traversal . Una Traversal es una óptica que se enfoca en múltiples elementos y le permite establecer o modificar todos ellos.

  4. Más abajo en la tabla, si combinas un Getter con un Traversal obtienes un Lens . Esto debería ser familiar para usted, ya que a menudo se habla de lentes como "una combinación de un captador y un colocador", y puede pensar en un Traversal como un Setter más poderoso.

  5. No voy a pretender que sé mucho sobre las relaciones entre Review , Prism , Iso y Equality . La forma en que lo entiendo,

    • Una Review es una envoltura básica alrededor de una función b -> t donde se supone que b es un campo dentro de la estructura t . Así que una Review toma un solo valor de campo y luego construye una estructura completa a su alrededor. Esto puede parecer una tontería, pero en realidad es lo contrario de un Getter , y útil para construir prismas.
    • Un Prism permite obtener y establecer valores dentro de tipos de ramificación. Es para Either lo que es una Lens para las tuplas. No puedes obtener un valor dentro de una Either con solo una lente, ya que explotará si golpea la rama Left .
    • Una Iso es una combinación muy poderosa de Lens y Prism , que te permite mirar libremente "en ambos sentidos" a través de la óptica. Mientras que una Lens permite mirar desde un nivel alto a una parte precisa de una estructura de datos, Iso también le permite mirar desde esa parte precisa de la estructura de datos hasta el nivel alto.
    • Una Equality es ... no tengo idea, lo siento.

Interludio: Tipo de firmas

Una firma de tipo como la Lens stab puede dar miedo al principio, así que intentaré cubrir rápidamente lo que significa. Si miras a Getter , verás que su tipo de firma es

Getter s a

Si pensamos en lo que es conceptualmente un captador, esto puede parecer familiar. ¿Qué es un captador? Es una función desde una estructura de datos hasta un valor único que está dentro de esa estructura. Por ejemplo, una función Person -> Age es un captador que obtiene la edad de un objeto Person . El correspondiente Getter simplemente tiene la firma.

Getter Person Age

Es realmente tan simple. A Getter sa es una óptica que puede obtener la a desde el interior de la s .

La firma de la Lens'' simplificada debería ser menos aterradora ahora. Si tuvieras que adivinar, ¿qué es un

Lens'' Person Age

? ¡Fácil! Es un Lens que puede obtener y establecer (¿recuerda cómo los lentes son combinaciones de captadores y definidores?) El campo Age dentro de un valor de Person . Entonces, la misma lógica que aplicó a Getter sa puede aplicar a Lens'' sa .

Pero ¿qué pasa con la stab ? Bueno, piensa en este tipo:

data Person a = { _idField :: a, address :: String }

una persona es identificable por algún valor de identificación y tiene una edad. Digamos que estás identificado por un nombre

carolyn :: Person String carolyn = Person "Carolyn" "North Street 12"

¿Qué toIdNumber :: String -> Int si tiene una función para toIdNumber :: String -> Int que hace un número de ID a partir de una cadena? Es posible que desee hacer esto:

λ> carolyn & idField %~ toIdNumber Person 57123 "North Street 12"

pero no puede hacerlo si la idField :: Lens'' (Person String) String porque esa lente solo puede tratar con String s. No sabe lo que significa convertir una cadena en un entero y pegarla en el mismo lugar. Por eso tenemos la Lens stab .

La forma en que leo esa firma es "si me das una función a -> b , te daré una función s -> t ", donde b refieren al objetivo de la lente, y s y t refieren a Las estructuras de datos que contienen. Ejemplo concreto: si tuviéramos

idField :: Lens (Person String) (Person Int) String Int

Podríamos hacer la transformación de arriba. Esa lente sabe cómo tomar a una Person con un campo de identificación de cadena y convertirla en una persona con un campo de identificación de identificación.

Así que, esencialmente, una Lens stab se puede leer como una "función" (a -> b) -> (s -> t) . "Dame una transformación (a -> b) para hacer en mi objetivo, y una estructura de datos que contenga ese objetivo, y te devolveré una estructura de datos donde se haya aplicado esa transformación".

Por supuesto, en realidad no es esa función, es más poderosa que eso, pero puedes convertirla en esa función si la asignas a la parte de Setter .

Contenidos de las cajas

Los contenidos de las cajas son simplemente las operaciones más comunes y / o centrales de cada tipo de óptica. Sus tipos dicen mucho sobre lo que hacen, y voy a elegir algunos ejemplos para mostrar lo que quiero decir.

En general, los elementos superiores son cómo construye ese tipo de óptica. Por ejemplo, el elemento superior del cuadro de Getter es la función to , que construye un Getter partir de cualquier función s -> a . Traversal un Traversal gratis de un Traversable si usas la función de traverse .

Entonces debajo de eso están las operaciones comunes. Por ejemplo, encontrará la view debajo de Getter , que es la forma en que usa un Getter para sacar algo de la estructura de datos. En Setter , encuentras over y te pones over alto.

(Observación interesante: la mayoría de las cajas parecen contener dos funciones duales: una forma de crear la óptica y una forma de usarlas. Lo interesante es que a menudo tienen casi la misma firma de tipo, pero volteadas en comparación con las demás. Ejemplo:

  • to :: (s -> a) -> Getter sa
  • view :: Getter sa -> (s -> a)

y

  • unto :: (b -> t) -> Review stab
  • review :: Review stab -> (b -> t)

Solo algo gracioso que noté ahora.)

Cómo uso el gráfico

Normalmente no estudio el gráfico tan de cerca como escribo esto. En su mayoría, solo miro a Setter , Getter , Traversal , Lens and Prism y dónde están en relación entre ellos, porque esas son las ópticas que más uso.

Cuando sé que necesito hacer algo, por lo general le doy un vistazo rápido al gráfico para ver qué cajas contienen operaciones similares a las que quiero hacer. (Por ejemplo, si tengo data Gendered a = Masculine a | Feminine a , entonces _Right es similar al hipotético _Feminine ). Cuando he determinado eso, me sumerjo en los Haddocks para esos módulos (en este ejemplo, Prism ). Buscando las operaciones que necesito.

Cómo aprendo sobre la óptica

Aprendo sobre la óptica y cómo usarlos de la misma manera que aprendí todo. Encuentro un problema real en el que la óptica es una buena solución y trato de resolverla. Lo intento y fallo y vuelvo a intentarlo y pido ayuda, lo intento, lo intento y lo intento. Eventualmente voy a tener éxito. Entonces intento algo ligeramente diferente.

Al usar las cosas de la manera en que estaban destinadas a ser utilizadas, recojo mucha experiencia útil sobre cómo funcionan y demás.

Bonus: Un error personal

Antes de comenzar a escribir esto, pensé que necesitabas Prism s para lidiar con los valores de Maybe , porque el tipo Maybe está ramificando, ¿no? ¡No exactamente! Al menos no más que el tipo de List , que no necesita Prism para tratar.

Debido a que el tipo Maybe es un contenedor de cero o uno de los elementos, en realidad solo necesita un Traversal para resolverlo. Cuando tienes dos ramas que pueden contener diferentes valores, empiezas a necesitar todo el poder del Prism . (Para construir un valor Maybe vez todavía necesitas la Review ).

Descubrí esto solo cuando comencé a leer el diagrama con mucho cuidado y explorando los módulos para descubrir las diferencias reales y formales entre las ópticas. Este es el inconveniente de usar mi método para aprender cosas. Mientras funcione, solo lo alabo. A veces eso conduce a formas indirectas de hacer las cosas.