如何从scala案例类中装饰不可变对象图

use*_*354 5 json scala immutability case-class playframework

我正在阅读结构化JSON,使用Play Frameworks的JSON Reads来构建带有案例类的对象图.

一个例子:

case class Foo (
                       id: Int,
                       bar_id: Int,
                       baz_id: Int,
                       x: Int,
                       y: String
                       )
{
  var bar: Bar = null
  var baz: Baz = null
}
Run Code Online (Sandbox Code Playgroud)

在建造Foo之后,我必须稍后回来并通过设置bar和baz来装饰它.这些在其他JSON文件中定义,仅在所有解析完成时才知道.但这意味着Foo不可能是不可变的.

Scala中制作不可变对象的"正确"方式是什么,然后是它的装饰版本,而不是一遍又一遍地重复Foo的每个字段?

我知道几种感觉错误的方法:

  • make"bar:Option [Bar]"和"baz:Option [Baz]"案例类参数,然后我可以使用"copy"来创建Foo类的新版本,并将它们设置为某个东西; 但是我必须每次访问它们都要检查它们 - 效率低,不安全,不能制作一个只保证具有正确结构的DecorativeFoo
  • 制作第二个案例类,它是第一个案例中所有结构的复制粘贴,但添加了两个额外的修饰参数 - 但这意味着在定义中回显整个参数列表,并在创建它的实例时再次
  • 案例类继承显然是有争议的,无论如何似乎也要求我在子类构造函数中重复每一个参数?
  • 列出常见案例类参数的非案例超类.然后在case类中扩展它.但这似乎仍然需要重复子类构造函数中的每个单个参数.
  • 我看到人们谈论这个问题的博客,并在运行时使用反射来填充他们的装饰副本的基本属性 - 这避免了回声,但现在你没有类型安全,指定属性名称为字符串,开销等...

当然,Scala必须有办法让人们用更简单的对象组成更复杂的不可变对象,而不必手工复制它们的每一部分?

Dim*_*tri 1

组合Option和输入参数,您可以标记您的案例类,并静态跟踪处理的字段是否为空:

import scala.language.higherKinds

object Acme {
  case class Foo[T[X] <: Option[X] forSome { type X }](a: Int,
                                                       b: String,
                                                       c: T[Boolean],
                                                       d: T[Double])

  // Necessary, Foo[None] won't compile
  type Unprocessed[_] = None.type
  // Just an alias
  type Processed[X] = Some[X]
}
Run Code Online (Sandbox Code Playgroud)

示例用例:

import Acme._

val raw: Foo[Unprocessed] = Foo[Unprocessed](42, "b", None, None)

def process(unprocessed: Foo[Unprocessed]): Foo[Processed] =
  unprocessed.copy[Processed](c = Some(true), d = Some(42d))

val processed: Foo[Processed] = process(raw)

// No need to pattern match, use directly the x from the Some case class
println(processed.c.x)
println(processed.d.x)
Run Code Online (Sandbox Code Playgroud)

我在当前的项目中使用过一次。我遇到的主要问题是当我想要Foo协变时。


或者,如果您不关心 的界限T

case class Foo[+T[_]](a: Int, b: String, c: T[Boolean], d: T[Double])
Run Code Online (Sandbox Code Playgroud)

那么你可以在需要时使用Foo[Unprocessed]or 。Foo[Processed]Foo[Option]

scala> val foo: Foo[Option] = processed
foo: Acme.Foo[Option] = Foo(42,b,Some(true),Some(42.0))
Run Code Online (Sandbox Code Playgroud)