If you browse through Lens entry on hackage, Lens Github's repo, or even google about Lens, you will find a lot of partial references such as introductory tutorials/videos, examples, overviews and so on. Since I already know most of the basics, I am looking for a more complete reference that would help me to get more knowledge about the advanced features. In other words, I still have no idea what this means and couldn't find a resource complete enough to explain this graphic as a whole. Ideas?
The Haddocks are the best in-depth resource. They contain everything, but they might be a bit hard to navigate at first. Just browse around the different modules and make mental notes on what's where, and you'll start to find your way around soon. The diagram you link to is also a very good map of the modules.
However, since you say you don't understand the graphic, I'm going to assume that you don't want an advanced or complete reference. The diagram is actually a very basic, high-level overview of the parts of the lens
package. If you don't understand the diagram, you should probably wait a bit with the advanced stuff.
When reading this, keep in mind that although the lens
package started out as a package of, well, lenses, there are now many kinds of optics in the package, which obey different laws and are used for different things. "Optic" is the general name for the lens-like things you use to poke at data structures.
Anyway, here's how I read the diagram.
The Boxes' Arrangement
For now, just look at the top of the boxes where the name of the optic is written.
The top level, the
Fold
and theSetter
, are the basic optics oflens
. As you can imagine, aSetter
is an optic that sets the value of some field. I'm going to elide a bunch of examples here since you say you know most of the basics, but essentially, when you doλ> ellen & age .~ 35 Person { _name = "Ellen", _age = 35 }
then you've used
age
as aSetter
.A
Getter
is a special kind ofFold
that allows you to get values out of data structures. (AFold
itself only allows you to somehow combine values to a new value, not get them out as they are.) That aGetter
is a special kind ofFold
is marked in the graphic by the arrow pointing fromGetter
toFold
.If you combine a
Fold
and aSetter
(i.e. you combine a way to loop over a bunch of values and a way to set single values) you get aTraversal
. ATraversal
is an optic that targets multiple elements and lets you set or modify all of them.Further down the chart, if you combine a
Getter
with aTraversal
you get aLens
. This should be familiar to you, as lenses are often talked about as "a combination of a getter and a setter", and you can think of aTraversal
as a more powerfulSetter
.I'm not going to pretend I know a whole lot about the relations between
Review
,Prism
,Iso
andEquality
. The way I understand it,- A
Review
is a basic wrapper around a functionb -> t
whereb
is supposed to be a field inside thet
structure. So aReview
takes a single field value and then constructs a whole structure around it. This might seem silly, but it's actually the reverse of aGetter
, and useful to build prisms. - A
Prism
allows you to get and set values within branching types. It's toEither
what aLens
is to tuples. You can't get a value inside anEither
with just a lens, because it will explode if it hits theLeft
branch. - An
Iso
is a very powerful combination ofLens
andPrism
, which allows you to freely look "both ways" through optics. While aLens
lets you look from a high level onto a pinpoint part of a data structure, anIso
lets you also look from that pinpoint part of the data structure up to the high level. - An
Equality
is ... I have no idea, sorry.
- A
Interlude: Type Signatures
A type signature like Lens s t a b
might be scary at first, so I'll try to quickly cover what it means. If you look at Getter
, you'll see its type signature is
Getter s a
If we think about what a getter conceptually is, this might seem familiar. What is a getter? It's a function from a data structure s
to a single value a
which is inside that structure. For example, a function Person -> Age
is a getter that gets the age from a Person
object. The corresponding Getter
simply has the signature
Getter Person Age
It's really that simple. A Getter s a
is an optic that can get the a
from inside the s
.
The simplified Lens'
signature should be less scary now. If you had to guess, what is a
Lens' Person Age
? Easy! It's a Lens
that can get and set (remember how lenses are combinations of getters and setters?) the Age
field inside a Person
value. So the same logic you applied to Getter s a
you can apply to Lens' s a
.
But what about the s t a b
part? Well, think about this type:
data Person a = { _idField :: a, address :: String }
a person is identifiable by some identification value and has an age. Let's say you are identified by a name
carolyn :: Person String
carolyn = Person "Carolyn" "North Street 12"
What if you have a function toIdNumber :: String -> Int
which makes an ID number from a string? You might want to do this:
λ> carolyn & idField %~ toIdNumber
Person 57123 "North Street 12"
but you can't if idField :: Lens' (Person String) String
because that lens can only deal with String
s. It doesn't know what it means to turn a string into an integer and stick that into the same spot. That's why we have Lens s t a b
.
The way I read that signature is "if you give me a function a -> b
, I will give you a function s -> t
", where a
and b
both refer to the target of the lens, and s
and t
refer to the containing data structures. Concrete example: if we had
idField :: Lens (Person String) (Person Int) String Int
we could do the transformation above. That lens knows how to take a Person
with a string id field and turn it into a person with an Int
id field.
So, essentially, a Lens s t a b
can be read as a "function" (a -> b) -> (s -> t)
. "Give me a transformation (a -> b)
to do on my target, and a data structure s
that contains that target, and I will return to you a data structure t
where that transformation has been applied."
Of course, it's not actually that function – it's more powerful than that – but you can turn it into that function by giving it to over
which is part of Setter
.
The Boxes' Contents
The contents of the boxes are simply the most common and/or core operations of each kind of optic. Their types say a lot about what they do, and I'm going to pick a few examples to show what I mean.
Generally, the topmost items are how you construct that kind of optic. For example, the topmost item in the Getter
box is the to
function, which constructs a Getter
from any function s -> a
. You get a Traversal
for free from a Traversable
if you use the traverse
function.
Then below that are the common operations. You'll for example find view
under Getter
, which is how you use a Getter
to get something out of a data structure. In Setter
, you find over
and set
high up.
(Interesting observation: most boxes seem to contain two dual functions: one way to create the optic, and one way to use them. What's interesting is that those often have almost the same type signature, but flipped compared to each other. Example:
to :: (s -> a) -> Getter s a
view :: Getter s a -> (s -> a)
and
unto :: (b -> t) -> Review s t a b
review :: Review s t a b -> (b -> t)
Just something funny I noticed now.)
How I Use the Graphic
I don't normally study the graphic as closely as I have writing this. Mostly, I just peek at Setter
, Getter
, Traversal
, Lens
and Prism
and where they are in relation to each other, because those are the optics I use most.
When I know I need to do something, I usually take a quick peek at the graphic to see which boxes contain operations similar to the ones I want to do. (For example, if I have data Gendered a = Masculine a | Feminine a
, then _Right
is similar to the hypothetical _Feminine
.) When I've determined that, I dive into the Haddocks for those modules (in this example, Prism
), searching for the operations I need.
How I Learn About Optics
I learn about optics and how to use them the same way I learn everything. I find a real problem where optics are a good solution, and I try to solve it. I try it and I fail and I try again and I ask for help and I try and I try and I try. Eventually I will succeed. Then I try something slightly different.
By actually using things the way they were meant to be used I collect a lot of useful experience on how they work and such.
Bonus: A Personal Misconception
Before I started writing this, I thought you needed Prism
s to deal with Maybe
values, because the Maybe
type is branching, no? Not quite! At least not any more than the List
type, which you don't need Prism
s to deal with.
Because the Maybe
type is a container of zero or one elements, you actually only need a Traversal
to deal with it. It's when you have two branches which can contain different values that you start needing the full power of the Prism
. (To construct a Maybe
value you still need the Review
though.)
I figured this out only when I started to read the diagram very carefully and exploring the modules to figure out the actual, formal differences between the optics. This is the drawback of using my method to learn things. As long as it works, I just wing it. Sometimes that leads to roundabout ways of doing things.
The graphic is a flow from what you can do with a weak relationship between two types, to what you can do with a strong relationship.
At the weakest, you can fold over "elements" of type a
within a type s
, or you can set the a
to a b
within an s
, changing it to a t
(where of course, a
and b
could be the same, as with s
and t
). At the very bottom, you have equality; one step up, you have isomorphism; then lenses and prisms, etc.
Viewed another way, it flows from most applicable down to least applicable, due to the requirements of the relationship: There are many types that can be folded in terms of some a
conceptually "within" them; whereas far fewer things will be equal or isomorphic to a
.
© 2022 - 2024 — McMap. All rights reserved.