Swift memoizing/caching 结构中的惰性变量
Swift memoizing/caching lazy variable in a struct
我在 Swift 喝了 struct/value koolaid。现在我有一个有趣的问题,我不知道如何解决。我有一个容器结构,例如
struct Foo {
var bars:[Bar]
}
当我对此进行编辑时,我会创建副本以便保留撤消堆栈。到目前为止,一切都很好。就像好的教程所显示的那样。不过,我对这个人使用了一些派生属性:
struct Foo {
var bars:[Bar]
var derivedValue:Int {
...
}
}
在最近的分析中,我注意到 a) 计算 derivedValue 的计算有点 expensive/redundant b) 在各种用例中并不总是需要计算。
以我经典的 OOP 方式,我会将其设为 memoizing/lazy 变量。基本上,在调用之前让它为零,计算一次并存储它,并且 return 表示未来调用的结果。因为我遵循 "make copies to edit" 模式,所以不变量不会被破坏。
但是如果它是结构的,我不知道如何应用这个模式。我可以这样做:
struct Foo {
var bars:[Bar]
lazy var derivedValue:Int = self.computeDerivation()
}
这有效,直到结构引用该值本身,例如
struct Foo {
var bars:[Bar]
lazy var derivedValue:Int = self.computeDerivation()
fun anotherDerivedComputation() {
return self.derivedValue / 2
}
}
此时,编译器会报错,因为 anotherDerivedComputation
导致接收器发生变化,因此需要标记为 mutating
。将访问器标记为变异只是感觉不对。但是对于笑容,我尝试了一下,但这会产生一系列新的问题。现在在任何我有类似
表达式的地方
XCTAssertEqaul(foo.anotherDerivedComputation(), 20)
编译器会报错,因为参数隐含地是一个非可变的 let 值,而不是一个 var。
我是否缺少一个包含 deferred/lazy/cached 成员的结构的模式?
我知道使这项工作起作用的唯一方法是将惰性成员包装在 class 中。这样,包含对象引用的结构可以保持不可变,而对象本身可以改变。
几年前我写了一篇关于这个主题的博客post:Lazy Properties in Structs。它更详细地介绍了细节,并提出了两种不同的包装器设计方法 class,具体取决于惰性成员是否需要来自结构的实例信息来计算缓存值。
记忆不会发生在结构内部。 memoize 的方法是将字典 off 存储在一些单独的 space 中。关键是推导值的任何内容,而值是计算一次的值。您可以将其设为结构类型的静态,就像命名空间的一种方式一样。
struct S {
static var memo = [Int:Int]()
var i : Int
var square : Int {
if let result = S.memo[i] {return result}
print("calculating")
let newresult = i*i // pretend that's expensive
S.memo[i] = newresult
return newresult
}
}
var s = S(i:2)
s.square // calculating
s = S(i:2)
s.square // [nothing]
s = S(i:3)
s.square // calculating
我将问题概括为一个更简单的问题:一个 x,y 点结构,它想要延迟 compute/cache r(adius) 的值。我使用 ref wrapper 围绕一个 block closure 并提出了以下内容。我称之为 "Once" 块。
import Foundation
class Once<Input,Output> {
let block:(Input)->Output
private var cache:Output? = nil
init(_ block:@escaping (Input)->Output) {
self.block = block
}
func once(_ input:Input) -> Output {
if self.cache == nil {
self.cache = self.block(input)
}
return self.cache!
}
}
struct Point {
let x:Float
let y:Float
private let rOnce:Once<Point,Float> = Once {myself in myself.computeRadius()}
init(x:Float, y:Float) {
self.x = x
self.y = y
}
var r:Float {
return self.rOnce.once(self)
}
func computeRadius() -> Float {
return sqrtf((self.x * self.x) + (self.y * self.y))
}
}
let p = Point(x: 30, y: 40)
print("p.r \(p.r)")
我选择让 OnceBlock 接受输入,否则将它初始化为一个引用 self 的函数会很痛苦,因为 self 在初始化时还不存在,所以更容易推迟到 cache/call 网站的链接(var r:Float
)
我在 Swift 喝了 struct/value koolaid。现在我有一个有趣的问题,我不知道如何解决。我有一个容器结构,例如
struct Foo {
var bars:[Bar]
}
当我对此进行编辑时,我会创建副本以便保留撤消堆栈。到目前为止,一切都很好。就像好的教程所显示的那样。不过,我对这个人使用了一些派生属性:
struct Foo {
var bars:[Bar]
var derivedValue:Int {
...
}
}
在最近的分析中,我注意到 a) 计算 derivedValue 的计算有点 expensive/redundant b) 在各种用例中并不总是需要计算。
以我经典的 OOP 方式,我会将其设为 memoizing/lazy 变量。基本上,在调用之前让它为零,计算一次并存储它,并且 return 表示未来调用的结果。因为我遵循 "make copies to edit" 模式,所以不变量不会被破坏。
但是如果它是结构的,我不知道如何应用这个模式。我可以这样做:
struct Foo {
var bars:[Bar]
lazy var derivedValue:Int = self.computeDerivation()
}
这有效,直到结构引用该值本身,例如
struct Foo {
var bars:[Bar]
lazy var derivedValue:Int = self.computeDerivation()
fun anotherDerivedComputation() {
return self.derivedValue / 2
}
}
此时,编译器会报错,因为 anotherDerivedComputation
导致接收器发生变化,因此需要标记为 mutating
。将访问器标记为变异只是感觉不对。但是对于笑容,我尝试了一下,但这会产生一系列新的问题。现在在任何我有类似
XCTAssertEqaul(foo.anotherDerivedComputation(), 20)
编译器会报错,因为参数隐含地是一个非可变的 let 值,而不是一个 var。
我是否缺少一个包含 deferred/lazy/cached 成员的结构的模式?
我知道使这项工作起作用的唯一方法是将惰性成员包装在 class 中。这样,包含对象引用的结构可以保持不可变,而对象本身可以改变。
几年前我写了一篇关于这个主题的博客post:Lazy Properties in Structs。它更详细地介绍了细节,并提出了两种不同的包装器设计方法 class,具体取决于惰性成员是否需要来自结构的实例信息来计算缓存值。
记忆不会发生在结构内部。 memoize 的方法是将字典 off 存储在一些单独的 space 中。关键是推导值的任何内容,而值是计算一次的值。您可以将其设为结构类型的静态,就像命名空间的一种方式一样。
struct S {
static var memo = [Int:Int]()
var i : Int
var square : Int {
if let result = S.memo[i] {return result}
print("calculating")
let newresult = i*i // pretend that's expensive
S.memo[i] = newresult
return newresult
}
}
var s = S(i:2)
s.square // calculating
s = S(i:2)
s.square // [nothing]
s = S(i:3)
s.square // calculating
我将问题概括为一个更简单的问题:一个 x,y 点结构,它想要延迟 compute/cache r(adius) 的值。我使用 ref wrapper 围绕一个 block closure 并提出了以下内容。我称之为 "Once" 块。
import Foundation
class Once<Input,Output> {
let block:(Input)->Output
private var cache:Output? = nil
init(_ block:@escaping (Input)->Output) {
self.block = block
}
func once(_ input:Input) -> Output {
if self.cache == nil {
self.cache = self.block(input)
}
return self.cache!
}
}
struct Point {
let x:Float
let y:Float
private let rOnce:Once<Point,Float> = Once {myself in myself.computeRadius()}
init(x:Float, y:Float) {
self.x = x
self.y = y
}
var r:Float {
return self.rOnce.once(self)
}
func computeRadius() -> Float {
return sqrtf((self.x * self.x) + (self.y * self.y))
}
}
let p = Point(x: 30, y: 40)
print("p.r \(p.r)")
我选择让 OnceBlock 接受输入,否则将它初始化为一个引用 self 的函数会很痛苦,因为 self 在初始化时还不存在,所以更容易推迟到 cache/call 网站的链接(var r:Float
)