An Introduction to Lenses in Scalaz

Posted By Douglas Rapp on Thursday, February 9th, 2012

An Introduction to Lenses in Scalaz

One of the simplest constructs to understand in Scalaz is the Lens. But because of its simplicity, I didn’t realize the practical usefulness of lenses in Scala until I came across a problem whose implementation was greatly improved by using them. It became clear to me that using lenses improves your codebase. The problem involved tracking and applying updates to a dataset that was represented as a nested structure of case classes and other immutable datastructes. Initially, the implementation involved only using many of the built-in facilities provided via the Scala std. lib. The code quickly became unwieldy and unclear to anyone besides me (and in a few weeks I wouldn’t have been able to read it, either). As this was an important algorithm in this project I had to make the code durable to future changes. I also wanted the algorithm to be clearly described by its implementation as it has one or two nuances that future users of the code should be able to grasp from its implementation and tests. I accomplished these goals using lenses, cleaning up the code base significantly while providing a better, clearer implementation that was more testable.

In these next few posts we will evolve an adaptation of that problem starting in this post with the basics of lenses and moving on to more complex usages. Hopefully, by the end of this post you too will begin to see their benefits and be interested in exploring them more.

What is a Lens?

At the most basic level, lenses are sort of getters and setters for immutable data. The definition is so simple, in fact, it’s often easier to show it than explain it:

That is it. A lens is a an object that contains two functions: get and set. get takes an A and returns a B. set takes an A and B and returns a new A. It’s easy to see that the type B is a value contained in A. When we pass an instance to get we return that value. When we pass an A and a B to set we update the value B in A and return a new A reflecting the change. For convenience the get is aliased to apply.

This should be pretty clear, but let’s see a simple and useless (by itself) lens in action. Scalaz defines Lens.fst which focuses on the first element in a pair. It is defined like so:

Copy – The Built-In Approach

At this point, you may be wondering, like I did at first, “why this is anything more than a toy?” But lenses are so much more. To really see them shine, an example is needed. Throughout this and subsequent posts on the subject, we will evolve an adapted version of some code I recently wrote using lenses. The example will revolve around my dog, Nola, who is laying next to me tearing through what used to resemble a tennis ball. Nola is quite the friendly pitbull to everyone and everything except her toys. Let’s assume we wanted to build an application which tracked the state of the toys in her toybox. In order to have immutable representations of our data, we will use case classes to model the toybox. We will give each toy in the toybox a unique name to identify it. One way we could represent this is:

For the toys in the toybox we will track their condition and how many days old they are:

One of the first things we want to be able to do in our application is update the condition of a toy in the toybox:

Of course our updates cannot be in place so the function must return the “updated” (read: new) ToyBox to us. In Scala, case classes come with a “built-in” way to perform “updates” to the data, which we know as copy. Even as we continue to use lenses copy will be integral to our implementation. However, it is how we use it with lenses that is important and it is what makes lenses a beneficial addition to your Scala programming toolbox. In order to illustrate this, look at the implementation using only copy:

Using Lenses

