Functional Lenses in Javascript with Ramda
Tags: javascript, ramda, functional, lenses
Published: 2019-06-14 02:26:57

[Lenses](https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/basic-lensing) provide a means to decouple an object's shape from the logic operating on that object. It accomplishes this using the getter/setter pattern to 'focus in' on a sub-part of the object, which then isolates that sub-part for reads and writes without mutating the object.

This can bring about multiple benefits. Let's start with the shape decoupling nature of lenses.

Decoupling an object's shape allows for future reshaping of your data while minimizing the effects of the rest of the code in your application. Take, for example, an object representing a person.

```javascript
const person = {
  firstName: 'John',
  lastName: 'Doe'
}
```

Now imagine the shape of that object changes such that the `firstName` and `lastName` properties are replaced with a single property called `name` which is itself an object containing properties `first` and `last`:

```javascript
const person = {
  name: {
    first: 'John',
    last: 'Doe'
  }
}
```

Any code working with that object would now need to be updated to reflect the object's change in shape. This is prevented in OOP through use of classes which hide the data's internal structure and provides access through a getter/setter API. If the shape of a class's internal data changes, all that needs updated is that class's API. Lenses provide the same benefit for plain old objects.

Another benefit to lenses is the ability to write to an object without mutating the object in the process. The non-mutation of data is, of course, one of the staples of FP (functional programming). The problem is, the larger and more complex the data you're working with is, the more difficult it becomes to change deeply nested data without mutations. As we'll see later on, lenses simplify the process with just a couple lines of code no matter how complex your data is.

