如何从 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 的每个字段多次?
我知道几种感觉不对的方法:
- 制作 "bar: Option[Bar]" 和 "baz: Option[Baz]" case class 参数,然后我可以使用 "copy" 制作新版本的 Foo class 并设置它们对某事;但是每次访问它们时我都必须检查它们 - 低效,不安全,无法制作保证具有正确结构的 DecoratedFoo
- 制作第二种情况 class 复制粘贴第一种情况中的所有结构,但添加两个额外的修饰参数 - 但这意味着在定义中回显整个参数列表,并再次创建它的实例时
- Case class 继承显然是有争议的,无论如何似乎也要求我在 subclass 构造函数中重复每一个参数?
- 制作一个非大小写超级class 列出常见大小写class 参数。然后在class的情况下扩展它。但这似乎仍然需要重复 subclass 构造函数中的每个参数。
- 我看到博客上有人在谈论这个问题,并在运行时使用反射来填充他们装饰副本的基本属性——这避免了回声,但现在你没有类型安全,将属性名称指定为字符串、开销等。 ..
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))
我正在阅读结构化的 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 的每个字段多次?
我知道几种感觉不对的方法:
- 制作 "bar: Option[Bar]" 和 "baz: Option[Baz]" case class 参数,然后我可以使用 "copy" 制作新版本的 Foo class 并设置它们对某事;但是每次访问它们时我都必须检查它们 - 低效,不安全,无法制作保证具有正确结构的 DecoratedFoo
- 制作第二种情况 class 复制粘贴第一种情况中的所有结构,但添加两个额外的修饰参数 - 但这意味着在定义中回显整个参数列表,并再次创建它的实例时
- Case class 继承显然是有争议的,无论如何似乎也要求我在 subclass 构造函数中重复每一个参数?
- 制作一个非大小写超级class 列出常见大小写class 参数。然后在class的情况下扩展它。但这似乎仍然需要重复 subclass 构造函数中的每个参数。
- 我看到博客上有人在谈论这个问题,并在运行时使用反射来填充他们装饰副本的基本属性——这避免了回声,但现在你没有类型安全,将属性名称指定为字符串、开销等。 ..
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))