128 votes

Nettoyeur de façon à mettre à jour des structures imbriquées

Dire que j’ai obtenu après deux `` es :

l’instance suivante de `` classe :

Maintenant, si je veux mettre à jour de alors je vais devoir faire :

Avec plus de niveaux d’imbrication, cela devient encore plus laid. Y a-t-il un moyen plus propre (quelque chose comme de Clojure `` ) pour mettre à jour ces imbriqué de structures ?

189voto

Daniel C. Sobral Points 159554

Marrant que personne n'a ajouté des lentilles, depuis qu'ils ont FAIT pour ce genre de trucs. Donc, ici, est une CS de fond de papier sur elle, ici est un blog qui touche brièvement sur les lentilles de l'utilisation de Scala, ici, est une des lentilles de mise en œuvre de Scalaz et ici est un code en utilisant, qui ressemble étonnamment à votre question. Et, pour couper vers le bas sur la plaque de la chaudière, voici un plugin qui génèrent Scalaz lentilles pour des classes de cas.

Pour les points de bonus, voici un autre S. O. question qui touche à lentilles, et un papier par Tony Morris.

Le gros problème sur les lentilles, c'est qu'ils sont composables. Ils sont donc un peu lourd au début, mais ils continuent de gagner du terrain le plus vous les utilisez. Aussi, ils sont parfaits pour la testabilité, puisque vous n'avez besoin de tester des lentilles individuelles, et peut tenir pour acquis leur composition.

Donc, basé sur une mise en œuvre à la fin de cette réponse, voici comment vous pouvez le faire avec des lentilles. Tout d'abord, déclarer des lentilles pour changer un code postal d'une adresse, et une adresse à 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, pour les composer pour obtenir un objectif que les changements de code postal dans une personne:

val personZipCodeLens = personAddressLens andThen addressZipCodeLens

Enfin, l'utilisation de cet objectif de changement raj:

val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)

Ou, à l'aide de certaines sucre syntaxique:

val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens(raj) + 1)

Ou encore:

val updatedRaj = personZipCodeLens.mod(raj, zip => zip + 1)

Voici la simple mise en œuvre, la prise de Scalaz, utilisé 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
}

95voto

retronym Points 35066

Fermetures à glissière

Huet de la fermeture à Glissière pratique de la traversée et de la "mutation" d'un immuable structure de données. Scalaz fournit fermeture éclair pour l' Stream (scalaz.Fermeture à glissière), et Tree (scalaz.TreeLoc). Il s'avère que la structure de la fermeture à glissière est automatiquement dérivables à partir de l'original de la structure de données, d'une manière qui ressemble à différentiation symbolique d'une expression algébrique.

Mais comment cela vous aider avec votre Scala cas des classes? Eh bien, Lukas Rytz récemment prototypé une extension de scalac qui permettrait de créer automatiquement des fermetures à glissière pour annoté des classes de cas. Je vais reproduire son exemple ici:

scala> @zip case class Pacman(lives: Int = 3, superMode: Boolean = false) 
scala> @zip case class Game(state: String = "pause", pacman: Pacman = Pacman()) 
scala> val g = Game() 
g: Game = Game("pause",Pacman(3,false))

// Changing the game state to "run" is simple using the copy method:
scala> val g1 = g.copy(state = "run") 
g1: Game = Game("run",Pacman(3,false))

// However, changing pacman's super mode is much more cumbersome (and it gets worse for deeper structures):
scala> val g2 = g1.copy(pacman = g1.pacman.copy(superMode = true))
g2: Game = Game("run",Pacman(3,true))

