An Introduction Into Lenses In JavaScript
Tags: functional, javascript
Published: 2021-05-12 20:00:35

---
title: An Introduction Into Lenses In JavaScript
published: true
tags: functional, JavaScript
---

# An Introduction Into Lenses In JavaScript

## Functional Getter/Setter


---

Originally published on _Nov 17, 2016_ and also available [here](https://medium.com/javascript-inside/an-introduction-into-lenses-in-javascript-e494948d1ea5).

---

### Introduction

If you know the ins and outs of lenses, including when and how to use them, then this walkthrough might not be for you. For everyone else, who might or might not have heard about **lenses**, this write-up is intended as an introductory into the concept.

### Basics

What are lenses? In short, they are **functional getter/setter**. A short hand functionality for accessing as well as updating data objects. Now what does this look like in the real world and why should we use it? Let’s start off with a simplified example.

```js
const user = {id: 1, name: 'userA'}
```

To access the user id we would write something like the following.

```js
user.id // 1
```

Updating could be accomplished like this f.e.

```js
user.id = 2
```

Now, mutating the user object might not be the best idea, as it might lead to some unexpected behaviours later on. We might assume some value to be defined, but have no guarantees.
So let’s bring in a couple of utility functions that could improve the previous approach.

### Getting Started

We’ll add a couple of **[Ramda](https://ramdajs.com/)** functions (you can also use **lodash/fp**).

_assoc_: for overriding a specified property and getting a new object in return. 
_prop_: for accessing an object property.
So a cleaner variant of accessing and updating the user id can be achieved by applying these functions.

```js
prop('id', user) // 1
assoc('id', 2, user) // {id: 2, name: 'userA'}
```

Updating the user id in the second example doesn’t mutate our user object. This is what we want to ensure in the first place.

Due to the fact that we have prop and assoc for updating and retrieving an object’s specified properties, we can start thinking about what lenses mean in this context. We know that lenses are functional getter/setter and our previous examples already enabled us to get and set properties, so let’s write some pseudo code to combine these things together.

```js
const idLens = lens(prop('id'), assoc('id'))
view(idLens, user) // 1
set(idLens, 2, user) // // {id: 2, name: 'userA'}
```

We introduced a couple of new functions here, so let’s go through them one by one and see how this all fits together.

The first function _lens_ expects two arguments, the first being a getter and the second being a setter. This doesn’t really need too much explanation, contrary to the next two lines. What do _view_ and _set_ do? _view_ expects a lens and an object to apply the lens on. _set_ expects three arguments, the lens the new value and the user. It then updates the defined field by applying the lens with the new value, just like the name implies.

What we have up until now is pseudo code, so to get a better feel, let’s create a naive implementation before we fallback to a tested and ready-to-use solution.

```js
const lens = (getter, setter) => {
  return ({
    get: obj => getter(obj),
    set: (val, obj) => setter(val, obj),
  })
}
```

Admitted, it’s not the nicest of all solutions, but it should work. Now that we have our own _lens_ function in place, let’s figure out how view and set might work.

```js
const view = (lens, obj) => {
  return lens.get(obj)
}
const set = (lens, val, obj) => {
  return lens.set(val, obj)
}
```

Actually, we could just call _get_ on the lens object when using _view_ and the lens _set_ method when applying the standalone _set_ function. Rerunning our previous example should return the expected outcome.

```js
const idLens = lens(prop('id'), assoc('id'))
view(idLens, user) // 1
set(idLens, 2, user) // // {id: 2, name: 'userA'}
```

From here on out let’s neglect our naive implementation and use Ramda instead. Ramda offers a number of very useful lens functions. The following examples rely on _lens, lensProp, lensPath, lensIndex, view, set, over_ and common Ramda functions like _compose_ and _map_. Now that we have the low level basics covered, let’s see lenses in action. The following examples will be based on the following user object.

```js
const user = {
  id: 1,
  name: 'userA',
  company: {
    id: 12,
    name: 'bar',
    address: {
      street: 'randomstreet',
    }
  },
  comments: [
    {id: 2, text: 'yes, this could work.', to: {id: 4}},
    {id: 3, text: 'not sure.', to: {id: 12}},
    {id: 4, text: 'well, maybe', to: {id: 4}},
  ],
}
```

Our previous code can be rewritten using the _lensProp_ shorthand function, which returns a lens for getting and setting a defined field. To reiterate on our previous example.

```js
const idLens = lensProp('id')
view(idLens, user) // 1
set(idLens, 2, user) // user = {id: 2, name: 'userA', ...}
```

Let’s see how we can update nested properties, by retrieving the companies street address. Ramda’s _lensPath_ comes handy in this specific case.

```js
const userCoStrLens = lensPath(['company', 'address', 'street'])
view(userCoStrLens, user) // randomstreet
```

Updating the street name can be achieved as easily, without mutating the user object.

```js
const updateUserAddress = set(userCoStrLens, 'Teststreet', user)
```


#### Updating array values

We can even update specific array values by using _lensIndex_.

```js
const firstItem = lensIndex(0)
```

Same can be achieved with _lensPath_, which can handle keys as well indices.

```js
const firstCommentLensId = lensPath(['comments', 0, 'id'])
view(firstCommentLensId, user) // 2
```

The _firstCommentLensId_ can also be applied to update that comment _id_ using the _set_ function.

```js
set(firstCommentLensId, 12, user)
```


#### Using over to apply a function

We have seen _view_ and _set_ in action, but we haven’t touched a third interesting function called _over_. With _over_ we can apply a function to update the field of an object or array. Imagine we wanted to uppercase the first comment.

```js
const firstCommentTextLens = lensPath(['comments', 0, 'text'])
over(firstCommentTextLens, toUpper, user) 
```


#### Composition

Another nice and interesting fact is that lenses compose.

```js
const addressLens = lensProp('address')
const streetLens = lensProp('street')
const addressStreetLens = compose(addressLens, streetLens)
```

A noteworthy aspect is that they **compose from left to right**. We can also mix and match _lensIndex_ and _lensProp_ just like in the following example.

```js
const commentLens = lensProp('comments')
const firstIndexLens = lensIndex(0)
const idLens = lensProp('id')
compose(commentLens, firstIndexLens, idLens)
```

By using map we can even convert all comments to uppercase.

```js
over(commentLens, map(over(textLens, toUpper)), user)
```

### Real World

You still might be asking yourself if this is worth all the trouble, when one can simply update or access an object directly. One use case that comes to mind is that we can pass a lens function around, enabling to retrieve values from a state object without having to know about how this object is actually structured. Another is that we never directly mutate our object or array but get a shallow copy in return.

Lenses should be used when we need to update or extend an object without wanting to break other implementations or where we don’t have access to libraries like immutable.js f.e.

Using lenses when rendering a view for example, where you need to format the given data, is one good example.

```js
const getComments = view(lensProp('comments'))
const getText = view(textLens)
const textToUpper = over(textLens, toUpper)
const allTextToUpper =
  compose(map(compose(getText, textToUpper)), getComments)
```

Now we can call _allTextToUpper_ which ensures that all are comments are in capital letters minus mutating our original user object.

```js
const renderView = user => (
  <div id="comments">
    {map(comment => (<div>{comment}</div>), allTextToUpper(user))}
  </div>
)
```

### Outro

We should have covered the basics with this write-up.
If you want to read more about lenses and see further examples, I would recommend reading [Lenses with Immutable.js](https://medium.com/@drboolean/lenses-with-immutable-js-9bda85674780#.s19tv1ze3) by Brian Lonsdorf and [Lenses and Virtual DOM Support Open Closed](http://joneshf.github.io/programming/2015/12/19/Lenses-and-Virtual-DOM-Support-Open-Closed.html) by Hardy Jones.

If you have any feedback please leave a comment here or on [Twitter](https://twitter.com/sharifsbeat).