在结构中快速记忆/缓存惰性变量

Tra*_*ggs 5 memoization swift swift-structs

我喝了Swift中的struct / value koolaid。现在我有一个有趣的问题,我不知道该如何解决。我有一个结构是一个容器,例如

struct Foo {
    var bars:[Bar]
}
Run Code Online (Sandbox Code Playgroud)

在对此进行编辑时,我会创建副本,以便保留撤消堆栈。到目前为止,一切都很好。就像显示的好教程一样。不过,我与此人一起使用了一些派生属性:

struct Foo {
    var bars:[Bar]

    var derivedValue:Int {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

在最近的分析中,我注意到a)计算派生Value的计算是一种昂贵/冗余的b)在各种用例中并不总是必须进行计算。

按照我经典的OOP方式,我会将其设置为记忆/惰性变量。基本上,在调用之前将其设置为零,对其进行一次计算并存储,然后在以后的调用中返回所述结果。由于我遵循的是“制作副本以进行编辑”模式,因此不变性不会被破坏。

但是我无法弄清楚如果是struct则如何应用这种模式。我可以做这个:

struct Foo {
    var bars:[Bar]
    lazy var derivedValue:Int = self.computeDerivation()
}
Run Code Online (Sandbox Code Playgroud)

它起作用,直到结构引用该值本身,例如

struct Foo {
    var bars:[Bar]
    lazy var derivedValue:Int = self.computeDerivation()

    fun anotherDerivedComputation() {
        return self.derivedValue / 2
    }
}
Run Code Online (Sandbox Code Playgroud)

此时,编译器会抱怨,因为anotherDerivedComputation正在引起接收方的更改,因此需要进行标记mutating。使访问器被标记为变异只是感觉不对。但是对于咧嘴笑,我尝试了一下,但是这带来了一系列新的问题。现在任何我有表达的地方

XCTAssertEqaul(foo.anotherDerivedComputation(), 20)
Run Code Online (Sandbox Code Playgroud)

编译器抱怨,因为参数隐式是一个不变的let值,而不是var。

我是否缺少一种具有延迟/惰性/缓存成员的结构的模式?

Tra*_*ggs 3

我将问题概括为一个更简单的问题:一个 x,y 点结构,它想要延迟计算/缓存 r(adius) 的值。我将引用包装器包裹在块闭合上,并提出了以下内容。我称之为“一次”块。

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)")
Run Code Online (Sandbox Code Playgroud)

我选择让 OnceBlock 接受输入,因为否则将其初始化为具有 self 引用的函数会很痛苦,因为 self 在初始化时还不存在,因此将链接推迟到缓存会更容易/呼叫站点(var r:Float