¿Cómo clonar una instancia de clase de caso y cambiar solo un campo en Scala?

208

Digamos que tengo una clase de caso que representa personas, personas en diferentes redes sociales. Las instancias de esa clase son completamente inmutables, y se mantienen en colecciones inmutables, para ser eventualmente modificadas por un actor de Akka.

Ahora, tengo una clase de caso con muchos campos, y recibo un mensaje que dice que debo actualizar uno de los campos, algo como esto:

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)

Tenga en cuenta que tengo que especificar todos los campos, aunque solo uno cambie. ¿Hay alguna manera de clonar la persona existente y reemplazar solo un campo, sin especificar todos los campos que no cambian? ¿Puedo escribir eso como un rasgo y usarlo para todas mis clases de casos?

Si Persona fuera una instancia similar a un mapa, sería fácil de hacer.

François Beausoleil
fuente

Respuestas:

324

case classviene con un copymétodo que se dedica exactamente a este uso:

val newPersona = existingPersona.copy(sentMessages = 
                   existingPersona.sentMessages + newMessage)
Nicolas
fuente
55
¿Dónde está documentado eso? No puedo encontrar una referencia para copiar en los puntos "obvios", scala-lang.org/api/current/index.html por ejemplo.
François Beausoleil
66
Es una característica del lenguaje, puede encontrarlo en la especificación de Scala: scala-lang.org/docu/files/ScalaReference.pdf §5.3.2. No está en la API porque no es parte de la API;)
Nicolas
1
Tenía la intención de hacer que ScalaDoc mostrara los métodos de copia cuando existen, ¿no es eso lo que quieres?
soc
44
Estaría bien Pero aquí, el problema de François (si tengo razón) es que él no sabía que tendrá un copymétodo si declara a case class.
Nicolas
2
@JonathanNeufeld Con ese sentimiento harás muchos amigos en el campo puro de fp. Tiendo a estar de acuerdo contigo.
javadba
46

Desde 2.8, las clases de caso Scala tienen un copymétodo que aprovecha los parámetros con nombre / predeterminados para hacer su magia:

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

También puede crear un método Personapara simplificar el uso:

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

luego

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

Considere usar lensen la Shapelessbiblioteca:

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

Además, en caso de que haya clases de casos anidadas , los métodos gettery setterpueden ser un poco tediosos para componer. Será una buena oportunidad para simplificar mediante el uso de la biblioteca de lentes.

Por favor, consulte también:

Kaihua
fuente
0

No quería incluir una gran biblioteca para hacer lentes complejos que le permitieran establecer valores profundos en las clases de casos anidados. Resulta que son solo unas pocas líneas de código en la biblioteca 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
  }

Luego puede crear lentes que establezcan valores profundamente anidados mucho más fácilmente que usar la función de copia integrada. Aquí hay un enlace a un gran conjunto de lentes complejas que mi biblioteca utiliza para establecer valores muy anidados.

simbo1905
fuente