C'est drôle que personne n'ait ajouté d'objectifs, car ils étaient FABRIQUÉS pour ce genre de choses. Donc, voici un document de base CS à ce sujet, voici un blog qui aborde brièvement l'utilisation des objectifs dans Scala, voici une implémentation d'objectifs pour Scalaz et voici un code qui l'utilise, qui ressemble étonnamment à votre question. Et, pour réduire la quantité de plaques chauffantes, voici un plugin qui génère des lentilles Scalaz pour les classes de cas.
Pour les points bonus, voici une autre question SO qui touche aux lentilles, et un article de Tony Morris.
Le gros problème avec les objectifs est qu'ils sont composables. Ils sont donc un peu encombrants au début, mais ils gagnent du terrain à mesure que vous les utilisez. En outre, ils sont parfaits pour la testabilité, car vous n'avez besoin que de tester des lentilles individuelles et vous pouvez tenir pour acquise leur composition.
Donc, sur la base d'une implémentation fournie à la fin de cette réponse, voici comment procéder avec des objectifs. Tout d'abord, déclarez les lentilles pour changer un code postal dans une adresse et une adresse chez une personne:
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))
Maintenant, composez-les pour obtenir une lentille qui change le code postal d'une personne:
val personZipCodeLens = personAddressLens andThen addressZipCodeLens
Enfin, utilisez cet objectif pour changer de raj:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)
Ou, en utilisant du sucre syntaxique:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens(raj) + 1)
Ou même:
val updatedRaj = personZipCodeLens.mod(raj, zip => zip + 1)
Voici l'implémentation simple, tirée de Scalaz, utilisée pour cet exemple:
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
}