functional programming

Scrap the Boilerplate in Scala with Lenses

Case classes provide a convenient way of working with immutable objects in scala. Manipulating fields within them can be tedious, however. In this post, we will look at how lenses can be used to abstract over this complexity while preserving the benefits of immutability.

Consider the following example:

To update the first line of a particular address, we can simply make a copy of it, overriding the relevant value:

To verify that the original value a has not changed as a result of this operation:

For shallow updates, using this technique works well. However, suppose the address field is part of some more complex structure; perhaps we have accounts that have an owner, each of whom has an address:

Updating the address for account acc1 then becomes:

which we can verify works as expected:

Nesting calls to copy in this fashion quickly leads to code that is verbose and difficult to maintain. Fortunately, there is a solution to this problem in the world of functional programming: lenses. Here, we will use monocle‘s GenLens macro to rewrite our example.

The code above defines three lenses, which we can use as ‘getters’ and ‘setters’ for the fields they relate to:

At face value, this provides little benefit over the previous approach, since we still need a reference to an address in order to set the first line of it. However, the real power of lenses is that they compose. For example, we can construct a lens that allows us to set the first line of an account owner’s address directly from a reference to the account itself:

With this in place, we can now write

…and we are done.

To dig a little deeper into the subject, suppose now that the domain model changes and account owners are no longer required to provide their address:

Here, we can no longer use lenses, since we can’t always be guaranteed to get a value back. Instead, we can use monocle’s Optional as a way of querying and modifying an owner’s address. We supply two functions as constructor arguments: these will be used as the ‘getter’ and ‘setter’ respectively:

We can check that this provides access to the address field for an account owner as follows:

We can compose Lenses and Optionals using composeOptional. Note that the result of this is always an Optional:

As before, this enables us to manipulate the nested addresses in our updated account type directly:

The examples presented here only scratch the surface of what lenses (and the wider topic of optics) are capable of. If you are interested in learning more, I would recommend checking out the Monocle docs and experimenting further. The code for all the examples used in this post is available as a GitHub Gist.


No comments yet.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Email Updates

Subscribe to this blog and receive notifications of new posts by email.