假设我有两个case class
:
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))
现在,如果我想更新raj
的zipCode
,那么我必须执行以下操作:
val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))
有没有更干净的方法(比如Clojure的update-in
)来更新这样的嵌套结构?
7条答案
按热度按时间fkvaft9z1#
有趣的是,没有人添加镜头,因为它们是为这类东西而设计的。所以,here是一份关于它的CS背景文件,here是一个博客,简要介绍了Scala中镜头的使用,here是Scalaz的一个镜头实现,here是一些使用它的代码,看起来和你的问题非常相似。而且,为了减少锅炉板,here's一个为case类生成Scalaz镜头插件。
对于加分,here's另一个涉及镜头的S.O.问题,和托尼莫里斯的paper。
镜头最重要的一点是,它们是可组合的。所以刚开始它们有点笨重,但随着使用次数的增加,它们会越来越受欢迎。此外,它们还具有很好的可测试性,因为你只需要测试单个镜头,就可以理所当然地使用它们的组合。
因此,根据本答案末尾提供的实现,下面是如何使用lens执行此操作的方法:首先,声明lens以更改地址中的邮政编码和人员中的地址:
现在,把它们组合成一个透镜,改变一个人的邮政编码:
最后,用那个透镜来改变Raj:
或者,使用一些语法糖:
或者甚至:
下面是一个简单的实现,取自Scalaz,用于本例:
nxagd54h2#
拉链
Huet's Zipper提供了对不变数据结构的方便的遍历和“变异”。Scalaz提供了
Stream
(scalaz.Zipper)和Tree
(scalaz.TreeLoc)的拉链。结果表明,拉链的结构可以自动从原始数据结构中导出,其方式类似于代数表达式的符号微分。但是这对你的Scala case类有什么帮助呢?Lukas Rytz最近对scalac进行了prototyped扩展,它可以自动为带注解的case类创建拉链。我将在这里重现他的例子:
因此,社区需要说服Scala团队继续进行这项工作,并将其集成到编译器中。
顺便说一句,Lukas最近开发了一个Pacman版本的published,用户可以通过DSL编程。不过,看起来他不像是使用了修改过的编译器,因为我看不到任何
@zip
注解。树重写
在其他情况下,您可能希望根据某种策略对整个数据结构应用某种转换(自上而下,自下而上),并基于与结构中某个点的值匹配的规则。经典的例子是为一种语言转换AST,可能是为了计算、简化或收集信息。Kiama支持Rewriting,请参阅RewriterTests中的示例。看看这个video。这里有一个片段可以激起你的食欲:
请注意,Kiama在类型系统之外实现了这一点。
d6kp6zgx3#
使用镜头的有用工具:
我只想补充一点,基于Scala 2.10宏的Macrocosm和Rillit项目提供了动态透镜创建。
使用Rillit:
使用宏观世界:
这甚至适用于在当前编译运行中定义的case类。
vq8itlhq4#
我一直在寻找语法最好、功能最好的Scala库,这里没有提到的一个库是monocle,它对我来说非常好。下面是一个例子:
这些都是非常好的,有很多方法来合并镜头。例如,Scalaz需要很多样板,这编译快速,运行很好。
要在项目中使用它们,只需将以下内容添加到依赖项中:
lndjwyie5#
“无形”就能做到这一点:
与:
请注意,虽然这里的一些其他答案可以让你组合镜头更深入到一个给定的结构中,但这些无形状的镜头(和其他库/宏)可以让你合并两个不相关的镜头,这样你就可以在你的结构中的任意位置设置任意数量的参数。对于复杂的数据结构,额外的组合是非常有帮助的。
oiopk7p56#
由于它们的可组合性,lens为嵌套结构过多的问题提供了一个非常好的解决方案。但是,对于嵌套程度较低的结构,我有时会觉得lens有点太多了,如果只有很少的地方有嵌套更新,我不想介绍整个lens方法。为了完整起见,这里有一个非常简单/实用的解决方案:
我所做的只是在顶层结构中编写一些
modify...
辅助函数,用于处理难看的嵌套副本。例如:我的主要目标(简化客户端的更新)已经实现:
创建完整的修改助手集显然很烦人。但是对于内部的东西,通常在第一次尝试修改某个嵌套字段时就创建它们是可以的。
ws51t4hk7#
使用QuickLens:
长答案
也许QuickLens更符合您的问题。QuickLens使用宏将IDE友好的表达式转换为接近原始副本语句的内容。
给定两个范例案例类别:
和Person类的示例:
您可以使用以下命令更新raj zipCode: