如何克隆案例类实例并仅在Scala中更改一个字段?


208

假设我有一个案例类,代表不同的社交网络上的人物角色。该类的实例是完全不可变的,并保存在不可变的集合中,最终由Akka演员对其进行修改。

现在,我有一个包含许多字段的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)

请注意,即使只有一个更改,我也必须指定所有字段。有没有一种方法可以克隆existingPersona并仅替换一个字段,而无需指定所有不变的字段?我可以将其写为特征并将其用于所有案例类吗?

如果Persona是类似Map的实例,则很容易做到。

Answers:


324

case class附带一种copy专门用于此用法的方法:

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

5
在哪里记录?我找不到要复制的参考资料,例如scala-lang.org/api/current/index.html
弗朗索瓦·博索莱尔

6
这是语言的功能,可以在斯卡拉规格找到它:scala-lang.org/docu/files/ScalaReference.pdf第5.3.2节。它不在API中,因为它不是API的一部分;)
Nicolas

1
我打算让ScalaDoc在存在复制方法时显示它们,这不是您想要的吗?
SOC

4
很好。但是在这里,弗朗索瓦(François)的问题(如果我是对的话)是,他不知道copy如果声明一个,他将拥有一个方法case class
尼古拉斯·

2
@JonathanNeufeld您将在这种情绪下的纯正fp阵营中结交许多不友善的人。我倾向于同意你的看法。
javadba

46

从2.8开始,Scala case类具有一种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

考虑lensShapeless库中使用:

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

此外,如果您嵌套了案例类,则gettersetter方法的编写可能会有些乏味。这是通过使用镜头库进行简化的好机会。

另请参阅:


0

我不想包含一个大型库来处理复杂的镜头,而这些镜头却使您无法在嵌套的case类中设置值。事实证明,这只是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
  }

然后,您可以创建设置深层嵌套值的镜头,比使用内置复制功能要容易得多。如果我的库用来设置大量嵌套值的复杂镜头,这是一个大集合的链接。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.