Disons que j'ai les deux suivants case class
:
case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)
et l'instance de Person
classe suivante:
val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg",
"Mumbai",
"Maharashtra",
411342))
Maintenant , si je veux mettre à jour zipCode
de raj
alors je vais devoir faire:
val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))
Avec plus de niveaux de nidification, cela devient encore plus laid. Existe-t-il un moyen plus propre (quelque chose comme celui de Clojure update-in
) de mettre à jour de telles structures imbriquées?
scala
case-class
zipper
manquant
la source
la source
Réponses:
Fermetures éclair
La fermeture éclair de Huet permet une traversée et une «mutation» pratiques d'une structure de données immuable. Scalaz fournit des fermetures éclair pour
Stream
( scalaz.Zipper ) etTree
( scalaz.TreeLoc ). Il s'avère que la structure de la fermeture à glissière est automatiquement dérivable de la structure de données d'origine, d'une manière qui ressemble à la différenciation symbolique d'une expression algébrique.Mais comment cela vous aide-t-il avec vos classes de cas Scala? Eh bien, Lukas Rytz a récemment prototypé une extension de scalac qui créerait automatiquement des fermetures à glissière pour les classes de cas annotées. Je vais reproduire son exemple ici:
La communauté doit donc persuader l'équipe Scala que cet effort doit être poursuivi et intégré dans le compilateur.
Incidemment, Lukas a récemment publié une version de Pacman, programmable par l'utilisateur via un DSL. Il ne semble pas qu'il ait utilisé le compilateur modifié, car je ne vois aucune
@zip
annotation.Réécriture d'arbres
Dans d'autres circonstances, vous souhaiterez peut-être appliquer une transformation à l'ensemble de la structure de données, selon une stratégie (descendante, ascendante) et basée sur des règles qui correspondent à la valeur à un moment donné de la structure. L'exemple classique consiste à transformer un AST pour une langue, peut-être pour évaluer, simplifier ou collecter des informations. Kiama prend en charge la réécriture , consultez les exemples dans RewriterTests et regardez cette vidéo . Voici un extrait pour vous mettre en appétit:
Notez que Kiama étapes en dehors du système de type pour y parvenir.
la source
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:
Maintenant, composez-les pour obtenir une lentille qui change le code postal d'une personne:
Enfin, utilisez cet objectif pour changer de raj:
Ou, en utilisant du sucre syntaxique:
Ou même:
Voici l'implémentation simple, tirée de Scalaz, utilisée pour cet exemple:
la source
personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)
est le même quepersonZipCodeLens mod (raj, _ + 1)
mod
n'est cependant pas une primitive pour les objectifs.Outils utiles pour utiliser les objectifs:
Je veux juste ajouter que les projets Macrocosm et Rillit , basés sur des macros Scala 2.10, fournissent la création dynamique de lentilles.
Utilisation de Rillit:
Utilisation du macrocosme:
la source
J'ai cherché quelle bibliothèque Scala qui a la plus belle syntaxe et la meilleure fonctionnalité et une bibliothèque non mentionnée ici est monocle qui pour moi a été vraiment bonne. Un exemple suit:
Celles-ci sont très belles et il existe de nombreuses façons de combiner les lentilles. Scalaz, par exemple, exige beaucoup de passe-partout et cela se compile rapidement et fonctionne très bien.
Pour les utiliser dans votre projet, ajoutez simplement ceci à vos dépendances:
la source
Shapeless fait l'affaire:
avec:
Notez que bien que d'autres réponses ici vous permettent de composer des objectifs pour aller plus loin dans une structure donnée, ces objectifs sans faille (et d'autres bibliothèques / macros) vous permettent de combiner deux objectifs non liés de sorte que vous puissiez créer un objectif qui définit un nombre arbitraire de paramètres dans des positions arbitraires. dans votre structure. Pour les structures de données complexes, cette composition supplémentaire est très utile.
la source
Lens
code dans la réponse de Daniel C. Sobral et ainsi évité d'ajouter une dépendance externe.En raison de leur nature composable, les lentilles offrent une très belle solution au problème des structures fortement imbriquées. Cependant, avec un faible niveau d'imbrication, j'ai parfois l'impression que les objectifs sont un peu trop importants, et je ne veux pas introduire l'approche globale des objectifs s'il n'y a que quelques endroits avec des mises à jour imbriquées. Par souci d'exhaustivité, voici une solution très simple / pragmatique pour ce cas:
Ce que je fais, c'est simplement écrire quelques
modify...
fonctions d'aide dans la structure de niveau supérieur, qui traitent de la vilaine copie imbriquée. Par exemple:Mon objectif principal (simplifier la mise à jour côté client) est atteint:
La création de l'ensemble complet des aides de modification est évidemment ennuyeuse. Mais pour les éléments internes, il est souvent acceptable de les créer simplement la première fois que vous essayez de modifier un certain champ imbriqué.
la source
Peut-être que QuickLens correspond mieux à votre question. QuickLens utilise des macros pour convertir une expression conviviale IDE en quelque chose qui est proche de l'instruction de copie d'origine.
Compte tenu des deux exemples de classes de cas:
et l'instance de la classe Person:
vous pouvez mettre à jour zipCode de raj avec:
la source