Lenses with Immutable.js

Recommended listening during read: spotify:track:3bCmDqflFBHijgJfvtqev5

Tl;dr

Lenses are pretty useful to dig deep into data structures and make changes in a pure functional way. Immutable.js and lenses are a great fit because they provide us with a universal api for working with built in and immutable data structures. We can also convert between built in and Immutable.js structures with isos which makes library interop easier.

Prereq

I’m going to be assuming some knowledge of ramda, which implies understanding the basics of currying and composition. If you’re not familiar, you may want to first read http://fr.umio.us/why-ramda/

Here’s the import section for this post if you want to run each example*

Through the Looking-Glass

What is a lens? Well, it’s a getter/setter pair. Okay, they are more than that, but for now think of them as such. Let’s first define some data, then play around with one.

This data consists of a user, Charles, who has two suspiciously simple addresses. What are you hiding, Charles?

Basic usage

Now we must define a lens to access properties on this data… let’s use lensProp to make a name lens.

As the examples suggest, name does nothing on its own. It must be used with another function like view, set, or over.

view(name) amounts to prop(‘name’) in this situation. What is interesting, though, is set and over — they preserve the whole data structure despite the alterations. They are also pure (hence, immutable) setters so we needn’t worry about bugs and the cognitive load arising from mutable state.

This isn’t the most efficient setter if you want to change hundreds of records (in that case use immutable.js like below), but it’s a great, cheap way to get an immutable change and I’ll bet it has no measurable performance difference at all in your application.


Composition

Lenses compose with each other. They do so in left-to-right order, much like transducers, their perverse cousins.

We made a lens called firstStreet which is the composition of the address, street, and first lenses. We introduced lensIndex here, which does what one might expect — makes a lens that accesses the zeroth index of an array. **

What’s amazing is that we’re just using normal compose, which means our lenses are just functions, and indeed they are. Notice too, we can compose object accessor lenses like street with array lenses like first. Finally, witness the ease at which we perform surgery to a small piece of the nested data structure.

Mapping

What if we need to access our user nested inside of a functor? We can use mapped which is also a lens. It acts like map.

Since our user is three functors deep (Task, Maybe, []), we use mapped 3 times to drill down to the user.

Where might all of this be useful? Let’s see a “real world” example.


The Real World

Say we’re going to try to render a profile page with our user. For security reasons, we must remove the street number. That’s pretty real world, huh.

Since the over function makes an immutable change while keeping our user in tact, we can update just the streets and send the whole thing off to the screen unharmed***


Immutable.js

Making our own lenses is fairly straight forward. We have a helper function lens that takes two function arguments: our getter and our setter. So if we want to make a lens that works for immutable.js we can just delegate to its get and set methods.

Now we can use this lens to recreate the example above:

The big win here is that we can compose getters/setters from Immutable with other getters/setters as well as lenses like mapped. This gives us one api to compose the world. You might also think of this as a protocol or interface for getters/setters: As a caller, I don’t need to know if I should use brackets, dot, get()/set(), prop(), etc — I can just give the lens to view/set/over.


Isomorphisms

An issue that comes up when working with Immutable.js is that most libraries expect built-in data structures, not things like List and Map. This forces us to convert with toJS() often, which is a pain.

We can define an isomorphism using iso that will work like a lens. Just like the lens helper function, it takes two arguments: a function that converts and a function that recovers. So here we can convert to and from a normal JS array to a List.

Let’s take it for a spin:

There are many isomorphisms between Immutable.js data types themselves let alone to JS and back, try to define some for fun!

In addition to isos, there are also folds and traversals, but we don’t have the time to go into them here. I will say that since Immutable.js structures implement reduce, we can use folds right out of the box (e.g. sum, max, any, etc). I should post some examples on the twitters…

*I don’t believe https://github.com/ramda/ramda-lens is on npm just yet (should be very shortly) so just point the package.json entry at the git url for now.

**@puffnfresh pointed out that [] does not satisfy the lens laws so lensIndex(0) does not qualify as a lens so that’s a misnomer. In https://hackage.haskell.org/package/lens-4.13.1/docs/Control-Lens-At.html ix, its analog, is considered a traversal or LensLike.

***Checkout http://joneshf.github.io/programming/2015/12/19/Lenses-and-Virtual-DOM-Support-Open-Closed.html for further examples

All artwork by: http://javierperez.ws/