Combine collection of lenses

Viewed 841

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...

1 Answers
Related