this post was originally published on my Github Pages site on August 19th, 2018
Mo' functions, mo' problems
While this is generally fine for very shallow objects, the story gets much more complicated when trying to enforce immutability in complex objects while changing deeply-nested values...
This is obviously not very idiomatic. The problem is made worse when a library like React enforces the idea of immutability (this isn't React's fault, though). So how do we deal with this in a way that feels a bit more natural? For this, I have turned to lenses. Lenses are a special type of object that combines a setter and getter such that you can perform standard operations, most commonly setting, getting, and mapping, on values of an object in a way that the original object is not modified. Not only do lenses allow you to operate on objects while enforcing immutability, they also compose together such that each lens digs deeper into your complex objects and exposes a set of immutable operations for the entire object.
So how do we make a lens?
While this is neat, lenses are not very useful on their own (just like any other structure). There is not a whole lot we can do with
nameLens on its own. This is where lens operators come in. The three operators provided by Ramda are
over, which allow you to get, set, and map the focused property, respectively.
The examples below will use the following object:
This function accepts a lens, then an object, and returns the value of the focused property of the lens. This essentially just calls the getter of the lens and is fairly straightforward. Here, we can use
nameLens to view the value of the focused property:
This function accepts a lens, a value, and then an object, and returns a copy of the object with the focused property set to the provided value. Again
set essentially just calls the setter of the lens and is fairly straightforward. Here, we use the
set operator along with
nameLens to set the value of the focused property. Note that the original object remains unchanged.
This function accepts a lens, a transform function, and then an object, and returns a copy of the object with the focused property set to the original value of the focused property *after* passing it through the provided transform function. This operator is a little harder to understand. This function is just like the
map function since it runs a function *over* the focused value. Here we use the
over operator to call the
toUpperCase method of the string. Just like before, the original object remains unchanged.
What if we need to change a value in the
Suppose we need to update the value in
person.parking.row while maintaining immutability. This is where the compositional nature of lenses come in handy since lenses compose using the standard compose operator! This is how we could create a lens for this scenario:
parkingRowLens can be used with the lens operators to do the same setting, getting, and mapping operations. Best of all, the original object will still remain unchanged due to the nature of lenses.
Is there an easier way to create lenses?
If you are using Ramda, then definitely yes. Otherwise, be sure to check the owner's manual for your lens package. Ramda provides a few convenience functions to help us create lenses:
|R.lensProp||Creates a lens which focuses on the provided property.||
|R.lensPath||Creates a composition of lenses to focus on the provided path.||
|R.lensIndex||Create a lens to focus on the provided array index.||