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

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (54)

假设我有两个case classES:

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

现在如果我想更新zipCoderaj那我就得做:

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

随着嵌套级别的增加,这种情况变得更加丑陋。有没有更干净的方法update-in)更新这些嵌套结构?

提问于
用户回答回答于

scala> @zip case class Pacman(lives: Int = 3, superMode: Boolean = false) 
scala> @zip case class Game(state: String = "pause", pacman: Pacman = Pacman()) 
scala> val g = Game() 
g: Game = Game("pause",Pacman(3,false))

// Changing the game state to "run" is simple using the copy method:
scala> val g1 = g.copy(state = "run") 
g1: Game = Game("run",Pacman(3,false))

// However, changing pacman's super mode is much more cumbersome (and it gets worse for deeper structures):
scala> val g2 = g1.copy(pacman = g1.pacman.copy(superMode = true))
g2: Game = Game("run",Pacman(3,true))

// Using the compiler-generated location classes this gets much easier: 
scala> val g3 = g1.loc.pacman.superMode set true
g3: Game = Game("run",Pacman(3,true)

因此,社区需要说服Scala团队继续这种努力,并将其集成到编译器中。

树重写

在其他情况下,可能希望根据某种策略并基于与结构中某个点的值匹配的规则,对整个数据结构应用一些转换。经典的例子是转换语言的AST,可能是为了评估、简化或收集信息。

// Test expression
val e = Mul (Num (1), Add (Sub (Var ("hello"), Num (2)), Var ("harold")))

// Increment every double
val incint = everywheretd (rule { case d : Double => d + 1 })
val r1 = Mul (Num (2), Add (Sub (Var ("hello"), Num (3)), Var ("harold")))
expect (r1) (rewrite (incint) (e))
用户回答回答于

因此,根据这个答案末尾提供的一个实现,下面是如何使用镜头来实现的。首先,声明镜头以更改地址中的邮政编码和个人地址:

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
}

扫码关注云+社区