Lenses can be a powerful component of a functional programmer’s toolbox. This post is not about their power or how you can use them. There are plenty of decent tutorials for that, so if you want to know what the lens fuss is about, I encourage you to check them out.

Instead, I will try to explain the magic behind how lenses work. After scrutinizing the types and code for a while, I finally had my burrito moment, so hopefully this explanation helps someone else out. I’ll be explaining the implementation behind the `purescript-lens`

library, which is modeled after the Haskell `lens`

library. Other implementations might be out there, too, but this is the one I’m focusing on. If you’ve ever looked at this implementation and wondered “How the hell do you get a getter and setter out of that?!”, then this post is meant for you.

The gist: a lens is a function that executes a series of steps to get an intermediate result, performs an operation on the intermediate result, then follows the series of steps backwards to build up a new result. The manner in which the new result is built up is controlled by the functor used by the operation.

# Lens type explained

A lens is a function with the following type:

```
type Lens s t a b = forall f. (Functor f) => (a -> f b) -> s -> f t
```

Looking at this type, it’s a little difficult to see what is going on. Essentially, a lens uses an operation `a -> f b`

to convert data of type `s`

into data of type `f t`

. I call `a -> f b`

the *focus conversion* and `s -> f t`

the *lens conversion*.

Let’s start with the types `s`

and `a`

. `s`

is the parent object that is being inspected, and `a`

is the part of the parent the lens puts focus on. For example:

```
type Foo =
{ foo :: {bar :: Number}
, baz :: Boolean }
bazL :: Lens Foo Foo Boolean Boolean
```

The `bazL`

type definition using `Foo Foo`

instead of two different types simply means that the lens conversion does not transform inputs of type `Foo`

into some unrelated type, excepting the functor `f`

. Indeed, a `Foo`

input will become an `f Foo`

output of the lens conversion. Similarly, `Boolean Boolean`

indicates that the focus conversion does not change the type of of the focus (again, excepting the functor `f`

) – `Boolean`

becomes `f Boolean`

.

This is the common pattern used to define a lens that works as both a getter and a setter. It’s so common that most lens libraries define a type synonym for it `type LensP s a = Lens s s a a`

.

# More than just a getter and setter

Getters and setters are common use cases for lenses, but really lenses perform a complete transformation across data. This is why there are four type variables instead of just two. Let’s look at a more general type for the `bazL`

lens that allows it to operate on any record with the `baz`

field and transform any `baz`

type `a`

into `f b`

.

```
bazL :: forall a b r. Lens {baz :: a | r} {baz :: b | r} a b
bazL = lens (\o -> o.baz) (\o x -> o{baz = x})
```

This type indicates that the lens conversion is from a record with a `baz`

field of any type to a record of a `baz`

field with a possibly different type (wrapped in a functor). The focus conversion goes from a type `a`

to a type `f b`

. The `lens`

function above is a utility function that builds a lens that can be used as both a getter and a setter from a getter function and a setter function.

Is it possible for a lens to have type `Lens a a b c`

, meaning the focus conversion performs a type change but the lens conversion does not? I’m actually not sure, but I think yes. Such a lens could be composed with `Lens b c d d`

to get `Lens a a d d`

, which is the familiar type we already know. In fact, lens composition is just normal function composition because lenses are just regular functions!

# Behind the curtain

What’s going on behind the scenes when we compose lenses? Let’s look at our `Foo`

type again, along with a couple new lenses and the `lens`

help function.

```
type Foo =
{ foo :: {bar :: Number}
, baz :: Boolean }
fooL :: forall a b r. Lens {foo :: a | r} {foo :: b | r} a b
fooL = lens (\o -> o.foo) (\o x -> o{foo = x})
barL :: forall a b r. Lens {bar :: a | r} {bar :: b | r} a b
barL = lens (\o -> o.bar) (\o x -> o{bar = x})
lens :: forall s t a b. (s -> a) -> (s -> b -> t) -> Lens s t a b
lens s2a s2b2t a2fb s = s2b2t s <$> a2fb (s2a s)
```

When we use `lens`

to build `fooL`

from a getter and a setter, here’s what the result looks like, with types specialized for our `Foo`

data type instead of the more general extensible record types.

```
fooL :: ({bar :: Number} -> f {bar :: Number}) -> Foo -> f Foo
fooL focusCon parent =
(\o x -> o{foo = x}) parent <$> focusCon ((\o -> o.foo) parent)
-- which we can simplify to
fooL focusCon parent = \x -> parent{foo = x} <$> focusCon (parent.foo)
```

Our resulting lens applies the `focusCon`

function to `parent.foo`

, then incorporates the result back into the `parent`

structure using `<$>`

applied for the functor.

The resulting `fooL`

lens is just function, so we can compose it with other functions.

```
(..) = (<<<) -- normal function composition
(..) :: (b -> c) -> (a -> b) -> a -> c
(..) f g a = f (g a)
-- or alternatively
(..) f g = \a -> f (g a)
```

