Boilerplate-free lenses for arbitrary case classes

A combination of LabelledGeneric and singleton-typed Symbol literals supports boilerplate-free lens creation for arbitrary case classes

// A pair of ordinary case classes ...
case class Address(street: String, city: String, postcode: String)
case class Person(name: String, age: Int, address: Address)

// Some lenses over Person/Address ...
val nameLens = lens[Person] >> Symbol("name")
val ageLens = lens[Person] >> Symbol("age")
val addressLens = lens[Person] >> Symbol("address")
val streetLens = lens[Person] >> Symbol("address") >> Symbol("street")
val cityLens = lens[Person] >> Symbol("address") >> Symbol("city")
val postcodeLens = lens[Person] >> Symbol("address") >> Symbol("postcode")

val person = Person("Joe Grey", 37, Address("Southover Street", "Brighton", "BN2 9UA"))

Read a field

ageLens.get(person) should be(res0)

Update a field

val updatedPerson = ageLens.set(person)(38)
updatedPerson.age should be(res0)

Transform a field

val updatedPerson = ageLens.modify(person)(_ + 1)
updatedPerson.age should be(res0)

Read a nested field

streetLens.get(person) should be(res0)

Update a nested field

val updatedPerson = streetLens.set(person)("Montpelier Road")
updatedPerson.address.street should be(res0)