Jak sklonować instancję klasy sprawy i zmienić tylko jedno pole w Scali?

208

Powiedzmy, że mam klasę przypadków, która reprezentuje osobowości, ludzi z różnych sieci społecznościowych. Instancje tej klasy są w pełni niezmienne i są przechowywane w niezmiennych kolekcjach, które ostatecznie zostaną zmodyfikowane przez aktora Akka.

Teraz mam klasę przypadków z wieloma polami i otrzymuję komunikat, że muszę zaktualizować jedno z pól, coś takiego:

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)

Zauważ, że muszę określić wszystkie pola, chociaż tylko jedna zmiana. Czy istnieje sposób na sklonowanie istniejącej Persony i zastąpienie tylko jednego pola bez określania wszystkich pól, które się nie zmieniają? Czy mogę napisać to jako cechę i używać jej we wszystkich moich klasach przypadków?

Gdyby Persona była instancją podobną do mapy, byłoby to łatwe.

François Beausoleil
źródło

Odpowiedzi:

324

case classpochodzi z copymetody dedykowanej właśnie do tego zastosowania:

val newPersona = existingPersona.copy(sentMessages = 
                   existingPersona.sentMessages + newMessage)
Nicolas
źródło
5
Gdzie to jest udokumentowane? Nie mogę znaleźć odniesienia do kopiowania w „oczywistych” miejscach, na przykład scala-lang.org/api/current/index.html .
François Beausoleil
6
Jest to cecha języka, którą można znaleźć w specyfikacji Scali: scala-lang.org/docu/files/ScalaReference.pdf §5.3.2. Nie ma go w interfejsie API, ponieważ nie jest częścią interfejsu API;)
Nicolas
1
Chciałem, aby ScalaDoc wyświetlał metody kopiowania, gdy istnieją, czyż nie tego chcesz?
soc
4
Byłoby miło. Ale tutaj problemem François (jeśli mam rację) jest to, że nie wiedział, że będzie miał copymetodę, jeśli zadeklaruje case class.
Nicolas
2
@JonathanNeufeld Z tym sentymentem spotkasz wielu nieprzyjaznych w obozie czysto fp. Zwykle się z tobą zgadzam.
javadba
46

Od wersji 2.8 klasy przypadków Scala mają copymetodę, która wykorzystuje parametry nazwane / domyślne do działania swojej magii:

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

Możesz także utworzyć metodę, Personaaby uprościć korzystanie:

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

następnie

val newPersona = existingPersona plusMsg newMsg
Kevin Wright
źródło
10
existingPersona.copy(sentMessages = existingPersona.sentMessages + newMessage)
Jean-Philippe Pellet
źródło
0

Rozważ użycie lensw Shapelessbibliotece:

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

Ponadto, w przypadku zagnieżdżenia klas spraw, metody getteri settermogą być nieco uciążliwe w komponowaniu. Będzie to dobra okazja do uproszczenia za pomocą biblioteki obiektywów.

Zobacz także:

Kaihua
źródło
0

Nie chciałem dołączać dużej biblioteki do wykonywania złożonych soczewek, które pozwalają ci ustawić wartości głęboko w zagnieżdżonych klasach przypadków. Okazuje się, że to tylko kilka wierszy kodu w bibliotece 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
  }

Następnie możesz utworzyć soczewki, które ustawiają wartości głęboko zagnieżdżone o wiele łatwiej niż przy użyciu wbudowanej funkcji kopiowania. Oto link do dużego zestawu, jeśli złożone soczewki używane przez moją bibliotekę do ustawiania mocno zagnieżdżonych wartości.

simbo1905
źródło