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.
El nivel superior, el
Fold
y elSetter
, son las ópticas básicas de lalens
. Como puedes imaginar, unSetter
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 unSetter
.Un
Getter
es un tipo especial deFold
que le permite obtener valores de las estructuras de datos. (UnFold
sí mismo solo le permite combinar de alguna manera los valores con un nuevo valor, no eliminarlos como están). Que unGetter
es un tipo especial deFold
se marca en el gráfico con la flecha que apunta deGetter
aFold
.Si combina un
Fold
y unSetter
(es decir, si combina una forma de hacer un bucle sobre un conjunto de valores y una forma de establecer valores individuales), obtendrá unTraversal
. UnaTraversal
es una óptica que se enfoca en múltiples elementos y le permite establecer o modificar todos ellos.Más abajo en la tabla, si combinas un
Getter
con unTraversal
obtienes unLens
. 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 unTraversal
como unSetter
más poderoso.No voy a pretender que sé mucho sobre las relaciones entre
Review
,Prism
,Iso
yEquality
. La forma en que lo entiendo,- Una
Review
es una envoltura básica alrededor de una funciónb -> t
donde se supone queb
es un campo dentro de la estructurat
. Así que unaReview
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 unGetter
, y útil para construir prismas. - Un
Prism
permite obtener y establecer valores dentro de tipos de ramificación. Es paraEither
lo que es unaLens
para las tuplas. No puedes obtener un valor dentro de unaEither
con solo una lente, ya que explotará si golpea la ramaLeft
. - Una
Iso
es una combinación muy poderosa deLens
yPrism
, que te permite mirar libremente "en ambos sentidos" a través de la óptica. Mientras que unaLens
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.
- Una
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.