Як клонувати екземпляр класу case та змінити лише одне поле у ​​Scala?


208

Скажімо, у мене є кейс-клас, який представляє персон, людей у ​​різних соціальних мережах. Екземпляри цього класу повністю незмінні і зберігаються у незмінних колекціях, щоб їх згодом змінив актор Акка.

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

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)

Зауважте, що я маю вказати всі поля, навіть не змінюючи лише одне. Чи є спосіб клонувати існуючуПерсону та замінити лише одне поле, не вказуючи всі поля, які не змінюються? Чи можу я записати це як ознаку і використовувати його для всіх моїх кейсів?

Якби Персона був екземпляром, подібним до карти, це було б легко зробити.

Відповіді:


324

case classпоставляється із copyметодом, який присвячений саме цьому використанню:

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

5
Де це задокументовано? Я не можу знайти посилання на копію в "очевидних" місцях, наприклад , scala-lang.org/api/current/index.html .
Франсуа Бозолей

6
Це особливості мови, ви можете знайти її у специфікації Scala: scala-lang.org/docu/files/ScalaReference.pdf §5.3.2. Це не в API, тому що це не частина API;)
Nicolas

1
Я мав намір змусити ScalaDoc показати методи копіювання, коли вони існують, чи не те, що ви хочете?
соц

4
Було б добре. Але тут проблема Франсуа (якщо я правий) полягає в тому, що він не знав, що у нього буде copyметод, якщо він оголосить а case class.
Ніколя

2
@JonathanNeufeld З цим настроєм ти зробиш багато невідомих у чистому таборі fp. Я схильний погоджуватися з вами.
javadba

46

Починаючи з 2.8, класи класів Scala мають copyметод, який використовує параметри з назви / параметри за замовчуванням, щоб виконувати свою магію:

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


0

Подумайте про використання 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 методи методи можуть бути трохи стомлюючими. Це буде хороший шанс спростити використання бібліотеки об'єктивів.

Також зверніться до:


0

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

Потім ви можете створити лінзи, які встановлюють глибоко вкладені значення набагато простіше, ніж використовувати вбудовану функцію копіювання. Ось посилання на великий набір, якщо складні об'єктиви, які моя бібліотека використовує для встановлення сильно вкладених значень.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.