Как клонировать экземпляр класса case и изменять только одно поле в Scala?

Скажем, у меня есть класс case, который представляет персонажи, людей в разных социальных сетях. Экземпляры этого класса полностью неизменяемы и хранятся в неизменных коллекциях, которые в конечном итоге будут изменены аккой Аккой.

Теперь у меня есть класс case со многими полями, и я получаю сообщение, в котором говорится, что я должен обновить одно из полей, примерно так:

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)

Заметьте, что я должен указать все поля, хотя только один изменяется. Есть ли способ клонировать existingPersona и заменять только одно поле без указания всех полей, которые не изменяются? Могу ли я написать это как свойство и использовать его для всех классов классов?

Если Persona был экземпляром, похожим на карту, это было бы легко сделать.

Ответ 1

case class поставляется с методом copy, который предназначен именно для этого использования:

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

Ответ 2

Начиная с 2.8, Scala классы case имеют метод copy, который использует свойства named/default для работы своей магией:

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

Вы также можете создать метод на Persona, чтобы упростить использование:

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

затем

val newPersona = existingPersona plusMsg newMsg

Ответ 3

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

Ответ 4

Рассмотрите возможность использования lens в библиотеке Shapeless:

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

Более того, если у вас есть вложенные классы падежей, методы getter и setter могут быть немного утомительны при составлении. Это будет хороший шанс упростить использование библиотеки линз.

Пожалуйста, также обратитесь к:

Ответ 5

Я не хотел включать большую библиотеку для создания сложных линз, которые позволят вам устанавливать значения глубоко во вложенных классах дел. Оказывается, это всего лишь несколько строк кода в библиотеке 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
  }

Затем вы можете создавать линзы, которые устанавливают глубоко вложенные значения гораздо проще, чем с помощью встроенной функции копирования. Вот ссылка на большой набор сложных линз, которые моя библиотека использует для установки сильно вложенных значений.