将 case-类 用于可变状态(真的)不好吗?
Is it (really that) bad to use case-classes for mutable state?
考虑以下代码:
case class Vector3(var x: Float, var y: Float, var z: Float)
{
def add(v: Vector3): Unit =
{
this.x += v.x
this.y += v.y
this.z += v.z
}
}
如您所见,case class
保持可变状态。非常不鼓励这样做,通常我会同意并绝对坚持这一点 "rule",但这是整个故事。
我正在使用 Scala 从 scratch 编写一个小的 3d 游戏引擎。所以首先我考虑使用(更多)函数式风格,但垃圾收集器会经常启动。
想一想:我在一个测试游戏中有几十个实体。它们都有一个位置 (Vector3)、一个方向 (Vector3)、一个比例 (Vector3) 和一大堆矩阵。如果我要在这些 classes(Vector3 和 Matrix4)中发挥作用并使它们不可变,我将每帧 return 数百个新对象,导致巨大的 fps 损失,因为,让我们面对现实吧, GC 有它的用处,但在游戏引擎和 OpenGL 中……没那么多。
Vector3 以前是 class,但现在是 class,因为在代码的某处我需要对其进行模式匹配。
那么, 使用保存可变状态的 case-class 真的不好吗?
请不要把它变成关于"Why do you even use Scala for a project such as that?"的讨论我知道可能有更好的选择,但我对用 C++ 编写(又一个)引擎不感兴趣,我也不太急于钻研 Rust(还)。
我会说使用具有可变状态的 case classes 是不好的,但这只是因为它们会覆盖您的 equals
和 hashCode
方法。在您的代码中的某处,您可能会检查 a == b
是否相等并发现它们相等。后来它们可能会有所不同,因为它们是可变的。至少,将它们与基于散列的集合结合使用是危险的。
但是,您似乎并不需要案例 class 提供的所有功能。您真正需要的似乎是用于模式匹配的提取器,那么为什么不定义它呢?此外,静态工厂 apply
和可读的 toString
表示可能很方便,因此您可以实现它们。
怎么样:
class Vector (var x: Float, var y: Float, var z: Float) {
override def toString = s"Vector($x, $y, $z)"
}
object Vector {
def apply(x: Float, y: Float, z: Float) = new Vector(x, y, z)
def unapply(v: Vector): Option[(Float, Float, Float)] = Some((v.x, v.y, v.z))
}
考虑以下代码:
case class Vector3(var x: Float, var y: Float, var z: Float)
{
def add(v: Vector3): Unit =
{
this.x += v.x
this.y += v.y
this.z += v.z
}
}
如您所见,case class
保持可变状态。非常不鼓励这样做,通常我会同意并绝对坚持这一点 "rule",但这是整个故事。
我正在使用 Scala 从 scratch 编写一个小的 3d 游戏引擎。所以首先我考虑使用(更多)函数式风格,但垃圾收集器会经常启动。
想一想:我在一个测试游戏中有几十个实体。它们都有一个位置 (Vector3)、一个方向 (Vector3)、一个比例 (Vector3) 和一大堆矩阵。如果我要在这些 classes(Vector3 和 Matrix4)中发挥作用并使它们不可变,我将每帧 return 数百个新对象,导致巨大的 fps 损失,因为,让我们面对现实吧, GC 有它的用处,但在游戏引擎和 OpenGL 中……没那么多。
Vector3 以前是 class,但现在是 class,因为在代码的某处我需要对其进行模式匹配。
那么, 使用保存可变状态的 case-class 真的不好吗?
请不要把它变成关于"Why do you even use Scala for a project such as that?"的讨论我知道可能有更好的选择,但我对用 C++ 编写(又一个)引擎不感兴趣,我也不太急于钻研 Rust(还)。
我会说使用具有可变状态的 case classes 是不好的,但这只是因为它们会覆盖您的 equals
和 hashCode
方法。在您的代码中的某处,您可能会检查 a == b
是否相等并发现它们相等。后来它们可能会有所不同,因为它们是可变的。至少,将它们与基于散列的集合结合使用是危险的。
但是,您似乎并不需要案例 class 提供的所有功能。您真正需要的似乎是用于模式匹配的提取器,那么为什么不定义它呢?此外,静态工厂 apply
和可读的 toString
表示可能很方便,因此您可以实现它们。
怎么样:
class Vector (var x: Float, var y: Float, var z: Float) {
override def toString = s"Vector($x, $y, $z)"
}
object Vector {
def apply(x: Float, y: Float, z: Float) = new Vector(x, y, z)
def unapply(v: Vector): Option[(Float, Float, Float)] = Some((v.x, v.y, v.z))
}