Come clonare un'istanza di case case e cambiare solo un campo in Scala?


208

Diciamo che ho una classe di casi che rappresenta personaggi, persone su diversi social network. Le istanze di quella classe sono completamente immutabili e sono conservate in collezioni immutabili, che alla fine saranno modificate da un attore di Akka.

Ora ho una classe di casi con molti campi e ricevo un messaggio che dice che devo aggiornare uno dei campi, qualcosa del genere:

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)

Nota che devo specificare tutti i campi, anche se cambia solo uno. C'è un modo per clonarePersona esistente e sostituire un solo campo, senza specificare tutti i campi che non cambiano? Posso scriverlo come tratto e usarlo per tutte le mie classi di casi?

Se Persona fosse un'istanza simile a una mappa, sarebbe facile da fare.

Risposte:


324

case classviene fornito con un copymetodo dedicato esattamente a questo utilizzo:

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

5
Dove è documentato? Non riesco a trovare un riferimento da copiare nei punti "ovvi", ad esempio scala-lang.org/api/current/index.html .
François Beausoleil,

6
È una caratteristica del linguaggio, puoi trovarlo nella specifica Scala: scala-lang.org/docu/files/ScalaReference.pdf §5.3.2. Non è nell'API perché non fa parte dell'API;)
Nicolas,

1
Intendevo fare in modo che ScalaDoc mostrasse i metodi di copia quando esistono, non è quello che vuoi?
soc

4
Sarebbe bello Ma qui, il problema di François (se ho ragione) è che non sapeva che avrebbe avuto un copymetodo se avesse dichiarato a case class.
Nicolas,

2
@JonathanNeufeld Farai molti amici nel puro campo fp con quel sentimento. Tendo ad essere d'accordo con te.
javadba,

46

A partire da 2.8, le classi di case Scala hanno un copymetodo che sfrutta i parametri nominati / predefiniti per funzionare in modo magico:

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

Puoi anche creare un metodo Personaper semplificare l'utilizzo:

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

poi

val newPersona = existingPersona plusMsg newMsg


0

Prendi lensin considerazione l'utilizzo in Shapelesslibreria:

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))

Inoltre, nel caso in cui tu abbia classi nidificate , i metodi gettere setterpossono essere un po 'noiosi da comporre. Sarà una buona occasione per semplificare utilizzando la libreria di obiettivi.

Si prega di fare riferimento anche a:


0

Non volevo includere una grande libreria per realizzare obiettivi complessi che permettessero di impostare valori profondi nelle classi di case nidificate. Si scopre che ci sono solo poche righe di codice nella libreria scalaz:

  /** 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
  }

È quindi possibile creare obiettivi che impostano valori nidificati molto più semplici rispetto all'utilizzo della funzione di copia integrata. Ecco un link a un grande set di obiettivi complessi che la mia libreria usa per impostare valori fortemente nidificati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.