Lenses are very useful when you have to update a deeply nested field in a hierarchy of case classes. For example if we have:
1 2 3 4 5
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:
1 2 3 4 5 6 7
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:
1 2 3 4 5
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 (
_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 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!