Bagaimana cara mengkloning instance kelas kasus dan mengubah hanya satu bidang di Scala?

208

Katakanlah saya memiliki kelas kasus yang mewakili kepribadian, orang-orang di jejaring sosial yang berbeda. Contoh-contoh dari kelas tersebut sepenuhnya tidak dapat diubah, dan disimpan dalam koleksi yang tidak dapat diubah, untuk akhirnya dimodifikasi oleh aktor Akka.

Sekarang, saya memiliki kelas kasus dengan banyak bidang, dan saya menerima pesan yang mengatakan saya harus memperbarui salah satu bidang, seperti ini:

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)

Perhatikan saya harus menentukan semua bidang, meskipun hanya satu perubahan. Apakah ada cara untuk mengkloningPersona yang ada dan mengganti hanya satu bidang, tanpa menentukan semua bidang yang tidak berubah? Bisakah saya menulis itu sebagai sifat dan menggunakannya untuk semua kelas kasus saya?

Jika Persona adalah contoh mirip Peta, itu akan mudah dilakukan.

François Beausoleil
sumber

Jawaban:

324

case classdilengkapi dengan copymetode yang didedikasikan tepat untuk penggunaan ini:

val newPersona = existingPersona.copy(sentMessages = 
                   existingPersona.sentMessages + newMessage)
Nicolas
sumber
5
Di mana itu didokumentasikan? Saya tidak dapat menemukan referensi untuk menyalin di tempat-tempat "jelas", scala-lang.org/api/current/index.html misalnya.
François Beausoleil
6
Ini adalah fitur bahasa, Anda dapat menemukannya dalam spesifikasi Scala: scala-lang.org/docu/files/ScalaReference.pdf §5.3.2. Itu tidak ada di API karena itu bukan bagian dari API;)
Nicolas
1
Saya bermaksud membuat ScalaDoc menunjukkan metode penyalinan ketika sudah ada, bukankah itu yang Anda inginkan?
soc
4
Itu akan menyenangkan. Tapi di sini, masalah François (jika saya benar) adalah bahwa dia tidak tahu bahwa dia akan memiliki copymetode jika dia menyatakan a case class.
Nicolas
2
@JonathanNeufeld Anda akan membuat banyak teman di kamp fp murni dengan sentimen itu. Saya cenderung setuju dengan Anda.
javadba
46

Sejak 2.8, kelas kasus Scala memiliki copymetode yang memanfaatkan param bernama / default untuk mengerjakan keajaibannya:

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

Anda juga dapat membuat metode Personauntuk menyederhanakan penggunaan:

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

kemudian

val newPersona = existingPersona plusMsg newMsg
Kevin Wright
sumber
10
existingPersona.copy(sentMessages = existingPersona.sentMessages + newMessage)
Jean-Philippe Pellet
sumber
0

Pertimbangkan menggunakan lensdi Shapelessperpustakaan:

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

Selain itu, jika Anda memiliki kelas kasus bersarang , getterdan settermetode dapat sedikit membosankan untuk menulis. Ini akan menjadi peluang bagus untuk disederhanakan dengan menggunakan perpustakaan lensa.

Silakan juga merujuk ke:

Kaihua
sumber
0

Saya tidak ingin menyertakan perpustakaan besar untuk melakukan lensa rumit yang memungkinkan Anda mengatur nilai jauh di kelas kasus bersarang. Ternyata itu hanya beberapa baris kode di perpustakaan 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
  }

Anda kemudian dapat membuat lensa yang menetapkan nilai bersarang jauh lebih mudah daripada menggunakan fitur salin bawaan. Berikut adalah tautan ke set besar jika lensa rumit yang digunakan perpustakaan saya untuk menetapkan nilai yang sangat bersarang.

simbo1905
sumber