Creating Lenses in Elixir

This is the first article on my own medium site, so welcome! The articles I write here will be for my own learnings/discussions and will be a bit rougher but I hope people find them useful.

In an effort to better my functional programming and Elixir understanding I thought I’d discuss lenses.

There are lots of articles about how to use a lens but very few on how we would implement them. Today, we’ll implement our own lens while discussing the hows and whys.

This article assumes a basic understanding of what a lens is but let’s still mention…

What is a Lens

A lens is not too dissimilar to getters and setters in imperative languages with 4 primary functions that we use when working with our data structures:
view: The view function is used to retrieve data from our nested structure.
over: over is a higher order function. It requires a function which takes the data from the record your lens is pointing at, and returns the entire structure with the update applied.
set: Set is similar to over but instead of taking a function it takes a constant value. The field that the lens is pointing at will be updated with this constant value.
concat/compose/append: A single purpose lens isn’t that useful so you will typically find a concat/compose/append function in the library that lets you join multiple lenses together. This is how you traverse deep within a structure with a lens.

Why use a Lens?

Another example is when you wish to defer code execution until the last possible moment when you need the data. As we’ll see shortly, a lens basically boils down to function composition. You are creating a function chain that will be executed only when you provide the data you’re interested in.

Finally, if you are working in a typed language which can infer types a lens provides the functionality to change a record’s type. In Elixir that isn’t much of a problem but I feel it’s worth mentioning.

There are others but in the interest of brevity let’s just dive into an example.

Example Lens Usage

So here we can see that we have various nesting examples that we can use through the rest of our tests.

Let’s first confirm that the view function will, actually, view a record:

Here we have created, and extended, a lens that will reach right into the deepest point in the tree. This is a similar example to using get_in([:nested, :deeply_nested, :tally])

So now that we have a state and proven that we can view deeply, let’s set deeply with a constant value.

This would be similar to put_in(%{ a: %{ b: %{ c: “abc” } } }, [:a, :b, :c], 6)

Now let’s have a bit of fun with over. We’ll start with a function to increment a tally from the lens provided:

Lens.over takes a state, a function that will be applied to the lens record found, and a lens. We extend the lens received to point to the tally and update. This is similar to update_in(state, [:tally], &(&1 + 1))

So now let’s create the tests. I will just create 3 tests where each level of state will be updated, confirming that concat works with updating, we confirmed view and set above, asserting on the single nested lens and starting with an empty lens.

We could even avoid destructuring state and start with Lens.from(:state)

So that example doesn’t really show much of a difference from using update_in but it highlights an example of concatenating lenses to go deeply. I’ll create one more example. A pattern I think might be nice when working through deeply nested structures, one where each function in a chain will update the state and pass forward or simply pass their extended lens to their nested functions.

So we have our tests and examples of simple lens usage. Now, let’s actually create our own lens.

Implement the Lens

In our tests we are working exclusively with objects so let’s create the 2 functions that get and set values in a Map.

You could also create a Lens.fromIdx that will can with lists, and any other assortment. You just need to be able to retrieve from the structure, and set it. Let’s stay with Maps for now.

So that’s pretty straight forward. We provide a key and get/put in the Map. So how does the Lens.view look?

Super easy, just use the get function on the record provided. I’ve created 2 versions of the view function, so we can use pipelines to build lenses or records.

Finally we have concat. Now as we are only working with functions, concat will be the composition of the functions.

As we’re working with lenses I’ll comment here that get is a contravariant function and set is covariant. Contravariant virtually boils down to “more explicitly defined through the result” while covariant is a function that returns a less specific type. This is the main reason I went on my lens journey, to discover what is a profunctor. We could push right out to a profunctor but that’s wayyyy too deep for this article so let’s move on.

So the new get function calls the left lens first, and then the right lens. This is the process of becoming more explicit in our result, the contravariant function implementation.

Set is a bit more convoluted as we only apply the change to the internal structure but we need to update the external to maintain immutability. Here we need to get the outer lens’ focus point (l_get.(obj)), pass that to the inner lens and make the changes. We then update the outer object with the inner result and then we return to our full record. This is a more typical function composition where we evaluate right to left.

So this gives us the ability to view deeply nested structures and a function to extend our lenses. Let’s create our set and over functions:

So that’s it! Here we have our set and over functions, plus the inverse to simplify pipelines. We’re evaluating in our over and passing the result straight into set which will update our record.

That is the full implementation that we need! 🎉 If you look at the supported lenses, like focus, they have type matching, error handling, etc that you would expect from a library but this is the core of what a common lens is, and how they are implemented.

VS get_in, put_in and update_in

However, as this is our lens we can build upon this base setup with the other Lens concepts like Prisms (which handles error cases when fields are not present, focus has a great implementation) or extend and modify the lens itself, following Functor map. Let’s do that.

Map over our Lens

I’m going to leverage the map function name, as it’s the functor function. and execute a function on each item they contain and returns the result within the Enum or Stream type. These make them Functors. So let’s execute a function on our data within our lens as we work with it and return another lens. Test first:

This is the exact same result as the previous nested test except we don’t need to capture the lens we’re working with. We are running the function over the :nested Map record and then passing through to the deeply_nested function.

So how would we implement this? With get being used every time we call set and over we only need to change get.

So here we have run the function over whatever get returns and return the change. We are also maintaining contravariance on the get function so we will always know what it’s doing! Same with concat.


I do, however, like the pattern in React with simple state. I like redux but that is a bit heavy at times when all I want to do is work with a root component’s state. That’s not Elixir and that’s why you’re here so … signing off!

  • Aaron

Feel free to follow me on twitter @aabrook. I don’t post often but am always happy to read/share/chat about Elixir or any functional dev in general