There is nothing terribly wrong with the implementation above. It works, but I contend it is not very good. I will address it in more detail after we see how we can do this with lenses, but mostly the implementation lacks clarity and good design. This makes the code brittle. We can improve this using lenses. To do so, we first need the lenses themselves. From before we know that Lens[A,B] lets us get and set the value of type B in some A. Given that we are updating ToyConditions of Toys in our ToyBox, it’s clear we want a lens Lens[ToyBox,Toy]. We could build that lens pretty easily by basically duplicating most of the implementation above of updateCondition, but we won’t. When using lenses, you want to build the most “focused” lens you can. This means you want to update members that are direct children of a piece of data, not something deeper in the hierarchy of your immutable data structures. Once those lenses are built you can combine them as needed to operate on those nested structures. This provides much simpler, clearer implementations of the lenses and greater flexibility. For our example, let’s start from the top. Following our rule, if we want to update the ToyBox, the lens we should write is a Lens[ToyBox,Map[String,Toy], which lets us get and set a Map in a ToyBox. As I mentioned before, to implement our lenses, we will still use copy. We won’t throw the baby out with the bathwater, just put the baby in a cleaner tub.

That was easy, right? Next we need a way to update the condition of a toy or a Lens[Toy,ToyCondition].

Also simple and, once again, using copy. These two lenses along with the built-in facilities for updating the value of a key in a Map, the plus (+) method, give us everything we need to implement a new version of updateCondition.

This version looks pretty similar to the first but uses our lenses. It’s still not a great implementation but it’s much better than the copy only implementation. Why? Using lenses we hide how our data structures are modified to create new, updated instances. This is better software design. For example, what if later we are forced to abandon the case class in favor of implementing our own class and companion object, losing copy along the way? Using our original approach, we would have to change the implementation of this function and any other function like it. Using lenses, that code only must be changed in a single place: the lens definitions. Like most other functional programming concepts, lenses make our code more durable to change and more modular.

The mod function

Although it won’t be until a later post that we cover some ways to really improve our implementation of updateCondition, there is one way we can remove a little of the cruft using a function defined on lenses called mod. Given a Lens[A,B], mod takes an A and a function B => B. mod will get the value, B, out of the A, apply the function to it and set it back, returning the updated A. Its definition is:

Using mod we can get rid of of the second call to toyBox.toys:

Boiling it Down

While there’s much more to come, we can already see how lenses improve our implementation of updateCondition. Using lenses we hid the implementation of reading and updating a value in an immutable data structure. This sort of information hiding is exactly what we want when writing modular code. Our codebase becomes much more resistant to change and more DRY as a result. Functions like mod and other higher-order functions in the Lens library also give us greater code re-use. In the next post in this series, we’ll be learning how to compose lenses. Composition allows us to write very limited lenses (ones that know only about the immediate members of a value, following our rule from earlier) and use them to build lenses that provide real utility. This sort of program design enforces a principle that should be familiar to programmers who have written object-oriented programs: the Law of Demeter. By implementing lenses that are very “focused” we are following the Law of Demeter in an functional programming world by defining lenses while exposing a minimal amount of knowledge about their containing or contained structures. With composition, we’ll be able to perform operations on deeply nested immutable structures without coupling those operations to those structures’ design. The lens abstraction also draws a parallel to other abstractions like the presenter pattern from Model-View-Presenter, but instead of being an abstraction that provides an API to prepare the data for display, the API allows you to read and update the data (are you wondering why you’ve always defined that as part of your class up until now?).

Using Scalaz’s lenses isn’t all peaches and cream, though. There are a few annoyances, although I believe they are more nuisances than downsides. Defining lenses, in particular when following the rule about “focused” lenses, requires a lot of boilerplate. Most of the implementations are identical minus a few names and types here and there. Additionally, in the way we have used lenses so far, there is a question of where to put them. I had the luxury of defining them in a floating manner or in the shell. In practice would you put them in a companion object or maybe a package object? (Hint: putting them in your class defintion and incurring the needless memory penalty for creating a bunch of useless instances when you only need one is a bad idea.) There are people addressing these problems, however. The Lensed Compiler Plugin generates lenses for your case classes automatically, and I believe there are some people who are pushing for integration of lenses directly in the Scala compiler. I hope it happens.

What’s Next

We ended up with a decent implementation of updateCondition using lenses but the Lens library provided by Scalaz is much more powerful and feature-rich than what we have seen so far. Using it could have made our implementation much better. In order to learn the library and explore the benefits gained, we will leave updateCondition behind for a more involved example — with even more case classes. We will see some of the lenses provided by Scalaz besides those that work on 2-tuples and how to compose lenses as well as how to use lenses within for-comprehensions, giving an almost mutable facade to working with immutable data. There are also some Lens Laws we need to cover. Additionally, it’s useful to be able to extend and implement functionality similar to what is provided by Scalaz. We will look at some examples of that, too.

Don’t have a StackMob account? Signup below.

Comments

comments