blu*_*e10 19 state design-patterns functional-programming scala immutability
我目前正在尝试将更多功能的编程风格应用于涉及低级(基于LWJGL)的GUI开发的项目.显然,在这种情况下,需要携带很多状态,这在当前版本中是可变的.我的目标是最终拥有一个完全不可变的状态,以避免状态变化作为副作用.我研究了scalaz的镜头和状态monad一段时间,但我主要担心的是:所有这些技术都依赖于写时复制.由于我的州有大量的田地和一些相当大的田地,我担心表现.
据我所知,修改不可变对象的最常用方法是使用生成的copy方法case class(这也是镜头所做的内容).我的第一个问题是,这种copy方法是如何实际实现的?我用以下类进行了一些实验:
case class State(
innocentField: Int,
largeMap: Map[Int, Int],
largeArray: Array[Int]
)
Run Code Online (Sandbox Code Playgroud)
标杆管理,并通过查看输出-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
}
Run Code Online (Sandbox Code Playgroud)
现在这允许做这样的事情:
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()
Run Code Online (Sandbox Code Playgroud)
我现在的问题是:你怎么看待这个设计?这是我不知道的某种FP设计模式(除了与Builder模式的相似性)?由于我没有找到类似的东西,我想知道这种方法是否存在一些重大问题?或者是否有更多标准方法可以解决写时复制瓶颈而不放弃不变性?
是否有可能以某种方式统一get/set/mod函数?
编辑:
我copy执行深层复制的假设确实是错误的.
Rex*_*err 12
这与视图基本相同,是一种惰性评估; 这种类型的策略或多或少是Haskell中的默认策略,并且在Scala中使用了相当一部分(参见例如地图上的mapValues,分组集合,几乎任何关于Iterator或Stream的返回另一个Iterator或Stream的内容等).在正确的背景下避免额外工作是一种行之有效的策略.
但我认为你的前提有点错误.
case class Foo(bar: Int, baz: Map[String,Boolean]) {}
Foo(1,Map("fish"->true)).copy(bar = 2)
Run Code Online (Sandbox Code Playgroud)
实际上并不会导致地图被深深复制.它只是设置引用.字节码证明:
62: astore_1
63: iconst_2 // This is bar = 2
64: istore_2
65: aload_1
66: invokevirtual #72; //Method Foo.copy$default$2:()Lscala/collection/immutable/Map;
69: astore_3 // That was baz
70: aload_1
71: iload_2
72: aload_3
73: invokevirtual #76; //Method Foo.copy:(ILscala/collection/immutable/Map;)LFoo;
Run Code Online (Sandbox Code Playgroud)
让我们看看那copy$default$2件事是做什么的:
0: aload_0
1: invokevirtual #50; //Method baz:()Lscala/collection/immutable/Map;
4: areturn
Run Code Online (Sandbox Code Playgroud)
只需返回地图.
而且copy呢?
0: new #2; //class Foo
3: dup
4: iload_1
5: aload_2
6: invokespecial #44; //Method "<init>":(ILscala/collection/immutable/Map;)V
9: areturn
Run Code Online (Sandbox Code Playgroud)
只需调用常规构造函数.没有克隆地图.
因此,当您复制时,您只需创建一个对象 - 您正在复制的内容的新副本,并填写字段.如果您有大量字段,您的视图将更快(因为您必须创建一个新对象) (如果您使用函数应用程序版本,则两个,因为您还需要创建函数对象)但它只有一个字段).否则它应该是一样的.
所以,是的,好主意可能,但仔细测试以确保它在你的情况下是值得的 - 你必须手工编写一些代码,而不是让案例类为你做所有.
| 归档时间: |
|
| 查看次数: |
1886 次 |
| 最近记录: |