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

2024-02-15

我目前正在尝试将更实用的编程风格应用于涉及低级(基于 LWJGL)GUI 开发的项目。显然,在这种情况下,有必要携带大量状态,而这些状态在当前版本中是可变的。我的目标是最终拥有一个完全不可变的状态,以避免状态更改带来的副作用。我研究了 scalaz 的镜头和状态单子一段时间,但我主要关心的仍然是:所有这些技术都依赖于写时复制。由于我所在的州既有大量领域,也有一些规模相当大的领域,所以我担心性能。

据我所知,修改不可变对象的最常见方法是使用生成的copy的方法case class(这也是镜头在引擎盖下的作用)。我的第一个问题是,这是如何copy方法实际实现了吗?我对这样的课程进行了一些实验:

case class State(
  innocentField: Int, 
  largeMap: Map[Int, Int], 
  largeArray: Array[Int]
)

通过基准测试以及查看输出-Xprof看起来正在更新someState.copy(innocentField = 42)实际上执行了深层复制,当我增加大小时,我观察到性能显着下降largeMap and 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 功能?

Edit:

我的假设是copy执行深复制确实是错误的。


这与视图基本相同,是一种惰性求值;这种类型的策略或多或少是 Haskell 中的默认策略,并且在 Scala 中也有相当多的使用(例如,参见映射上的 mapValues、按集合分组、迭代器或流上返回另一个迭代器或流的几乎所有内容等)。这是一种经过验证的策略,可以在适当的情况下避免额外的工作。

但我认为你的前提有些错误。

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;

让我们看看那是什么copy$default$2事情的作用是:

0:  aload_0
1:  invokevirtual   #50; //Method baz:()Lscala/collection/immutable/Map;
4:  areturn

只是返回地图。

And copy itself?

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

只需调用常规构造函数即可。没有地图的克隆。

因此,当您复制时,您只会创建一个对象 - 您正在复制的内容的新副本,其中填充了字段。如果您有大量字段,您的视图会更快(因为您必须创建一个新对象(如果您使用函数应用程序版本,则需要两个,因为您还需要创建函数对象)但它只有一个字段)。否则应该是差不多的。

所以,是的,这可能是个好主意,但请仔细进行基准测试,以确保它在您的案例中是值得的——您必须手动编写相当多的代码,而不是让案例类为您完成这一切。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Scala:收集不可变状态的更新/更改 的相关文章

  • 如何为抽象工厂创建的类设置特定属性?

    是否可以让具体工厂使用抽象工厂模式为其创建具有特定类型参数的具体类 或者由各自的具体工厂创建的不同具体类是否需要具有相同的字段 例如 在下图中 您将如何使用客户端 应用程序 给出的不同参数集来实例化 WinButton 和 OSXButto
  • 服务作为 SOA 中的中介

    我知道什么是 通常的 中介设计模式 维基百科中有一些描述 http en wikipedia org wiki Mediator pattern http en wikipedia org wiki Mediator pattern 在我的
  • andThen 类型不匹配的 Scala 链接函数

    我有一堆函数可以清理文本并将它们分成单词 最小的例子 val txt Mary had a little nlamb val stopwords Seq a def clean text String String text replace
  • 在 Scala 中将元素追加到列表末尾

    我无法添加 type 元素T到一个列表中List T 我尝试过myList myElement但它似乎创建了一个奇怪的对象并访问myList last始终返回放入列表中的第一个元素 我怎么解决这个问题 List 1 2 3 4 Result
  • Java 8 Stream,获取头部和尾部

    Java 8 引入了Stream http download java net jdk8 docs api java util stream Stream html类似于 Scala 的类Stream http www scala lang
  • “功能性”Rust 对性能有哪些影响?

    我正在关注 Rust 轨道运动 io https exercism io 我有相当多的 C C 经验 我喜欢 Rust 的 功能 元素 但我担心相对性能 我解决了 行程编码 问题 https exercism io tracks rust
  • 在scala 2.13中,为什么有时无法显式调用类型类?

    这是 Shapeless 2 3 3 中的一个简单示例 val book author gt gt Benjamin Pierce title gt gt Types and Programming Languages id gt gt 2
  • 为什么自类型类可以声明类

    我知道 Scala 只能混合特征 这对于依赖注入和蛋糕模式是有意义的 我的问题是为什么我仍然可以声明一个需要另一个 类 但不需要特征的类 Code class C class D self C gt 这仍然编译成功 我认为它应该编译失败 因
  • 获取SettingKey[T]的值

    我正在开发一个用于文档生成的插件 我想将所有生成的文件输出到我选择的目录中 该目录可以是SBT的子目录target目录 如下 val newTargetDirectory SettingKey File document target di
  • Spark 2.2 无法将 df 写入 parquet

    我正在构建一个聚类算法 我需要存储模型以供将来加载 我有一个具有以下架构的数据框 val schema new StructType add StructField uniqueId LongType add StructField tim
  • Spark scala 模拟 Spark.implicits 用于单元测试

    当尝试使用 Spark 和 Scala 简化单元测试时 我使用 scala test 和mockito scala 以及mockito Sugar 这只是让你做这样的事情 val sparkSessionMock mock SparkSes
  • Scala 案例类忽略 Spark shell 中的导入

    我希望这个问题有一个明显的答案 我刚刚升级到 Spark v2 0 并且遇到了一个奇怪的问题火花外壳 Scala 2 11 版本 如果我输入以下最小的 Scala import java sql Timestamp case class C
  • java异常处理策略[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 在管道中重用变量的功能方式

    在 javascript 和 typescript 中与 Ramda 一起使用函数式编程 我经常发现自己编写如下代码 const myFun c gt const myId c id const value pipe getAnotherO
  • 返回 Java 8 中的通用函数接口

    我想写一种函数工厂 它应该是一个函数 以不同的策略作为参数调用一次 它应该返回一个函数 该函数根据参数选择其中一种策略 该参数将由谓词实现 嗯 最好看看condition3为了更好的理解 问题是 它没有编译 我认为因为编译器无法弄清楚函数式
  • std::bind 重载解析

    下面的代码工作正常 include
  • 如何关闭 Scala 中因方法重载而导致代码无法编译的特定隐式?

    我正忙着尝试自己回答这个问题 Scala Play 2 4 x 通过 anorm MySQL 处理扩展字符到 Java Mail https stackoverflow com questions 31417718 scala play 2
  • 在spark-kafka中使用schema将ConsumerRecord值转换为Dataframe

    我正在使用 Spark 2 0 2 和 Kafka 0 11 0 并且 我正在尝试在火花流中使用来自卡夫卡的消息 以下是代码 val topics notes val kafkaParams Map String Object bootst
  • 如何判断何时创建新组件?

    我一直在寻找背后的逻辑当有人在 AngularJS Angular 上的 Web 应用程序中创建新组件时但我认为这更通用 可能适用于所有基于组件的前端框架 我知道有像这样的一些原则应该是抽象的和可重用的但例如我在角度文档中看到 每个单独的路
  • 当泛型类型与无界通配符一起使用时,不考虑类型参数绑定

    在我的项目中 我有一个这样的星座 trait F trait X A lt F def test x X X lt F x Trait X有一个类型参数 其上限为F 根据我的理解 类型X and X lt F 应该是等价的 但scalac2

随机推荐