首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >更新嵌套结构的更干净的方法

更新嵌套结构的更干净的方法
EN

Stack Overflow用户
提问于 2010-10-10 20:37:05
回答 6查看 25.1K关注 0票数 126

假设我有以下两个case classE:

代码语言:javascript
复制
case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)

以及Person类的以下实例:

代码语言:javascript
复制
val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg", 
                                           "Mumbai", 
                                           "Maharashtra", 
                                           411342))

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

代码语言:javascript
复制
val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))

随着嵌套级别的增加,这会变得更加丑陋。有没有一种更干净的方式(类似Clojure的update-in)来更新这种嵌套结构?

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2010-10-10 21:35:38

拉链

Huet's Zipper提供了对不可变数据结构的方便的遍历和“突变”。Scalaz为Stream (scalaz.Zipper)和Tree (scalaz.TreeLoc)提供了拉链。事实证明,拉链的结构可以从原始数据结构自动导出,其方式类似于代数表达式的符号微分。

但是这对您的Scala case类有什么帮助呢?嗯,Lukas Rytz最近prototyped了一个scalac的扩展,它将自动为带注释的case类创建拉链。我将在这里重现他的例子:

代码语言:javascript
复制
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最近published了一个版本的吃豆人,用户可以通过数字用户线编程。看起来他并没有使用修改过的编译器,因为我看不到任何@zip注释。

树重写

在其他情况下,您可能希望根据某种策略(自上而下、自下而上)并基于与结构中某个点的值相匹配的规则,在整个数据结构中应用一些转换。典型的例子是将AST转换为一种语言,可能是为了计算、简化或收集信息。Kiama支持Rewriting,请参阅RewriterTests中的示例,并观看此video。这里有一个片段来刺激你的胃口:

代码语言:javascript
复制
// 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 steps outside类型系统实现了这一点。

票数 95
EN

Stack Overflow用户

发布于 2011-04-09 00:02:17

有趣的是,没有人添加镜头,因为它们是为这种东西制作的。因此,here是一篇CS背景文章,here是一个博客,简要介绍了Scala中镜头的使用,here是Scalaz的镜头实现,here是一些使用它的代码,看起来很像你的问题。此外,为了减少样板文件,可以使用here's插件为case类生成Scalaz镜头。

对于加分,here's另一个涉及镜头的S.O.问题,以及托尼·莫里斯的paper

镜头的重要之处在于它们是可组合的。所以它们一开始有点笨重,但随着你使用它们的次数越来越多,它们就会越来越多。此外,它们对于可测试性也很好,因为你只需要测试单个镜头,并且可以理所当然地认为它们的组成。

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

代码语言:javascript
复制
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))

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

代码语言:javascript
复制
val personZipCodeLens = personAddressLens andThen addressZipCodeLens

最后,用这个镜头改变raj:

代码语言:javascript
复制
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)

或者,使用一些语法糖:

代码语言:javascript
复制
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens(raj) + 1)

或者甚至是:

代码语言:javascript
复制
val updatedRaj = personZipCodeLens.mod(raj, zip => zip + 1)

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

代码语言:javascript
复制
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
}
票数 188
EN

Stack Overflow用户

发布于 2014-06-22 01:56:55

我一直在寻找语法最好、功能最好的Scala库,这里没有提到的一个库是monocle,它对我来说真的很好。下面是一个示例:

代码语言:javascript
复制
import monocle.Macro._
import monocle.syntax._

case class A(s: String)
case class B(a: A)

val aLens = mkLens[B, A]("a")
val sLens = aLens |-> mkLens[A, String]("s")

//Usage
val b = B(A("hi"))
val newB = b |-> sLens set("goodbye") // gives B(A("goodbye"))

这些都是非常好的,有很多方法来组合镜头。以Scalaz为例,它需要大量的样板文件,编译速度快,运行良好。

要在项目中使用它们,只需将以下代码添加到您的依赖项中:

代码语言:javascript
复制
resolvers ++= Seq(
  "Sonatype OSS Releases"  at "http://oss.sonatype.org/content/repositories/releases/",
  "Sonatype OSS Snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/"
)

val scalaVersion   = "2.11.0" // or "2.10.4"
val libraryVersion = "0.4.0"  // or "0.5-SNAPSHOT"

libraryDependencies ++= Seq(
  "com.github.julien-truffaut"  %%  "monocle-core"    % libraryVersion,
  "com.github.julien-truffaut"  %%  "monocle-generic" % libraryVersion,
  "com.github.julien-truffaut"  %%  "monocle-macro"   % libraryVersion,       // since 0.4.0
  "com.github.julien-truffaut"  %%  "monocle-law"     % libraryVersion % test // since 0.4.0
)
票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/3900307

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档