Engraçado que ninguém adicionou lentes, pois elas foram feitas para esse tipo de coisa. Então, aqui está um documento de plano de fundo da CS, aqui está um blog que aborda brevemente o uso de lentes no Scala, aqui está uma implementação de lentes para o Scalaz e aqui está um código usando-o, que se parece surpreendentemente com a sua pergunta. E, para reduzir a placa da caldeira, aqui está um plugin que gera lentes Scalaz para classes de casos.
Para pontos de bônus, aqui está outra questão do SO que toca nas lentes e um artigo de Tony Morris.
O grande problema das lentes é que elas são compostáveis. Portanto, eles são um pouco pesados no começo, mas continuam ganhando terreno quanto mais você os usa. Além disso, eles são ótimos em termos de testabilidade, já que você só precisa testar lentes individuais e pode dar como certa sua composição.
Portanto, com base em uma implementação fornecida no final desta resposta, veja como você faria isso com lentes. Primeiro, declare as lentes para alterar um CEP em um endereço e um endereço em uma pessoa:
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))
Agora, componha-as para obter uma lente que altera o CEP de uma pessoa:
val personZipCodeLens = personAddressLens andThen addressZipCodeLens
Por fim, use essa lente para alterar o raj:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)
Ou, usando um pouco de açúcar sintático:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens(raj) + 1)
Ou até:
val updatedRaj = personZipCodeLens.mod(raj, zip => zip + 1)
Aqui está a implementação simples, tirada do Scalaz, usada neste exemplo:
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
}