scala - 更新嵌套结构的更简洁的方法

标签 scala case-class zipper

假设我有以下两个案例类:

case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)

以及以下 Person 类的实例:

val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg", 
                                           "Mumbai", 
                                           "Maharashtra", 
                                           411342))

现在,如果我想更新 rajzipCode 那么我必须这样做:

val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))

随着嵌套级别的增加,这会变得更加难看。是否有更简洁的方法(例如 Clojure 的 update-in)来更新此类嵌套结构?

最佳答案

有趣的是,没有人添加镜头,因为它们是为这种东西制造的。所以,here 是一篇关于它的 CS 背景论文,here 是一篇博客,简要介绍了 Scala 中镜头的使用,here 是 Scalaz 的镜头实现,here 是一些使用它的代码,这看起来与你的问题非常相似。而且,为了减少样板,here's 是一个为案例类生成 Scalaz 镜头的插件。

要获得奖励积分,here's 另一个 S.O.涉及镜头的问题,以及 Tony Morris 的 paper

镜头的重要之处在于它们是可组合的。因此,它们一开始有点麻烦,但随着您使用它们的次数增多,它们会越来越受欢迎。此外,它们非常适合可测试性,因为您只需要测试单个镜头,并且可以理所当然地认为它们的组成。

因此,根据本答案末尾提供的实现,您可以使用以下镜头来实现这一点。首先,声明 Lens 来更改地址中的邮政编码和人员中的地址:

val addressZipCodeLens = Lens(
    get = (_: Address).zipCode,
    set = (addr: Address, zipCode: Int) => addr.copy(zipCode = zipCode))

val personAddressLens = Lens(
    get = (_: Person).address, 
    set = (p: Person, addr: Address) => p.copy(address = addr))

现在,将它们组合起来,得到一个可以改变一个人的邮政编码的镜头:

val personZipCodeLens = personAddressLens andThen addressZipCodeLens

最后,使用该镜头来更改 raj:

val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)

或者,使用一些语法糖:

val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens(raj) + 1)

或者甚至:

val updatedRaj = personZipCodeLens.mod(raj, zip => zip + 1)

以下是取自 Scalaz 的简单实现,用于本示例:

case class Lens[A,B](get: A => B, set: (A,B) => A) extends Function1[A,B] with Immutable {
  def apply(whole: A): B   = get(whole)
  def updated(whole: A, part: B): A = set(whole, part) // like on immutable maps
  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
}

关于scala - 更新嵌套结构的更简洁的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3900307/

相关文章:

scala - 如何在 Play ScalaTest 上生成 HTML 报告

scala - 这个名为 : new C { i = 5 }//there's a block after the new 的 Scala 语法是什么

scala - 从 EventStream 创建源

scala - 要在 Scala 中映射的案例类

scala - Scala 中的多个 Trait 构造错误

scala - 自动散列 Consed 案例类

arrays - 仅将函数应用于Scala中列表或数组中的一个元素

抑制 TRIE 的嵌套 Maps 的 Clojure Zipper

haskell - 都是可区分的类型 Monad

scala - 更改 Scala 案例类树中的节点