mis*_*tor 122 scala zipper case-class
说我有以下两个case class
es:
case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)
Run Code Online (Sandbox Code Playgroud)
和以下Person
类的实例:
val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg",
"Mumbai",
"Maharashtra",
411342))
Run Code Online (Sandbox Code Playgroud)
现在,如果我想更新zipCode
,raj
那么我将不得不做:
val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))
Run Code Online (Sandbox Code Playgroud)
随着嵌套水平的提高,这将变得更加丑陋.是否有更清洁的方式(像Clojure的东西update-in
)来更新这样的嵌套结构?
Dan*_*ral 185
有趣的是,没有人添加镜头,因为他们是为这种东西制作的.所以,这里有一篇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))
Run Code Online (Sandbox Code Playgroud)
现在,组合它们以获得一个改变一个人的zipcode的镜头:
val personZipCodeLens = personAddressLens andThen addressZipCodeLens
Run Code Online (Sandbox Code Playgroud)
最后,使用那个镜头来改变raj:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)
Run Code Online (Sandbox Code Playgroud)
或者,使用一些语法糖:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens(raj) + 1)
Run Code Online (Sandbox Code Playgroud)
甚至:
val updatedRaj = personZipCodeLens.mod(raj, zip => zip + 1)
Run Code Online (Sandbox Code Playgroud)
以下是从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
}
Run Code Online (Sandbox Code Playgroud)
ret*_*nym 94
Huet的Zipper为不可变数据结构提供了方便的遍历和"变异".Scalaz为Stream
(scalaz.Zipper)和Tree
(scalaz.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)
Run Code Online (Sandbox Code Playgroud)
因此,社区需要说服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))
Run Code Online (Sandbox Code Playgroud)
请注意,Kiama会在类型系统之外执行此操作.
Seb*_*ber 11
使用镜头的有用工具:
只想补充一点,基于Scala 2.10宏的Macrocosm和Rillit项目提供动态镜头创建.
使用Rillit:
case class Email(user: String, domain: String)
case class Contact(email: Email, web: String)
case class Person(name: String, contact: Contact)
val person = Person(
name = "Aki Saarinen",
contact = Contact(
email = Email("aki", "akisaarinen.fi"),
web = "http://akisaarinen.fi"
)
)
scala> Lenser[Person].contact.email.user.set(person, "john")
res1: Person = Person(Aki Saarinen,Contact(Email(john,akisaarinen.fi),http://akisaarinen.fi))
Run Code Online (Sandbox Code Playgroud)
使用Macrocosm:
这甚至适用于当前编译运行中定义的案例类.
case class Person(name: String, age: Int)
val p = Person("brett", 21)
scala> lens[Person].name._1(p)
res1: String = brett
scala> lens[Person].name._2(p, "bill")
res2: Person = Person(bill,21)
scala> lens[Person].namexx(()) // Compilation error
Run Code Online (Sandbox Code Playgroud)
我一直在寻找具有最好的语法和最好的功能的Scala库,这里没有提到的一个库是单片机,这对我来说非常好.一个例子如下:
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"))
Run Code Online (Sandbox Code Playgroud)
这些非常好,有许多方法可以组合镜头.例如Scalaz需要很多样板,并且编译速度快且运行良好.
要在项目中使用它们,只需将其添加到依赖项:
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
)
Run Code Online (Sandbox Code Playgroud)
由于它们的可组合性,镜头为重度嵌套结构的问题提供了非常好的解决方案.然而,由于嵌套水平较低,我有时觉得镜头太多了,如果只有很少的地方有嵌套更新,我不想介绍整个镜头方法.为了完整起见,这是一个非常简单/实用的解决方案:
我所做的只是modify...
在顶层结构中编写一些辅助函数,它处理丑陋的嵌套副本.例如:
case class Person(firstName: String, lastName: String, address: Address) {
def modifyZipCode(modifier: Int => Int) =
this.copy(address = address.copy(zipCode = modifier(address.zipCode)))
}
Run Code Online (Sandbox Code Playgroud)
我的主要目标(简化客户端更新)是:
val updatedRaj = raj.modifyZipCode(_ => 41).modifyZipCode(_ + 1)
Run Code Online (Sandbox Code Playgroud)
创建完整的修改帮助程序显然很烦人.但是对于内部东西,通常可以在第一次尝试修改某个嵌套字段时创建它们.
无形无能为力:
"com.chuusai" % "shapeless_2.11" % "2.0.0"
Run Code Online (Sandbox Code Playgroud)
有:
case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)
object LensSpec {
import shapeless._
val zipLens = lens[Person] >> 'address >> 'zipCode
val surnameLens = lens[Person] >> 'firstName
val surnameZipLens = surnameLens ~ zipLens
}
class LensSpec extends WordSpecLike with Matchers {
import LensSpec._
"Shapless Lens" should {
"do the trick" in {
// given some values to recreate
val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg",
"Mumbai",
"Maharashtra",
411342))
val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))
// when we use a lens
val lensUpdatedRaj = zipLens.set(raj)(raj.address.zipCode + 1)
// then it matches the explicit copy
assert(lensUpdatedRaj == updatedRaj)
}
"better yet chain them together as a template of values to set" in {
// given some values to recreate
val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg",
"Mumbai",
"Maharashtra",
411342))
val updatedRaj = raj.copy(firstName="Rajendra", address = raj.address.copy(zipCode = raj.address.zipCode + 1))
// when we use a compound lens
val lensUpdatedRaj = surnameZipLens.set(raj)("Rajendra", raj.address.zipCode+1)
// then it matches the explicit copy
assert(lensUpdatedRaj == updatedRaj)
}
}
}
Run Code Online (Sandbox Code Playgroud)
需要注意的是,尽管这里的一些其他的答案,让您撰写的镜头去深入到给定结构,这些shapless镜头(和其他库/宏)让你把两个不相干的镜头,这样你可以让镜头的参数集的任意数量为任意位置在你的结构中.对于复杂的数据结构,额外的组合非常有用.
使用QuickLens:
val updatedRaj = raj.modify(_.address.zipCode).using(_ + 1)
Run Code Online (Sandbox Code Playgroud)
适用于 scala 2.11+ 和 3+。
也许QuickLens更符合您的问题。QuickLens 使用宏将 IDE 友好的表达式转换为接近原始复制语句的内容。
给出两个示例案例类:
case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)
Run Code Online (Sandbox Code Playgroud)
和 Person 类的实例:
val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg",
"Mumbai",
"Maharashtra",
411342))
Run Code Online (Sandbox Code Playgroud)
您可以使用以下命令更新 raj 的 zipCode:
import com.softwaremill.quicklens._
val updatedRaj = raj.modify(_.address.zipCode).using(_ + 1)
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
22764 次 |
最近记录: |