Could someone explain the diagram about the `lens` library?
Asked Answered
S

2

18

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?

Sport answered 20/4, 2015 at 8:22 Comment(0)
S
35

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.

  1. The top level, the Fold and the Setter, are the basic optics of lens. As you can imagine, a Setter 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 a Setter.

  2. A Getter is a special kind of Fold that allows you to get values out of data structures. (A Fold itself only allows you to somehow combine values to a new value, not get them out as they are.) That a Getter is a special kind of Fold is marked in the graphic by the arrow pointing from Getter to Fold.

  3. If you combine a Fold and a Setter (i.e. you combine a way to loop over a bunch of values and a way to set single values) you get a Traversal. A Traversal is an optic that targets multiple elements and lets you set or modify all of them.

  4. Further down the chart, if you combine a Getter with a Traversal you get a Lens. 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 a Traversal as a more powerful Setter.

  5. I'm not going to pretend I know a whole lot about the relations between Review, Prism, Iso and Equality. The way I understand it,

    • A Review is a basic wrapper around a function b -> t where b is supposed to be a field inside the t structure. So a Review takes a single field value and then constructs a whole structure around it. This might seem silly, but it's actually the reverse of a Getter, and useful to build prisms.
    • A Prism allows you to get and set values within branching types. It's to Either what a Lens is to tuples. You can't get a value inside an Either with just a lens, because it will explode if it hits the Left branch.
    • An Iso is a very powerful combination of Lens and Prism, which allows you to freely look "both ways" through optics. While a Lens lets you look from a high level onto a pinpoint part of a data structure, an Iso 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.

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 Strings. 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 Prisms 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 Prisms 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.

Sport answered 20/4, 2015 at 8:22 Comment(2)
Just wanted to state I'm still digesting your answer and going through a lot of material, that's why I didn't comment yet, but I'm very grateful for it and it is helping me a lot. Thank you!Hobart
@Viclib Taking some time is to be expected. This is stuff it took me months to learn when being at it on and off. :)Sport
B
1

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.

Bestial answered 21/1, 2016 at 21:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.