ケースクラスのインスタンスを複製し、Scalaの1つのフィールドのみを変更する方法は?


208

ペルソナ、さまざまなソーシャルネットワークの人々を表すケースクラスがあるとします。そのクラスのインスタンスは完全に不変であり、不変のコレクションに保持され、最終的にAkkaアクターによって変更されます。

これで、多くのフィールドを持つケースクラスがあり、次のようなフィールドの1つを更新する必要があることを示すメッセージが表示されます。

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)

変更するのは1つだけですが、すべてのフィールドを指定する必要があることに注意してください。変更されないすべてのフィールドを指定せずに、existingPersonaを複製して1つのフィールドのみを置き換える方法はありますか?これを特性として記述して、すべてのケースクラスに使用できますか?

ペルソナがマップのようなインスタンスである場合、それを行うのは簡単です。

回答:


324

case classcopyこの使用法に特化したメソッドが付属しています:

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

5
それはどこに文書化されていますか?「明白な」スポット、たとえばscala-lang.org/api/current/index.htmlにコピーする参照が見つかりません。
フランソワボーソレイユ2011

6
:それは、あなたがScalaの仕様でそれを見つけることができる言語の機能ですscala-lang.org/docu/files/ScalaReference.pdfを §5.3.2。APIの一部ではないため、APIには含まれていません;)
Nicolas

1
ScalaDocにコピーメソッドが存在する場合にそれらを表示させるつもりでしたが、そうしたいですか。
soc '31

4
いいですね。しかし、ここで、フランソワの問題(私が正しい場合)は、彼がcopyを宣言した場合にメソッドがあることを彼が知らなかったことですcase class
ニコラ

2
@JonathanNeufeldあなたはその感情で純粋なFPキャンプで多くの友達を作るでしょう。私はあなたに同意する傾向があります。
javadba

46

2.8以降、Scalaケースクラスには、copynamed / default paramsを利用してその魔法を機能させるメソッドがあります。

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

さらに、入れ子の caseクラスがある場合、getterand 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
  }

次に、組み込みのコピー機能を使用するよりもはるかに簡単に、深くネストされた値を設定するレンズを作成できます。これは、私のライブラリが非常にネストされた値を設定するために使用する複雑なレンズの場合、大きなセットへのリンクです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.