Monocle is a great library (and not the only one) which implements lenses pattern, which is great if we have to change one field in huge nested object. Like in example http://julien-truffaut.github.io/Monocle/
case class Street(number: Int, name: String)
case class Address(city: String, street: Street)
case class Company(name: String, address: Address)
case class Employee(name: String, company: Company)
The following boilerplate
employee.copy(
company = employee.company.copy(
address = employee.company.address.copy(
street = employee.company.address.street.copy(
name = employee.company.address.street.name.capitalize // luckily capitalize exists
)
)
)
)
Can easily be replaced with
import monocle.macros.syntax.lens._
employee
.lens(_.company.address.street.name)
.composeOptional(headOption)
.modify(_.toUpper)
Which is great. As far as I understand, macros magic converts everything exactly to the same code as above.
However, what if I want to combine several actions? What if I want to change street name, address city and company name at the same time with one call? Like the following:
employee.copy(
company = employee.company.copy(
address = employee.company.address.copy(
street = employee.company.address.street.copy(
name = employee.company.address.street.name.capitalize // luckily capitalize exists
),
city = employee.company.address.city.capitalize
),
name = employee.company.name.capitalize
)
)
If I just reuse lenses here, I would have the following code:
employee
.lens(_.company.address.street.name).composeOptional(headOption).modify(_.toUpper)
.lens(_.company.address.city).composeOptional(headOption).modify(_.toUpper)
.lens(_.company.name).composeOptional(headOption).modify(_.toUpper)
Which will eventually be translated to THREE employee.copy(...).copy(...).copy(...)
invocations, not just ONE employee.copy(...)
. How to make it better?
Furthermore, it would be really great to apply a sequence of operations. Like sequence of pairs Seq[(Lens[Employee, String], String => String)]
where first element is a lens pointing to the correct field and the second one is a function which modifies it. It will help to build such sequence of operations from the outside. For the above example:
val operations = Seq(
GenLens[Employee](_.company.address.street.name) -> {s: String => s.capitalize},
GenLens[Employee](_.company.address.city) -> {s: String => s.capitalize},
GenLens[Employee](_.company.name) -> {s: String => s.capitalize}
)
or something similar...