我目前正在尝试将更具功能性的编程风格应用于涉及低级(基于 LWJGL)GUI 开发的项目。显然,在这种情况下,需要携带很多状态,这在当前版本中是可变的。我的目标是最终拥有一个完全不可变的状态,以避免状态更改作为副作用。我研究了 scalaz 的镜头和状态单子一段时间,但我主要关心的是:所有这些技术都依赖于写时复制。由于我所在的州既有大量的字段,也有一些相当大的字段,所以我担心性能。
据我所知,修改不可变对象的最常见方法是使用copy
a 的生成方法case class
(这也是镜头在引擎盖下所做的)。我的第一个问题是,这种copy
方法实际上是如何实现的?我对一个类进行了一些实验,例如:
case class State(
innocentField: Int,
largeMap: Map[Int, Int],
largeArray: Array[Int]
)
通过基准测试以及查看它的输出,-Xprof
看起来更新someState.copy(innocentField = 42)
实际上执行了深层复制,当我增加 和 的大小时,我观察到性能显着largeMap
下降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 功能?
编辑:
copy
我执行深拷贝的假设确实是错误的。