Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are lenses used/useful for?

I can't seem to find any explanation of what lenses are used for in practical examples. This short paragraph from the Hackage page is the closest I've found:

This modules provides a convienient way to access and update the elements of a structure. It is very similar to Data.Accessors, but a bit more generic and has fewer dependencies. I particularly like how cleanly it handles nested structures in state monads.

So, what are they used for? What benefits and disadvantages do they have over other methods? Why are they needed?

like image 431
Pubby Avatar asked May 28 '12 17:05

Pubby


People also ask

What objects uses lens?

Cameras, telescopes, movie projectors, eye glasses, magnifying lenses, microscopes.


1 Answers

They offer a clean abstraction over data updates, and are never really "needed." They just let you reason about a problem in a different way.

In some imperative/"object-oriented" programming languages like C, you have the familiar concept of some collection of values (let's call them "structs") and ways to label each value in the collection (the labels are typically called "fields"). This leads to a definition like this:

typedef struct { /* defining a new struct type */   float x; /* field */   float y; /* field */ } Vec2;  typedef struct {   Vec2 col1; /* nested structs */   Vec2 col2; } Mat2; 

You can then create values of this newly defined type like so:

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; 

Similarly in Haskell, we have data types:

data Vec2 =   Vec2   { vecX :: Float   , vecY :: Float   }  data Mat2 =   Mat2   { matCol1 :: Vec2   , matCol2 :: Vec2   } 

This data type is then used like this:

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 

However, in Haskell, there's no easy way of changing nested fields in a data structure. This is because you need to re-create all of the wrapping objects around the value that you are changing, because Haskell values are immutable. If you have a matrix like the above in Haskell, and want to change the upper right cell in the matrix, you have to write this:

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

It works, but it looks clumsy. So, what someone came up with, is basically this: If you group two things together: the "getter" of a value (like vecX and matCol2 above) with a corresponding function that, given the data structure that the getter belongs to, can create a new data structure with that value changed, you are able to do a lot of neat stuff. For example:

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) 

There are many ways of implementing lenses; for this text, let's say that a lens is like the above:

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

I.e. it is the combination of a getter and a setter for some type a which has a field of type b, so memberLens above would be a Lens Data Int. What does this let us do?

Well, let's first make two simple functions that extract the getters and setters from a lens:

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

Now, we can start abstracting over stuff. Let's take the situation above again, that we want to modify a value "two stories deep." We add a data structure with another lens:

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

Now, let's add a function that composes two lenses:

(#) :: 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 

The code is kind of quickly written, but I think it's clear what it does: the getters are simply composed; you get the inner data value, and then you read its field. The setter, when it is supposed to alter some value a with the new inner field value of x, first retrieves the old inner data structure, sets its inner field, and then updates the outer data structure with the new inner data structure.

Now, let's make a function that simply increments the value of a lens:

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

If we have this code, it becomes clear what it does:

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

Now, because we can compose lenses, we can also do this:

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

What all of the lens packages do is essentially to wrap this concept of lenses - the grouping of a "setter" and a "getter," into a neat package that makes them easy to use. In a particular lens implementation, one would be able to write:

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

So, you get very close to the C version of the code; it becomes very easy to modify nested values in a tree of data structures.

Lenses are nothing more than this: an easy way of modifying parts of some data. Because it becomes so much easier to reason about certain concepts because of them, they see a wide use in situations where you have huge sets of data structures that have to interact with one another in various ways.

For the pros and cons of lenses, see a recent question here on SO.

like image 180
dflemstr Avatar answered Sep 19 '22 12:09

dflemstr