Scala:收集不变状态的更新/更改
我正在尝试将一个更具功能的编程风格应用于涉及低级(LWJGL)GUI开发的项目。显然,在这种情况下,需要携带很多状态,这在当前版本中是可变的。我的目标是最终有一个完全不变的状态,以避免国家的变化作为副作用。我研究了scalaz的镜头和状态monads一段时间,但我的主要关切仍然是:所有这些技术都依赖于写时复制。既然我的国家既有大量的领域,也有一些相当大的领域,我担心的是表现。
据我所知,修改不可变对象最常用的方法是使用案例类生成的复制方法(这也是镜头在引擎盖下)。我的第一个问题是,这种复制方式是如何实际实现的?我进行了一些类似的实验: case class State( innocentField: Int,largeMap: Map[Int,Int],largeArray: Array[Int] ) 通过基准测试,并且通过查看-Xprof的输出,看起来更新someState.copy(innocentField = 42)实际上执行了深层拷贝,当我增加largeMap和largeArray的大小时,我观察到显着的性能下降。我以某种方式期望新构造的实例共享原始状态的对象引用,因为内部的引用应该被传递给构造函数。我可以以某种方式强制还是禁用默认副本的深层复制行为? 在思考复制问题时,我想知道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函数? 编辑: 我的假设,副本执行深层次的确是错误的。 解决方法
这与观点基本相同,是一种懒惰评估;这种类型的策略或多或少是Haskell中的默认值,并且在Scala中使用一个公平的位(参见例如mapValues在地图上,分组在集合上,几乎任何在Iterator或Stream上返回另一个Iterator或Stream等等)。这是一种经过验证的策略,可以在正确的背景下避免额外的工作。
但我认为你的前提有点错误。 case class Foo(bar: Int,baz: Map[String,Boolean]) {} Foo(1,Map("fish"->true)).copy(bar = 2) 实际上并不会导致地图被深深的复制。它只是设置引用。字节码证明: 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; 让我们看看这个拷贝$ default $ 2的东西: 0: aload_0 1: invokevirtual #50; //Method baz:()Lscala/collection/immutable/Map; 4: areturn 只需返回地图。 并复制本身? 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 只需调用常规构造函数。没有克隆地图。 所以当你复制时,你创建一个对象 – 一个新的副本,你正在复制的内容,填写的字段。如果你有大量的字段,你的视图将会更快(因为你必须创建一个新的对象(两个如果你使用函数应用程序版本,因为你也需要创建函数对象),但它只有一个字段)。否则应该是大致相同的。 所以,是的,好的主意有可能,但是仔细地进行基准测试,以确保在你的情况下是值得的 – 你必须用手写一些相当的代码,而不是让case类为你做这些。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |