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

208

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

Теперь у меня есть класс 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)

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

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

Франсуа Босолей
источник

Ответы:

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;)
Николас
1
Я намеревался заставить ScalaDoc показывать методы копирования, когда они существуют, разве это не то, что вы хотите?
общ
4
Это было бы хорошо. Но здесь проблема Франсуа (если я прав) состоит в том, что он не знал, что у него будет copyметод, если он объявит case class.
Николас
2
@JonathanNeufeld С этим настроением вы будете делать много друзей в чистом fp лагере. Я склонен согласиться с вами.
Джавадба
46

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

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
Кевин Райт
источник
10
existingPersona.copy(sentMessages = existingPersona.sentMessages + newMessage)
Жан-Филипп Пелле
источник
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методы могут быть немного утомительно композ. Это будет хороший шанс упростить использование библиотеки линз.

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

Kaihua
источник
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
  }

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

simbo1905
источник