sách gpt4 ai đã đi

Scala: Thu thập các bản cập nhật/thay đổi trạng thái không thay đổi

In lại 作者:行者123 更新时间:2023-12-03 12:55:18 28 4
mua khóa gpt4 Nike

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

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

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

通过基准测试以及查看 -Xprof 的输出,看起来更新 someState.copy(innocentField = 42) 实际上执行了深层复制,当我增加 largeMaplargeArray 的大小时,我观察到性能显着下降。我以某种方式期望新构造的实例共享原始状态的对象引用,因为在内部引用应该只是传递给构造函数。我可以以某种方式强制或禁用默认 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 功能?

biên tập:

Tôi nghĩ copy 执行深拷贝的假设确实是错误的。

1 Câu trả lời

这与 View 基本相同,是一种惰性求值;这种类型的策略或多或少是 Haskell 中的默认策略,并且在 Scala 中使用得相当多(参见例如 mapValues on maps、grouped on collections、Iterator 或 Stream 上几乎所有返回另一个 Iterator 或 Stream 的东西等)。这是一种行之有效的策略,可以避免在正确的环境中进行额外的工作。

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

case class Foo(bar: Int, baz: Map[String,Boolean]) {}
Foo(1,Map("fish"->true)).copy(bar = 2)

实际上不会导致 map 被深度复制。它只是设置引用。字节码证明:
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

只返回 map 。

copy本身?
0: new #2; //class Foo
3: dup
4: iload_1
5: aload_2
6: invokespecial #44; //Method "":(ILscala/collection/immutable/Map;)V
9: areturn

只需调用常规构造函数。没有克隆 map 。

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

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

关于Scala:收集不可变状态的更新/更改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13260162/

28 4 0
行者123
Hồ sơ cá nhân

Tôi là một lập trình viên xuất sắc, rất giỏi!

Nhận phiếu giảm giá Didi Taxi miễn phí
Mã giảm giá Didi Taxi
Giấy chứng nhận ICP Bắc Kinh số 000000
Hợp tác quảng cáo: 1813099741@qq.com 6ren.com