Wie klone ich eine Fallklasseninstanz und ändere nur ein Feld in Scala?

208

Angenommen, ich habe eine Fallklasse, die Personas repräsentiert, Menschen in verschiedenen sozialen Netzwerken. Instanzen dieser Klasse sind vollständig unveränderlich und werden in unveränderlichen Sammlungen aufbewahrt, die schließlich von einem Akka-Schauspieler modifiziert werden.

Jetzt habe ich eine Fallklasse mit vielen Feldern und erhalte die Meldung, dass ich eines der Felder aktualisieren muss.

case class Persona(serviceName  : String,
                   serviceId    : String,
                   sentMessages : Set[String])

// Somewhere deep in an actor
val newPersona = Persona(existingPersona.serviceName,
                         existingPersona.serviceId,
                         existingPersona.sentMessages + newMessage)

Beachten Sie, dass ich alle Felder angeben muss, obwohl sich nur eines ändert. Gibt es eine Möglichkeit, vorhandene Personen zu klonen und nur ein Feld zu ersetzen, ohne alle Felder anzugeben, die sich nicht ändern? Kann ich das als Merkmal schreiben und für alle meine Fallklassen verwenden?

Wenn Persona eine kartenähnliche Instanz wäre, wäre dies einfach.

François Beausoleil
quelle

Antworten:

324

case classwird mit einer copyMethode geliefert, die genau dieser Verwendung gewidmet ist:

val newPersona = existingPersona.copy(sentMessages = 
                   existingPersona.sentMessages + newMessage)
Nicolas
quelle
5
Wo ist das dokumentiert? Ich kann keinen Verweis zum Kopieren an den "offensichtlichen" Stellen finden, z. B. scala-lang.org/api/current/index.html .
François Beausoleil
6
Es ist ein Merkmal der Sprache, das Sie in der Scala-Spezifikation finden: scala-lang.org/docu/files/ScalaReference.pdf §5.3.2. Es ist nicht in der API, weil es nicht Teil der API ist;)
Nicolas
1
Ich wollte, dass ScalaDoc die Kopiermethoden anzeigt, wenn sie existieren. Wollen Sie das nicht?
Soc
4
Es wäre schön. Aber hier ist das Problem von François (wenn ich recht habe), dass er nicht wusste, dass er eine copyMethode haben wird, wenn er a deklariert case class.
Nicolas
2
@ JonathanNeufeld Mit diesem Gefühl wirst du im reinen Fp-Camp viele Unfreunde finden. Ich stimme Ihnen eher zu.
Javadba
46

Seit 2.8 haben Scala- copyFallklassen eine Methode, die benannte / Standardparameter nutzt, um ihre Magie zu entfalten:

val newPersona =
  existingPersona.copy(sentMessages = existing.sentMessages + newMessage)

Sie können auch eine Methode erstellen Persona, um die Verwendung zu vereinfachen:

case class Persona(
  svcName  : String,
  svcId    : String,
  sentMsgs : Set[String]
) {
  def plusMsg(msg: String) = this.copy(sentMsgs = this.sentMsgs + msg)
}

dann

val newPersona = existingPersona plusMsg newMsg
Kevin Wright
quelle
10
existingPersona.copy(sentMessages = existingPersona.sentMessages + newMessage)
Jean-Philippe Pellet
quelle
0

Erwägen Sie die Verwendung lensin der ShapelessBibliothek:

import shapeless.lens

case class Persona(serviceName  : String,
                   serviceId    : String,
                   sentMessages : Set[String])
// define the lens
val messageLens = lens[Persona] >> 'sentMessages 

val existingPersona = Persona("store", "apple", Set("iPhone"))

// When you need the new copy, by setting the value,
val newPersona1 = messageLens.set(existingPersona)(Set.empty)
// or by other operation based on current value.
val newPersona2 = messageLens.modify(existingPersona)(_ + "iPad")

// Results:
// newPersona1: Persona(store,apple,Set())
// newPersona2: Persona(store,apple,Set(iPhone, iPad))

Wenn Sie verschachtelte Fallklassen haben, kann das Erstellen der Methoden getterund setteretwas mühsam sein. Es ist eine gute Chance, durch Verwendung der Objektivbibliothek zu vereinfachen.

Bitte beachten Sie auch:

Kaihua
quelle
0

Ich wollte keine große Bibliothek für komplexe Objektive einbinden, mit denen Sie Werte tief in verschachtelten Fallklassen festlegen können. Es stellt sich heraus, dass es sich nur um ein paar Codezeilen in der Scalaz-Bibliothek handelt:

  /** http://stackoverflow.com/a/5597750/329496 */
  case class Lens[A, B](get: A => B, set: (A, B) => A) extends ((A) => B) with Immutable {
    def apply(whole: A): B = get(whole)

    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
  }

Sie können dann Objektive erstellen, die tief verschachtelte Werte viel einfacher festlegen als die integrierte Kopierfunktion. Hier ist ein Link zu einem großen Satz komplexer Objektive, mit denen meine Bibliothek stark verschachtelte Werte festlegt.

simbo1905
quelle