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

Resuelto François Beausoleil asked hace 13 años • 5 respuestas

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, que eventualmente serán modificadas por un actor 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)

Observe que tengo que especificar todos los campos, aunque solo cambie uno. ¿Hay alguna manera de clonar una 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 avatar Aug 31 '11 03:08 François Beausoleil
Aceptado

case classviene con un copymétodo que está dedicado exactamente a este uso:

val newPersona = existingPersona.copy(sentMessages = 
                   existingPersona.sentMessages + newMessage)
Nicolas avatar Aug 30 '2011 20:08 Nicolas

Desde 2.8, las clases de casos de 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)
}

entonces

val newPersona = existingPersona plusMsg newMsg
Kevin Wright avatar Aug 30 '2011 20:08 Kevin Wright
existingPersona.copy(sentMessages = existingPersona.sentMessages + newMessage)
Jean-Philippe Pellet avatar Aug 30 '2011 20:08 Jean-Philippe Pellet

Considere usar lensen Shapelessla biblioteca:

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 tenga clases de casos anidadas , los métodos gettery setterpueden ser un poco tediosos de componer. Será una buena oportunidad para simplificar utilizando la biblioteca de lentes.

Consulte también:

  • Lentes sin forma Github / Boilerplate-free para clases de casos arbitrarios
  • Github de lentes rápidas
  • Lente en escala
Kaihua avatar Mar 20 '2019 15:03 Kaihua