Lenses

Composable Getters and Setters for Functional Programming

Eric Elliott
JavaScript Scene
7 min readDec 23, 2018

--

Smoke Art Cubes to Smoke — MattysFlicks — (CC BY 2.0)

Note: This is part of the “Composing Software” book that started life right here as a blog post series. It covers functional programming and compositional software techniques in JavaScript (ES6+) from the ground up.
< Previous | << Start over at Part 1

A lens is a composable pair of pure getter and setter functions which focus on a particular field inside an object, and obey a set of axioms known as the lens laws. Think of the object as the whole and the field as the part. The getter takes a whole and returns the part of the object that the lens is focused on.

The setter takes a whole, and a value to set the part to, and returns a new whole with the part updated. Unlike a function which simply sets a value into an object’s member field, Lens setters are pure functions:

Note: In this text, we’re going to use some naive lenses in the code examples just to give you a beneath-the-hood peek at the general concept. For production code, you should look at a well tested library like Ramda, instead. The API differs between different lens libraries, and it’s possible to express lenses in more composable, elegant ways than they are presented here.

Imagine you have a tuple array representing a point’s x, y, and z coordinates:

To get or set each field individually, you might create three lenses. One for each axis. You could manually create getters which focus on each field:

Likewise, the corresponding setters might look like this:

Why Lenses?

State shape dependencies are a common source of coupling in software. Many components may depend on the shape of some shared state, so if you need to later change the shape of that state, you have to change logic in multiple places.

Lenses allow you to abstract state shape behind getters and setters. Instead of littering your codebase with code that dives deep into the shape of a particular object, import a lens. If you later need to change the state shape, you can do so in the lens, and none of the code that depends on the lens will need to change.

This follows the principle that a small change in requirements should require only a small change in the system.

Background

In 1985, “Structure and Interpretation of Computer Programs” described getter and setter pairs (called put and get in the text) as a way to isolate an object's shape from the code that uses the object. The text shows how to create generic selectors that access parts of a complex number independent of how the number is represented. That isolation is useful because it breaks state shape dependencies. These getter/setter pairs were a bit like referenced queries which have existed in relational databases for decades.

Lenses took the concept further by making getter/setter pairs more generic and composable. They were popularized after Edward Kmett released the Lens library for Haskell. He was influenced by Jeremy Gibbons and Bruno C. d. S. Oliveira, who demonstrated that traversals express the iterator pattern, Luke Palmer’s “accessors”, Twan van Laarhoven, and Russell O’Connor.

Note: An easy mistake to make is to equate the modern notion of a functional lens with Anamorphisms, based on Erik Meijer, Maarten Fokkinga, and Ross Paterson’s “Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire” in 1991. “The term ‘lens’ in the functional reference sense refers to the fact that it looks at part of a whole. The term ‘lens’ in a recursion scheme sense refers to the fact that [( and )] syntactically look kind of like concave lenses. tl;dr They have nothing to do with one another." ~ Edward Kmett on Stack Overflow

Lens Laws

The lens laws are algebraic axioms which ensure that the lens is well behaved.

  1. view(lens, set(lens, a, store)) ≡ a — If you set a value into the store, and immediately view the value through the lens, you get the value that was set.
  2. set(lens, b, set(lens, a, store)) ≡ set(lens, b, store) — If you set a lens value to a and then immediately set the lens value to b, it's the same as if you'd just set the value to b.
  3. set(lens, view(lens, store), store) ≡ store — If you get the lens value from the store, and then immediately set that value back into the store, the value is unchanged.

Before we dive into code examples, remember that if you’re using lenses in production, you should probably be using a well tested lens library. The best one I know of in JavaScript is Ramda. We’re going to skip that for now and build some naive lenses ourselves, just for the sake of learning:

Let’s prove the lens laws for these functions:

Composing Lenses

Lenses are composable. When you compose lenses, the resulting lens will dive deep into the object, traversing the full object path. Let’s import the more full-featured lensProp from Ramda to demonstrate:

That’s great, but there’s more to composition with lenses that we should be aware of. Let’s take a deeper dive.

Over

It’s possible to apply a function from a => b in the context of any functor data type. We've already demonstrated that functor mapping is composition. Similarly, we can apply a function to the value of focus in a lens. Typically, that value would be of the same type, so it would be a function from a => a. The lens map operation is commonly called "over" in JavaScript libraries. We can create it like this:

Setters obey the functor laws:

For the composition example, we’re going to use an auto-curried version of over:

Now it’s easy to see that lenses under the over operation also obey the functor composition law:

We’ve barely scratched the surface of lenses here, but it should be enough to get you started. For a lot more, detail, Edward Kmett has spoken a lot on the topic, and many people have written much more in-depth explorations.

Eric Elliott is a distributed systems expert and author of the books, “Composing Software” and “Programming JavaScript Applications”. As co-founder of DevAnywhere.io, he teaches developers the skills they need to work remotely and embrace work/life balance. He builds and advises development teams for crypto projects, and has contributed to software experiences for Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean, Metallica, and many more.

He enjoys a remote lifestyle with the most beautiful woman in the world.

--

--