为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

更新嵌套结构的更清洁方法说我有以下两个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))现在,如果我想更新zipCode,raj那么我将不得不做:val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))随着嵌套水平的提高,这将变得更加丑陋。是否有更清洁的方式(像Clojure的东西update-in)来更新这样的嵌套结构?
查看完整描述

3 回答

?
MYYA

TA贡献1868条经验 获得超4个赞

拉链

Huet的Zipper为不可变数据结构提供了方便的遍历和“变异”。Scalaz为Streamscalaz.Zipper)和Treescalaz.TreeLoc)提供Zippers 。事实证明,拉链的结构可以自动地从原始数据结构中导出,其方式类似于代数表达式的符号区分。

但是,这对您的Scala案例类有何帮助?好吧,Lukas Rytz最近制作了一个scalac扩展原型,可以自动为带注释的案例类创建拉链。我将在这里重现他的例子:

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团队,这项工作应该继续并集成到编译器中。


顺便提一下,Lukas最近发布了Pacman版本,用户可以通过DSL进行编程。但是,看起来他没有使用修改过的编译器,因为我看不到任何@zip注释。


树重写

在其他情况下,您可能希望在整个数据结构中应用一些转换,根据某种策略(自上而下,自下而上),并基于与结构中某个点的值匹配的规则。经典的例子是为一种语言转换AST,也许是为了评估,简化或收集信息。Kiama支持重写,请参阅RewriterTests中的示例,并观看此视频。这是一个激发你胃口的片段:


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

请注意,Kiama会在类型系统之外执行此操作。


查看完整回答
反对 回复 2019-08-15
?
守候你守候我

TA贡献1802条经验 获得超10个赞

有趣的是,没有人添加镜头,因为他们是为这种东西制作的。所以,这里有一篇CS背景文件,这里有一篇博客,简要介绍了Scala中使用的镜头,这里是Scalaz的镜头实现,这里有一些使用它的代码,看起来非常像你的问题。而且,为了减少锅炉板,这是一个为案例类生成Scalaz镜头的插件。


对于奖励积分,这是另一个涉及镜头的SO问题,以及Tony Morris 的论文。


镜头的重要性在于它们是可组合的。所以一开始它们有点麻烦,但是你使用它们的次数越来越多。此外,它们非常适合测试,因为您只需要测试单个镜头,并且可以理所当然地将它们的成分视为理所当然。


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


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

现在,组合它们以获得一个改变一个人的zipcode的镜头:


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

}


查看完整回答
反对 回复 2019-08-15
  • 3 回答
  • 0 关注
  • 436 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信