Es curioso que nadie haya agregado lentes, ya que fueron HECHOS para este tipo de cosas. Entonces, aquí hay un documento de antecedentes de CS sobre él, aquí hay un blog que trata brevemente sobre el uso de lentes en Scala, aquí hay una implementación de lentes para Scalaz y aquí hay un código que lo usa, que sorprendentemente se parece a su pregunta. Y, para reducir la placa de la caldera, aquí hay un complemento que genera lentes Scalaz para clases de casos.
Para obtener puntos de bonificación, aquí hay otra pregunta SO que toca lentes y un artículo de Tony Morris.
Lo importante de las lentes es que son compostables. Por lo tanto, son un poco engorrosos al principio, pero siguen ganando terreno cuanto más los usas. Además, son excelentes para la capacidad de prueba, ya que solo necesita probar lentes individuales y puede dar por sentado su composición.
Entonces, basado en una implementación proporcionada al final de esta respuesta, así es como lo haría con lentes. Primero, declare lentes para cambiar un código postal en una dirección y una dirección en una persona:
val addressZipCodeLens = Lens(
get = (_: Address).zipCode,
set = (addr: Address, zipCode: Int) => addr.copy(zipCode = zipCode))
val personAddressLens = Lens(
get = (_: Person).address,
set = (p: Person, addr: Address) => p.copy(address = addr))
Ahora, compónelos para obtener una lente que cambie el código postal de una persona:
val personZipCodeLens = personAddressLens andThen addressZipCodeLens
Finalmente, use esa lente para cambiar raj:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)
O, usando un poco de azúcar sintáctica:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens(raj) + 1)
O incluso:
val updatedRaj = personZipCodeLens.mod(raj, zip => zip + 1)
Aquí está la implementación simple, tomada de Scalaz, utilizada para este ejemplo:
case class Lens[A,B](get: A => B, set: (A,B) => A) extends Function1[A,B] with Immutable {
def apply(whole: A): B = get(whole)
def updated(whole: A, part: B): A = set(whole, part) // like on immutable maps
def mod(a: A, f: B => B) = set(a, f(this(a)))
def compose[C](that: Lens[C,A]) = Lens[C,B](
c => this(that(c)),
(c, b) => that.mod(c, set(_, b))
)
def andThen[C](that: Lens[B,C]) = that compose this
}