Adam Warski

28 Feb 2015

Quicklens: modify deeply nested case class fields

functional programming
metaprogramming
scala

TL;DR: Quicklens: modify deeply nested fields in case classes, e.g.:
modify(person)(_.address.street.name).using(_.toUpperCase).
Similar to lenses, but without the actual lens creation.

Lenses are very useful when you have to update a deeply nested field in a hierarchy of case classes. For example if we have:

case class Street(name: String)
case class Address(street: Street)
case class Person(address: Address)

val person = Person(Address(Street("1 Functional Rd.")))

and we’d like to modify the name of the street (let’s say convert to upper case), we would have to do:

person.copy(
  address = person.address.copy(
    street = person.address.street.copy(
      name = person.address.street.name.toUpperCase
    )
  )
)

Quite a lot of boilerplate! Plus it’s quite hard to see what we are actually trying to achieve.

One solution is to use lenses and lens composition, which provide a much shorter way to achieve the above. There’s a couple of lens libraries, e.g. Monocle; using it, our example now becomes:

val _name = Lenser[Street](_.name)
val _street = Lenser[Address](_.address)
val _address = Lenser[Person](_.person)

(_address ^|-> _street ^|-> _name).modify(_.toUpperCase)(person)

Lenses can be also created using macro annotations, however that’s IDE-unfriendly. The lens objects (_name, _street, _address) provide a view into a specific field of a case class, and can be composed with each other (plus a couple of extra things!).

What if we just want to update a field? The process of creating the lens objects can then become boilerplate as well.

Using the Quicklens macro we can simplify the original task to:

modify(person)(_.address.street.name).using(_.toUpperCase)

As modify is a macro, this code is transformed during compile time into the appropriate chain of “copy” invocations (no intermediate objects or actual lenses are created); hence the bytecode is almost identical to the original, “boilerplate” solution.

I think Quicklens can be a good alternative for lens creation if you need just the field updates, without the additional features of full-blown lenses. I’m also not aware of a similar existing implementation. Quicklens is open-source and available on Github under the Apache2 license. Comments welcome!

comments powered by Disqus

Any questions?

Can’t find the answer you’re looking for?