如何从 scala case 类 装饰一个不可变对象图

How to decorate an immutable object graph from scala case classes

我正在阅读结构化的 JSON,使用 Play Frameworks 的 JSON 阅读来构建带有大小写 classes 的对象图。

一个例子:

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

建好Foo以后一定要回来,设置bar和baz来装饰一下。这些在其他 JSON 文件中定义,只有在所有解析完成后才知道。但这意味着 Foo 不可能是不可变的。

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

我知道几种感觉不对的方法:

Scala 肯定有办法让人们用更简单的对象组合更复杂的不可变对象,而不必手动复制它们的每个部分吗?

另一个策略可能是创建另一个案例 class:

case class Foo(
  id: Int,
  bar_id: Int,
  baz_id: Int,
  x: Int,
  y: String
)

case class ProcessedFoo(
  foo: Foo,
  bar: Bar,
  baz: Baz
)

您可以为已处理的类型引入一个新特征,一个扩展该特征的 class,以及一个隐式转换:

case class Foo(bar: Int)

trait HasBaz {
    val baz: Int
}

class FooWithBaz(val foo: Foo, val baz: Int) extends HasBaz

object FooWithBaz {
    implicit def innerFoo(fwb: FooWithBaz): Foo = fwb.foo

    implicit class RichFoo(val foo: Foo) extends AnyVal {
        def withBaz(baz: Int) = new FooWithBaz(foo, baz)
    }
}

那么你可以这样做:

import FooWithBaz._
Foo(1).withBaz(5)

而且,尽管 withBaz return 是 FooWithBaz,但由于隐式转换,我们可以在必要时将 return 值视为 Foo .

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

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]
}

示例用例:

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)

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


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

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

那么当你需要 Foo[Option].

时,你可以使用 Foo[Unprocessed]Foo[Processed]
scala> val foo: Foo[Option] = processed
foo: Acme.Foo[Option] = Foo(42,b,Some(true),Some(42.0))