ChatGPT解决这个技术问题 Extra ChatGPT

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

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

现在,我有一个包含许多字段的案例类,我收到一条消息说我必须更新其中一个字段,如下所示:

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 的实例,那将很容易做到。


N
Nicolas

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

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

这是在哪里记录的?例如,我在“明显”的地方找不到复制的参考,scala-lang.org/api/current/index.html
这是该语言的一个特性,您可以在 Scala 规范中找到它:scala-lang.org/docu/files/ScalaReference.pdf §5.3.2。它不在 API 中,因为它不是 API 的一部分;)
我打算让 ScalaDoc 在它们存在时显示复制方法,这不是你想要的吗?
这会很好。但在这里,François 的问题(如果我是对的)是他不知道如果他声明一个 case class,他将有一个 copy 方法。
@JonathanNeufeld 你会带着这种情绪在纯 fp 阵营中结交很多朋友。我倾向于同意你的观点。
K
Kevin Wright

从 2.8 开始,Scala 案例类有一个 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

J
Jean-Philippe Pellet
existingPersona.copy(sentMessages = existingPersona.sentMessages + newMessage)

K
Kaihua

考虑在 Shapeless 库中使用 lens

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 方法的编写可能有点乏味。这将是一个很好的机会通过使用镜头库来简化。

另请参考:

适用于任意案例类别的无形 Github / 无样板镜头

快速镜头 Github

scala中的镜头


s
simbo1905

我不想包含一个大型库来做复杂的镜头,让您在嵌套案例类中设置值。原来它只是 scalaz 库中的几行 code

  /** 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
  }

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