When we compose lenses `fooL..barL`

, we have:

```
fooL..barL =
\fc -> fooL (barL fc) =
\fc parent -> \x -> parent{foo = x} <$> (barL fc) (parent.foo) =
\fc parent -> \x -> parent{foo = x} <$>
(\parent2 -> \y -> parent2{bar = y} <$> fc parent2.bar)
parent.foo
-- and now we can reason the following
fc :: Number -> f Number -- fc is the focus conversion function
parent :: Foo
fooL..barL :: (Number -> f Number) -> Foo -> f Foo
fooL..barL :: Lens Foo Foo Number Number
```

That’s amazing! Lenses compose using normal function composition.

Take a second look at the function pipeline above and assume that the focus conversion `fc`

has been defined already. First, `parent.foo`

is piped in as `parent2`

. `fc`

is applied to `parent2.bar`

to give a result of type `f Number`

. The result is mapped over using `<$>`

to apply `\y -> parent2{bar = y}`

to get a result of `f {bar :: Number}`

. This is then mapped over again using `<$>`

to get a result of type `f Foo`

, which is the output of the lens conversion.

This is the essence of a lens; the composed lens looks deep within the object, focuses on a value of a certain type, does something with that type, then recurses back up the object, “combining” the results together using `<$>`

.

# That functor magic

One thing we haven’t looked at yet is the type variable `f`

, the functor used by the focus conversion function. Not only is the functor used in the focus conversion function, but it is also used every time the lens recurses up one layer of the object and combines the results together. The functor, therefore, controls *how* the new result is built out intermediate results. The *choice of functor* is what determines the behavior of the lens.

Let’s take our `fooL..barL`

example again.

```
fooL..barL =
\fc parent -> \x -> parent{foo = x} <$>
(\parent2 -> \y -> parent2{bar = y} <$> fc parent2.bar)
parent.foo
```

Let’s pick the `Const`

functor for `f`

, which has the following implementation `(<$>) fx (Const x) = Const x`

. In other words, no matter what function `fx`

is applied with `<$>`

, a `Const x`

value will never change. Let’s see what happens when we simplify our lens using `Const`

as the focus function.

```
fooL..barL Const =
\parent -> \x -> parent{foo = x} <$>
(\parent2 -> \y -> parent2{bar = y} <$> Const parent2.bar)
parent.foo =
\parent -> \x -> parent{foo = x} <$>
(\parent2 -> \y -> Const parent2.bar)
parent.foo =
\parent -> \x -> parent{foo = x} <$> Const parent.foo.bar =
\parent -> Const parent.foo.bar
```

When using the `Const`

functor, the lens is a getter! Can you guess what happens when the `Identity`

functor is used? For reference, `(<$>) fx (Identity x) = Identity (fx x)`

, which is just normal function application after unwrapping the `Identity`

constructor.

```
fooL..barL Identity =
\parent -> \x -> parent{foo = x} <$>
(\parent2 -> \y -> parent2{bar = y} <$> Identity parent2.bar)
parent.foo =
\parent -> \x -> parent{foo = x} <$>
(\parent2 -> Identity (\y -> parent2{bar = y} $ parent2.bar))
parent.foo =
\parent -> \x -> parent{foo = x} <$>
(\parent2 -> Identity (parent2{bar = parent2.bar}))
parent.foo =
\parent -> \x -> parent{foo = x} <$>
Identity (parent.foo{bar = parent.foo.bar}) =
\parent -> Identity (\x -> parent{foo = x} $
parent.foo{bar = parent.foo.bar})) =
\parent -> Identity $ parent{foo = parent.foo{bar = parent.foo.bar}} =
\parent -> Identity parent
```

Under the `Identity`

functor, the lens take a `Foo`

and returns an equal `Foo`

by building one from the ground up.

So what if, instead of using just `Identity`

as the focus conversion, we first run the focus through a function? I.E. the focus function is `\val -> Identity (fun val)`

for some `fun`

? I’ll spare the derivation and jump to the end: we get back the parent we put in except the focus will have the value returned from `fun`

. Set `fun = const newVal`

and you have a setter!

As a little aside, I can say personally that understanding how drastically the functor changes the behavior of the lens was a major hurdle for me. So many lens tutorials say that lenses “combine a getter and a setter” or something along those lines, and then I’d check the implementation and see no obvious getter or setter and wonder, “What kind of devilry is this?!”

If the functor controls the rebuilding done at the end of the lens conversion, then how is the functor selected? They are built into the implementations of `view`

, `set`

, and any of the numerous operators that can be used to run a lens conversion.

# The gist, again

Lenses are tools for ripping something into constituent pieces, doing something with the final piece, then recombining all the pieces together into something new. At least that’s how the person behind this keyboard thinks about them now. There’s probably an even *more* general interpretation that describes all kinds of wonky things lenses can do beyond the usual getter, setter, identity, and similar behaviors, but this covers the most common uses pretty well. I’ll save that more general discover for another day after my cognitive burrito appetite returns.