首页 文章

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

提问于
浏览
5

我正在阅读结构化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
}

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

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

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

  • make "bar: Option[Bar]"和"baz: Option[Baz]" case类参数,然后我可以使用"copy"来创建Foo类的新版本,并将它们设置为某个东西;但是我必须每次访问它们都要检查它们 - 效率低,不安全,不能制作一个只保证具有正确结构的DecorativeFoo

  • 制作第二个案例类,它是第一个案例中所有结构的复制粘贴,但添加了两个额外的修饰参数 - 但这意味着在定义中回显整个参数列表,并在创建它的实例时再次

  • 案例类继承显然是有争议的,无论如何似乎也要求我在子类构造函数中重复每一个参数?

  • 创建一个列出常见案例类参数的非案例超类 . 然后在case类中扩展它 . 但这似乎仍然需要重复子类构造函数中的每个单个参数 .

  • 我看到人们谈论这个问题的博客,并在运行时使用反射来填充其装饰副本的基本属性 - 这避免了回声,但现在你没有类型安全,指定属性名称为字符串,开销等...

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

3 回答

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

    用例示例:

    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))
    
  • 1

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

    case class Foo(
      id: Int,
      bar_id: Int,
      baz_id: Int,
      x: Int,
      y: String
    )
    
    case class ProcessedFoo(
      foo: Foo,
      bar: Bar,
      baz: Baz
    )
    
  • 1

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

    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 返回 FooWithBaz ,但由于隐式转换,我们可以在必要时将返回值视为 Foo .

相关问题