// Using the compiler-generated location classes this gets much easier: 
scala> val g3 = g1.loc.pacman.superMode set true
g3: Game = Game("run",Pacman(3,true)

Donc, les besoins de la communauté afin de persuader la Scala équipe que cet effort devra être poursuivi et intégrée dans le compilateur.

D'ailleurs, Lukas a récemment publié une version de Pacman, programmable par l'utilisateur par le biais d'un DSL. N'a pas l'air comme il avait l'habitude de la modification de compilateur, même si, comme je ne vois pas d' @zip des annotations.

Arbre De Réécriture

Dans d'autres circonstances, vous pouvez appliquer un peu de la transformation sur l'ensemble de la structure de données, selon une certaine stratégie (top-down, bottom-up), et fondé sur des règles qui correspondent à l'encontre de la valeur à un certain point dans la structure. L'exemple classique est la transformation d'un AST pour une langue, peut-être, d'évaluer, de les simplifier, ou de recueillir des informations. Kiama prend en charge la Réécriture, voir les exemples dans les RewriterTests, et de regarder cette vidéo. Voici un extrait pour vous mettre en appétit:

// Test expression
val e = Mul (Num (1), Add (Sub (Var ("hello"), Num (2)), Var ("harold")))

// Increment every double
val incint = everywheretd (rule { case d : Double => d + 1 })
val r1 = Mul (Num (2), Add (Sub (Var ("hello"), Num (3)), Var ("harold")))
expect (r1) (rewrite (incint) (e))

Notez que Kiama étapes à l'extérieur du système de type pour atteindre cet objectif.

12voto

Sebastien Lorber Points 9682

Des outils utiles pour l'utilisation de Lentilles:

Tiens juste à ajouter que le Macrocosme et Rillit projets, en s'appuyant sur Scala 2.10 macros, fournit la Dynamique de la Lentille de la Création.


À L'Aide De Rillit:

case class Email(user: String, domain: String)
case class Contact(email: Email, web: String)
case class Person(name: String, contact: Contact)

val person = Person(
  name = "Aki Saarinen",
  contact = Contact(
    email = Email("aki", "akisaarinen.fi"),
    web   = "http://akisaarinen.fi"
  )
)

scala> Lenser[Person].contact.email.user.set(person, "john")
res1: Person = Person(Aki Saarinen,Contact(Email(john,akisaarinen.fi),http://akisaarinen.fi))

À L'Aide De Macrocosme:

Ceci fonctionne même pour les cas les classes définies dans le courant de la compilation exécuter.

case class Person(name: String, age: Int)

val p = Person("brett", 21)

scala> lens[Person].name._1(p)
res1: String = brett

scala> lens[Person].name._2(p, "bill")
res2: Person = Person(bill,21)

scala> lens[Person].namexx(()) // Compilation error

11voto

Johan S Points 543

J'ai été en regardant autour de ce que Scala bibliothèque qui a les plus belles de la syntaxe et de la meilleure fonctionnalité et une bibliothèque n'est pas mentionné ici est le monocle qui pour moi a été vraiment bon. Un exemple qui suit:

import monocle.Macro._
import monocle.syntax._

case class A(s: String)
case class B(a: A)

val aLens = mkLens[B, A]("a")
val sLens = aLens |-> mkLens[A, String]("s")

//Usage
val b = B(A("hi"))
val newB = b |-> sLens set("goodbye") // gives B(A("goodbye"))

Ils sont très sympa et il y a beaucoup de façons de combiner les lentilles. Scalaz par exemple demande beaucoup de passe-partout et cette compile rapide et fonctionne bien.

Pour les utiliser dans votre projet, il suffit d'ajouter ce pour les dépendances:

resolvers ++= Seq(
  "Sonatype OSS Releases"  at "http://oss.sonatype.org/content/repositories/releases/",
  "Sonatype OSS Snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/"
)

val scalaVersion   = "2.11.0" // or "2.10.4"
val libraryVersion = "0.4.0"  // or "0.5-SNAPSHOT"

libraryDependencies ++= Seq(
  "com.github.julien-truffaut"  %%  "monocle-core"    % libraryVersion,
  "com.github.julien-truffaut"  %%  "monocle-generic" % libraryVersion,
  "com.github.julien-truffaut"  %%  "monocle-macro"   % libraryVersion,       // since 0.4.0
  "com.github.julien-truffaut"  %%  "monocle-law"     % libraryVersion % test // since 0.4.0
)

7voto

bluenote10 Points 1932

En raison de leur composable la nature, les verres offrent une très belle solution pour le problème de fortement structures imbriquées.

Cependant, avec un faible niveau d'imbrication, j'ai parfois l'impression lentilles sont un peu trop. Par souci d'exhaustivité, je veux ajouter un très simple/solution pragmatique à ce cas. Ce que j'ai à faire est de simplement écrire quelques modify... des fonctions d'assistance dans le niveau supérieur de la structure, qui traitent de l'laid imbriquée copie. Par exemple:

case class Person(firstName: String, lastName: String, address: Address) {
  def modifyZipCode(modifier: Int => Int) = 
    this.copy(address = address.copy(zipCode = modifier(address.zipCode)))
}

Mon objectif principal (la simplification de la mise à jour côté client) est atteint:

val updatedRaj = raj.modifyZipCode(_ => 41).modifyZipCode(_ + 1)

La création de l'ensemble complet de modifier les helpers est évidemment gênant. Mais pour des trucs internes, il est souvent bon de simplement créer la première fois que vous essayez de modifier un certain champ imbriqué.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X