加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

Scala:收集不变状态的更新/更改

发布时间:2020-12-16 09:38:32 所属栏目:安全 来源:网络整理
导读:我正在尝试将一个更具功能的编程风格应用于涉及低级(LWJGL)GUI开发的项目。显然,在这种情况下,需要携带很多状态,这在当前版本中是可变的。我的目标是最终有一个完全不变的状态,以避免国家的变化作为副作用。我研究了scalaz的镜头和状态monads一段时间,
我正在尝试将一个更具功能的编程风格应用于涉及低级(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类为你做这些。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读