【发布时间】:2012-10-26 22:40:34
【问题描述】:
我目前正在尝试将更具功能性的编程风格应用于涉及低级(基于 LWJGL)GUI 开发的项目。显然,在这种情况下,需要携带很多状态,这在当前版本中是可变的。我的目标是最终拥有一个完全不可变的状态,以避免状态更改作为副作用。我研究了 scalaz 的镜头和状态单子一段时间,但我主要关心的是:所有这些技术都依赖于写时复制。由于我所在的州既有大量的字段,也有一些相当大的字段,所以我担心性能。
据我所知,修改不可变对象的最常用方法是使用 case class 生成的 copy 方法(这也是镜头在引擎盖下所做的)。我的第一个问题是,这个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 执行深拷贝的假设确实是错误的。
【问题讨论】:
-
这是一个非常好的问题,最重要的是,它写得很好,值得注意的是,OP 花了一些心思。我真的想知道谁投票结束了这个......
-
回想起来,最好问两个问题,一个是关于案例类副本的基准测试结果,另一个是关于提议设计的改进。但显然我没想到我搞砸了基准:)。
-
我想说这个问题在你的情况下已经解决了,因为你不必再担心复制案例类了。关于您的设计改进:对我来说,跟踪状态的标准 FP 解决方案是 State Monad,网络上有很多材料,例如 this one。您的解决方案的主要问题是它将不可变的数据更改存储在 可变结构 中,这不是纯粹的功能设计。状态单子以纯粹的方式为您提供相同的功能。
标签: scala design-patterns functional-programming state immutability