And finally, lenses are [curryable](https://en.wikipedia.org/wiki/Currying) and [composable](https://en.wikipedia.org/wiki/Function_composition), which makes them fit in well with the FP paradigm. We'll use both of these in later examples.

With the help of [Ramda](https://ramdajs.com/), let's create a lens for working with the person's `firstName`.
 

```javascript
const person = {
  firstName: 'John',
  lastName: 'Doe'
}
```

We'll start with Ramda's most generic lens creation function called simply [lens()](https://ramdajs.com/docs/#lens). As mentioned previously, lenses use the getter/setter pattern for reading and writing data to our object. Let's create those first.

```javascript
const getFirstName = data => data.firstName // getter
const setFirstName = (value, data) => ({    // setter
  ...data, firstName: value
})
```

And then the lens itself:

```javascript
const firstNameLens = lens(getFirstName, setFirstName)
```

The `lens()` function takes two arguments, the getter and setter we defined previously. The lens is then ready to be applied to an object, in this example, the person object. But before we do so I want to point out a few things.

* The lens, itself, isn't given a reference to any data. This makes the lens reusable and able to be applied to any data, as long as that data conforms to the shape required of its getter and setter arguments. In other words, this lens is only useful when applied to data that has a `firstName` property, which could be a person, an employee, or even a pet.
* Since the lens isn't tied to any specific piece of data, the getter and setter functions need to be given the data they'll be operating on. The lens will take the object it's applied to and automatically pass it to the supplied getters and setters for you.
* Since FP doesn't allow the mutation of data, the setter must return an updated copy of the data that the lens is applied to. In this example, our lens will be applied to a person object so the lens's setter function will return a copy of the person object.

Let's look at how we can use the lens to read from an object using Ramda's [view()](https://ramdajs.com/docs/#view) function:

```javascript
view(firstNameLens, person) // => "John"
```

The `view()` function takes two arguments; a lens, and an object to apply that lens to. It then executes the len's getter function to return the value of the property the lens is focused on; in this case, `firstName`. 

It's also worth noting that `view()` is curryable, in that we can configure `view()` with just the lens and supply the object later. This becomes particularly handy if you want to compose `view()` with other functions using Ramda's [compose()](https://ramdajs.com/docs/#compose), [pipe()](https://ramdajs.com/docs/#pipe), or various other composition functions.

```javascript
const sayHello = name => `Hello ${name}`

const greetPerson = pipe(
  view(firstNameLens),
  sayHello
);

greetPerson(person) // => "Hello John"
```

Now let's see how we can write to an object with our lens using Ramda's [set()](https://ramdajs.com/docs/#pipe) function:

```javascript
set(firstNameLens, 'Jane', person) 
// => {"firstName": "Jane", "lastName": "Doe"}
```

The `set()` function also takes a lens and an object to apply that lens to, as well as a value to update the focused property. And as mentioned earlier, we get back a copy of the object with the focused property changed. And, just like `view()`, `set()` is curryable allowing you first configure it with a lens and value and provide it with data later.

There's a third lens application function called [over()](https://ramdajs.com/docs/#over), which acts just like `set()` except, instead of providing an updated value, you provide a function for updating the value. The provided function will be passed the result of the lens's getter. Let's say we want to uppercase the person's `firstName`:

```javascript
over(firstNameLens, toUpper, person)
// => {"firstName": "JOHN", "lastName": "Doe"}
```

We're also making use of Ramda's [toUpper()](https://ramdajs.com/docs/#toUpper) function. It's the equivalent of:

```javascript
const toUpper = value => value.toUpperCase()
```

I want to go back to our original getter and setter functions and look at more concise ways they can be written. 

```javascript
const getFirstName = data => data.firstName // getter
const setFirstName = (value, data) => ({    // setter
  ...data, firstName: value
})
```

If we're using Ramda for lens creation, it only makes sense to take advantage of Ramda functions for other parts of our code. In particular we'll use Ramda's [prop()](https://ramdajs.com/docs/#prop) function to replace our getter and the [assoc()](https://ramdajs.com/docs/#assoc) function to replace our setter.

The `prop()` function takes a property name and an object, and returns the value of that property name on that object. It works very similarly to our getter function.

```javascript
prop('firstName', person) // => "John"
```

Again, as with most all Ramda functions, `prop()` is curryable, allowing us to configure it with a property name and provide the data later:

```javascript
const firstNameProp = prop('firstName')
firstNameProp(person) // => "John"
```

When using it with a lens, we can configure it with a property name and let the lens pass its data later.

```javascript
lens(prop('firstName'), ...)
```

This is also an example of [point-free style or tacit programming](https://en.wikipedia.org/wiki/Tacit_programming) in that we don't define one or more of the arguments (in this case, person) in our logic. It can be hard to see how this works if you're not used to this style commonly found in FP, but it can make more sense when broken down...

When passing a single argument to a [multiary](https://en.wikipedia.org/wiki/Arity) (multi-arg) curried function, it returns a new function accepting the rest of the arguments. It's not until all the arguments are supplied that it executes its function body and returns the results. So when configuring `prop()` with just the property name, we'll receive a new function that takes the data argument. That matches perfectly with what a lens getter is: a function that takes a data argument.

The `assoc()` function works the same way, but is designed for writing rather than reading. In addition, it'll return a copy of the object it's writing to, which is the same functionality required by a lens setter.

```javascript
assoc('firstName', 'Jane', person)
// => {"firstName": "Jane", "lastName": "Doe"}
```

When used with a lens, we can configure `assoc()` with just the property name, and let the `set()` function curry the value and data through.

```javascript
const firstNameLens = lens(prop('firstName'), assoc('firstName'))

view(firstNameLens, person) // => "John"
set(firstNameLens, 'Jane', person)
// => {"firstName": "Jane", "lastName": "Doe"}
```

Those are basics of lenses but there are other, more specialized, lens creation functions in Ramda. Specifically, [lensProp()](https://ramdajs.com/docs/#lensProp), [lensIndex()](https://ramdajs.com/docs/#lensIndex), and [lensPath()](https://ramdajs.com/docs/#lensPath). These are the functions you'll probably use most often when creating lenses. The generic `lens()` would be used only when needing to do very customized lens creation. Let's go over each of these specialized lens creation functions.

The `lensProp()` function takes a single argument; a property name.

```javascript
const lastNameLens = lensProp('lastName')
```

And that's it! The property name is all it needs to generate the appropriate getter and setter:

```javascript
view(lastNameLens, person) // => "Doe"
set(lastNameLens, 'Smith', person)
// => {"firstName": "John", "lastName": "Smith"}
```

The `lensIndex()` function works similarly to `lensProp()` except it's designed for focusing in on an array index, and therefore, you pass it an index rather than a property name. Let's add an array of data to our person to test it out.

```javascript
const person = {
  firstName: 'John',
  lastName: 'Doe',
  phones: [
    {type: 'home', number: '5556667777'},
    {type: 'work', number: '5554443333'}
  ]
}
```

Then when applying the lens...

```javascript
const firstPhoneLens = lensIndex(0)

view(firstPhoneLens, person.phones)
// => {"number": "5556667777", "type": "home"}

set(
  firstPhoneLens, 
  {type: 'mobile', number: '5557773333'}, 
  person.phones
)
// => [
//  {"number": "5557773333", "type": "mobile"}, 
//  {"number": "5554443333", "type": "work"}
//]
```

Notice how when applying the lens we have to pass in `person.phones`. While this works, it's less than ideal because now we're relying on knowledge of the object's shape in our general application code, rather than hiding it in our lens. In addition, when applying the lens with the `set()` function, we get back the array of phones, not the person. This emphasizes that whatever object you give the lens application, the same is what you get back. The likely next step would be to merge the new array of phones back into the person object. This would, of course, need to be done in a non-mutating way... something that Ramda could handle easily. However, it'd be better to not even have to take that extra step. That leads us to the third specialized lens, `lensPath()` which is designed for focusing in on nested data.

```javascript
const homePhoneNumberLens = lensPath(['phones', 0, 'number'])

view(homePhoneNumberLens, person) // => "5556667777"
set(homePhoneNumberLens, '5558882222', person)
// => {
//  "firstName": "John", "lastName": "Doe"
//  "phones": [
//    {"number": "5558882222", "type": "home"}, 
//    {"number": "5554443333", "type": "work"}
//  ]
//}
```

As you can see, `lensPath()` takes an array with path segments leading to the nested data that we want focused. Each path segment can be a property name or an index. Since we're giving it the root person object, we get back a full copy of the person object with just the home phone number changed. In my opinion, this is where the lens feature really starts to shine. Imagine if we wanted to duplicate the result of the `set()` function above, but with regular Javascript. Even with the latest features, such as spreading and destructuring, we might end up with something like the following:

```javascript
const [homePhone, ...otherPhones] = person.phones
const updatedPerson = {
  ...person,
  phones: [
    {...homePhone, number: '5558882222'},
    ...otherPhones
  ]
}
```

That's quite a bit of work compared to the two line example using lenses!

One of the more powerful features of lenses is their ability to be composed together with other lenses. This allows you to build up new and more complex lenses from existing ones:

```javascript
const phonesLens = lensProp('phones')
const workPhoneLens = lensIndex(1)
const phoneNumberLens = lensProp('number')

const workPhoneNumberLens = compose(
  phonesLens, 
  workPhoneLens, 
  phoneNumberLens
)

view(workPhoneNumberLens, person) // => "5554443333"
```

The result is not too different from using a single `lensPath()`. In fact, if I had no need for the individual `phonesLens` and `workPhoneLens` in other contexts, I'd probably just use a `lensPath()` instead. However, the nice thing about this approach is that no one lens has the full knowledge of the entire shape of a person. Instead, each lens just keeps track of their own pieces of shape, relieving that responsibility from the next lens in the composition. If we were to, for example, change the property name `phones` to `phoneList`, we'd only need to update the lens responsible for that piece of shape (`phoneLens`) rather than updating multiple lenses that happen to overlap that path.

And that's a rundown of the features and benefits of functional lenses in Javascript with Ramda.