我目前正在尝试将更实用的编程风格应用于涉及低级(基于 LWJGL)GUI 开发的项目。显然,在这种情况下,有必要携带大量状态,而这些状态在当前版本中是可变的。我的目标是最终拥有一个完全不可变的状态,以避免状态更改带来的副作用。我研究了 scalaz 的镜头和状态单子一段时间,但我主要关心的仍然是:所有这些技术都依赖于写时复制。由于我所在的州既有大量领域,也有一些规模相当大的领域,所以我担心性能。
据我所知,修改不可变对象的最常见方法是使用生成的copy
的方法case class
(这也是镜头在引擎盖下的作用)。我的第一个问题是,这是如何copy
方法实际实现了吗?我对这样的课程进行了一些实验:
case class State(
innocentField: Int,
largeMap: Map[Int, Int],
largeArray: Array[Int]
)
通过基准测试以及查看输出-Xprof
看起来正在更新someState.copy(innocentField = 42)
实际上执行了深层复制,当我增加大小时,我观察到性能显着下降largeMap
and largeArray
。我以某种方式期望新构造的实例共享原始状态的对象引用,因为在内部该引用应该传递给构造函数。我可以以某种方式强制或禁用默认的深度复制行为吗copy
?
在思考写时复制问题时,我想知道 FP 中是否有更通用的解决方案来解决这个问题,即以一种增量方式存储不可变数据的更改(在“收集更新”或“收集”的意义上)变化”)。令我惊讶的是我找不到任何东西,所以我尝试了以下方法:
// example state with just two fields
trait State {
def getName: String
def getX: Int
def setName(updated: String): State = new CachedState(this) {
override def getName: String = updated
}
def setX(updated: Int): State = new CachedState(this) {
override def getX: Int = updated
}
// convenient modifiers
def modName(f: String => String) = setName(f(getName))
def modX(f: Int => Int) = setX(f(getX))
def build(): State = new BasicState(getName, getX)
}
// actual (full) implementation of State
class BasicState(
val getName: String,
val getX: Int
) extends State
// CachedState delegates all getters to another state
class CachedState(oldState: State) extends State {
def getName = oldState.getName
def getX = oldState.getX
}
现在这允许做这样的事情:
var s: State = new BasicState("hello", 42)
// updating single fields does not copy
s = s.setName("world")
s = s.setX(0)
// after a certain number of "wrappings"
// we can extract (i.e. copy) a normal instance
val ns = s.setName("ok").setX(40).modX(_ + 2).build()
我现在的问题是:你觉得这个设计怎么样?这是我不知道的某种 FP 设计模式吗(除了与 Builder 模式的相似性之外)?由于我没有找到类似的东西,我想知道这种方法是否存在一些重大问题?或者有没有更标准的方法来解决写时复制瓶颈而不放弃不变性?
是否有可能以某种方式统一 get/set/mod 功能?
Edit:
我的假设是copy
执行深复制确实是错误的。