【问题标题】:How to clone a case class instance and change just one field in Scala?如何克隆案例类实例并仅更改 Scala 中的一个字段?
【发布时间】:2011-11-07 03:25:27
【问题描述】:

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

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

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

【问题讨论】:

标签: scala


【解决方案1】:

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 阵营中结交很多朋友。我倾向于同意你的观点。
【解决方案2】:

从 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

【讨论】:

    【解决方案3】:
    existingPersona.copy(sentMessages = existingPersona.sentMessages + newMessage)
    

    【讨论】:

      【解决方案4】:

      考虑在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 方法的编写可能有点乏味。这将是使用镜头库进行简化的好机会。

      另请参考:

      【讨论】:

        【解决方案5】:

        我不想包含一个大型库来执行复杂的镜头,让您在嵌套案例类中深度设置值。原来只是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
          }
        

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

        【讨论】:

          猜你喜欢
          • 2013-12-05
          • 2017-11-24
          • 1970-01-01
          • 1970-01-01
          • 2017-05-19
          • 1970-01-01
          • 1970-01-01
          • 2016-06-24
          • 2021-09-23
          相关资源
          最近更新 更多