Забавно, что никто не добавил линзы, поскольку они были СДЕЛАНЫ для таких вещей. Итак, вот справочная статья CS по этому поводу , вот блог, в котором кратко говорится об использовании линз в Scala, вот реализация линз для Scalaz, и вот некоторый код, использующий ее, что удивительно похоже на ваш вопрос. И, чтобы сократить количество шаблонов , вот плагин, который генерирует линзы Scalaz для классов корпусов.
Что касается бонусных очков, вот еще один вопрос SO, который касается линз, и статья Тони Морриса.
Особенность линз в том, что они являются составными. Так что сначала они немного громоздки, но чем больше вы их используете, тем шире их популярность. Кроме того, они отлично подходят для тестирования, поскольку вам нужно тестировать только отдельные линзы, и вы можете принять их состав как должное.
Итак, исходя из реализации, представленной в конце этого ответа, вот как вы бы сделали это с линзами. Сначала объявите линзы, чтобы изменить почтовый индекс в адресе и адрес в человеке:
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))
Теперь составьте их, чтобы получить линзу, меняющую почтовый индекс у человека:
val personZipCodeLens = personAddressLens andThen addressZipCodeLens
Наконец, используйте эту линзу, чтобы изменить raj:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)
Или, используя синтаксический сахар:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens(raj) + 1)
Или даже:
val updatedRaj = personZipCodeLens.mod(raj, zip => zip + 1)
Вот простая реализация, взятая из Scalaz, использованная для этого примера:
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
}