Adam Warski

14 Mar 2015

Quicklens: traversing options and lists

functional programming
metaprogramming
scala

Quicklens is a small library which allows to modify deeply nested fields in case classes e.g.: modify(person)(_.address.street.name).using(_.toUpperCase), without the need to create dedicated lens objects.

I got some very good feedback on the initial release – thanks! There’s also a spin-off implementation, using a different syntax.

One problem that I anticipated from the beginning and also mentioned in the comments, was that it wasn’t possible to traverse Optional fields. And that’s of now fixed! Options and Lists can be “unwrapped” using the .each method. For example:

import com.softwaremill.quicklens._

case class Street(name: String)
case class Address(street: Option[Street])
case class Person(addresses: List[Address])

val person = Person(List(
  Address(Some(Street("1 Functional Rd."))),
  Address(Some(Street("2 Imperative Dr.")))
))

val newPerson = modify(person)(_.addresses.each.street.each.name)
  .using(_.toUpperCase)

The .each can only be used inside modify. You can add support for your own containers by providing an implicit QuicklensFunctor[C] with the appropriate C type parameter (there are default implementations for List, Vector and Option).

Other changes

  • added documentation on how to create lenses, that is a modification of a path parametrized by the actual object: val nameLens = modify(_: Person)(_.address.street.name). This can be later used as follows: nameLens(person).using(_.toUpperCase).
  • a dedicated method to set a new value of a field, instead of transforming the old value: modify(person)(_.address.street.name).setTo("2 Imperative Dr.")
  • a andThenModify method provided by an implicit conversion to combine two lenses:
val modifyAddress = modify(_: Person)(_.address)
val modifyStreetName = modify(_: Address)(_.street.name)

val newPerson = (modifyAddress andThenModify modifyStreetName)(person)
  .using(_.toUpperCase)

Alternate syntax

There were also some comments that the current syntax suggests mutable behaviour (which, of course, is not true – all modifications result in copies of the case classes to be created). An alternative could be:

copy(person).modifying(_.address.street.name).using(_.toUpperCase)

However it is a bit longer than the original. What do you think?

Quicklens is available on GitHub under the Apache2 license.

comments powered by Disqus

Any questions?

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