如何正确地在 Swift 中的变异结构上创建惰性派生 属性?
How to properly make a lazy derived property on a mutating struct in Swift?
我正在制作一个变异结构,其派生值的计算成本非常高。所以我想做的是懒惰地计算这个派生值并存储结果,直到结构再次发生变异,此时派生值不再有效,需要重新计算。
(失败)选项 1:已生成 属性
如果派生值是生成的属性(如下所示),总是会返回正确的值,但总是会重新计算。
(失败)选项 2:延迟加载 属性
如果它是惰性的 属性,则计算只进行一次……永远。因此,一旦结构发生变异,派生值就会错误,并且不会重新计算。此外,如果我从结构中分配一个常量值,我将无法访问 属性。
在Swift 1.2 中是否有任何可能的解决方案,或者我是否需要提交雷达?
struct Struct {
var value: Int
// Option 1: Generated property
var derivedValue: Int {
println("Doing expensive calculation")
return self.value * 2
}
// Option 2: Lazy property
lazy var derivedValue: Int = {
println("Doing expensive calculation")
return self.value * 2
}()
init(value: Int) {
self.value = value
}
mutating func mutate() {
value = random()
}
}
var test = Struct(value: 2)
test.derivedValue
test.derivedValue // If not lazy, expensive calculation is done again here
test.mutate()
test.derivedValue // If lazy, this has wrong value
let test2 = test
test2.derivedValue // Compiler error if using lazy implementation
这是一个非常有趣的问题。我有一些不同的想法可以提供帮助。
首先,您有点误用了 lazy
属性 的概念。你只能有 lazy stored properties 因为懒惰所做的只是延迟执行,直到它第一次执行。从那时起,该值就是 属性 中的 stored
。您正在处理不能以这种方式使用的计算 属性 。您当然可以提交雷达,但我认为这是一个失败的原因,因为您的用例在 IMO 中不是有效的惰性案例。
话虽如此,我认为您有几个选择。
选项 1 - 使用 Class 和 属性 观察者
class Calculator {
var value: Int {
didSet {
valueChanged = true
}
}
var valueChanged = false
var derivedValue: Int {
if valueChanged {
println("Doing expensive calculation")
valueChanged = false
}
return self.value * 2
}
init(value: Int) {
self.value = value
}
func mutate() {
value = random()
}
}
这里的优点是您仍然可以在调用 属性 时延迟计算 derivedValue
。缺点是您不再使用 "by value" 对象。
选项 2 - 在 Mutate 方法中计算昂贵的值
struct SortOfLazyCalculator {
var value: Int
var expensiveComputedValue: Int = 0 // just guessing
var derivedValue: Int {
return self.value * 2
}
init(value: Int) {
self.value = value
}
mutating func mutate() {
value = random()
expensiveComputedValue = random() // not sure what the expensive calculation is
}
}
这种方法的优点是你仍然可以保留你的 "by value" 对象,但你必须在突变时计算昂贵的值。您不能在 derivedValue
属性 中执行此操作,因为您不能在计算的 属性 中为结构更改 self
。
选项 3 - 使用静态结构监视值更改
struct Struct {
var value: Int
var derivedValue: Int {
struct Static { static var previousValue: Int? }
if Static.previousValue == nil {
println("Setting previous value since it is nil")
Static.previousValue = value
}
if value != Static.previousValue! {
println("Doing expensive calculation")
Static.previousValue = value
}
return self.value * 2
}
init(value: Int) {
self.value = value
}
mutating func mutate() {
value = random()
}
}
这种方法允许您保留 "by value" 对象,同时还允许您延迟计算昂贵的值。不过,这里的主要问题是这仅适用于单个对象。如果您正在创建多个对象,这是一个糟糕的方法。
总结
不幸的是,这不是懒惰 属性 的有效用例。但是,还有其他方法可以解决此问题。希望其中一个就足够了。根据您提供的所有信息,我大胆猜测 选项 2 可能是您最好的选择。
使用嵌入式 class 绕过了改变结构的限制。这使您可以使用按值类型,该类型不会 运行 昂贵的计算直到需要它们,但之后仍会记住结果。
下面的示例 Number
结构以您描述的方式计算并记住其平方 属性。数学本身效率低得离谱,但它是说明解决方案的简单方法。
struct Number {
// Store a cache in a nested class.
// The struct only contains a reference to the class, not the class itself,
// so the struct cannot prevent the class from mutating.
private class Cache {
var square: Int?
var multiples: [Int: Int] = [:]
}
private var cache = Cache()
// Empty the cache whenever the struct mutates.
var value: Int {
willSet {
cache = Cache()
}
}
// Prevent Swift from generating an unwanted default initializer.
// (i.e. init(cache: Number.Cache, value: Int))
init(value: Int) {
self.value = value
}
var square: Int {
// If the computed variable has been cached...
if let result = cache.square {
// ...return it.
print("I’m glad I don’t have to do that again.")
return result
} else {
// Otherwise perform the expensive calculation...
print("This is taking forever!")
var result = 0
for var i = 1; i <= value; ++i {
result += value
}
// ...store the result to the cache...
cache.square = result
// ...and return it.
return result
}
}
// A more complex example that caches the varying results
// of performing an expensive operation on an input parameter.
func multiple(coefficient: Int) -> Int {
if let result = cache.multiples[coefficient] {
return result
} else {
var result = 0
for var i = 1; i <= coefficient; ++i {
result += value
}
cache.multiples[coefficient] = result
return result
}
}
}
这就是它的表现:
// The expensive calculation only happens once...
var number = Number(value: 1000)
let a = number.square // “This is taking forever!”
let b = number.square // “I’m glad I don’t have to do that again.”
let c = number.square // “I’m glad I don’t have to do that again.”
// Unless there has been a mutation since last time.
number.value = 10000
let d = number.square // “This is taking forever!”
let e = number.square // “I’m glad I don’t have to do that again.”
// The cache even persists across copies...
var anotherNumber = number
let f = anotherNumber.square // “I’m glad I don’t have to do that again.”
// ... until they mutate.
anotherNumber.value = 100
let g = anotherNumber.square // “This is taking forever!”
作为一个更现实的例子,我在日期结构上使用了这种技术,以确保日历系统之间转换的重要计算尽可能少运行。
我正在制作一个变异结构,其派生值的计算成本非常高。所以我想做的是懒惰地计算这个派生值并存储结果,直到结构再次发生变异,此时派生值不再有效,需要重新计算。
(失败)选项 1:已生成 属性
如果派生值是生成的属性(如下所示),总是会返回正确的值,但总是会重新计算。
(失败)选项 2:延迟加载 属性
如果它是惰性的 属性,则计算只进行一次……永远。因此,一旦结构发生变异,派生值就会错误,并且不会重新计算。此外,如果我从结构中分配一个常量值,我将无法访问 属性。
在Swift 1.2 中是否有任何可能的解决方案,或者我是否需要提交雷达?
struct Struct {
var value: Int
// Option 1: Generated property
var derivedValue: Int {
println("Doing expensive calculation")
return self.value * 2
}
// Option 2: Lazy property
lazy var derivedValue: Int = {
println("Doing expensive calculation")
return self.value * 2
}()
init(value: Int) {
self.value = value
}
mutating func mutate() {
value = random()
}
}
var test = Struct(value: 2)
test.derivedValue
test.derivedValue // If not lazy, expensive calculation is done again here
test.mutate()
test.derivedValue // If lazy, this has wrong value
let test2 = test
test2.derivedValue // Compiler error if using lazy implementation
这是一个非常有趣的问题。我有一些不同的想法可以提供帮助。
首先,您有点误用了 lazy
属性 的概念。你只能有 lazy stored properties 因为懒惰所做的只是延迟执行,直到它第一次执行。从那时起,该值就是 属性 中的 stored
。您正在处理不能以这种方式使用的计算 属性 。您当然可以提交雷达,但我认为这是一个失败的原因,因为您的用例在 IMO 中不是有效的惰性案例。
话虽如此,我认为您有几个选择。
选项 1 - 使用 Class 和 属性 观察者
class Calculator {
var value: Int {
didSet {
valueChanged = true
}
}
var valueChanged = false
var derivedValue: Int {
if valueChanged {
println("Doing expensive calculation")
valueChanged = false
}
return self.value * 2
}
init(value: Int) {
self.value = value
}
func mutate() {
value = random()
}
}
这里的优点是您仍然可以在调用 属性 时延迟计算 derivedValue
。缺点是您不再使用 "by value" 对象。
选项 2 - 在 Mutate 方法中计算昂贵的值
struct SortOfLazyCalculator {
var value: Int
var expensiveComputedValue: Int = 0 // just guessing
var derivedValue: Int {
return self.value * 2
}
init(value: Int) {
self.value = value
}
mutating func mutate() {
value = random()
expensiveComputedValue = random() // not sure what the expensive calculation is
}
}
这种方法的优点是你仍然可以保留你的 "by value" 对象,但你必须在突变时计算昂贵的值。您不能在 derivedValue
属性 中执行此操作,因为您不能在计算的 属性 中为结构更改 self
。
选项 3 - 使用静态结构监视值更改
struct Struct {
var value: Int
var derivedValue: Int {
struct Static { static var previousValue: Int? }
if Static.previousValue == nil {
println("Setting previous value since it is nil")
Static.previousValue = value
}
if value != Static.previousValue! {
println("Doing expensive calculation")
Static.previousValue = value
}
return self.value * 2
}
init(value: Int) {
self.value = value
}
mutating func mutate() {
value = random()
}
}
这种方法允许您保留 "by value" 对象,同时还允许您延迟计算昂贵的值。不过,这里的主要问题是这仅适用于单个对象。如果您正在创建多个对象,这是一个糟糕的方法。
总结
不幸的是,这不是懒惰 属性 的有效用例。但是,还有其他方法可以解决此问题。希望其中一个就足够了。根据您提供的所有信息,我大胆猜测 选项 2 可能是您最好的选择。
使用嵌入式 class 绕过了改变结构的限制。这使您可以使用按值类型,该类型不会 运行 昂贵的计算直到需要它们,但之后仍会记住结果。
下面的示例 Number
结构以您描述的方式计算并记住其平方 属性。数学本身效率低得离谱,但它是说明解决方案的简单方法。
struct Number {
// Store a cache in a nested class.
// The struct only contains a reference to the class, not the class itself,
// so the struct cannot prevent the class from mutating.
private class Cache {
var square: Int?
var multiples: [Int: Int] = [:]
}
private var cache = Cache()
// Empty the cache whenever the struct mutates.
var value: Int {
willSet {
cache = Cache()
}
}
// Prevent Swift from generating an unwanted default initializer.
// (i.e. init(cache: Number.Cache, value: Int))
init(value: Int) {
self.value = value
}
var square: Int {
// If the computed variable has been cached...
if let result = cache.square {
// ...return it.
print("I’m glad I don’t have to do that again.")
return result
} else {
// Otherwise perform the expensive calculation...
print("This is taking forever!")
var result = 0
for var i = 1; i <= value; ++i {
result += value
}
// ...store the result to the cache...
cache.square = result
// ...and return it.
return result
}
}
// A more complex example that caches the varying results
// of performing an expensive operation on an input parameter.
func multiple(coefficient: Int) -> Int {
if let result = cache.multiples[coefficient] {
return result
} else {
var result = 0
for var i = 1; i <= coefficient; ++i {
result += value
}
cache.multiples[coefficient] = result
return result
}
}
}
这就是它的表现:
// The expensive calculation only happens once...
var number = Number(value: 1000)
let a = number.square // “This is taking forever!”
let b = number.square // “I’m glad I don’t have to do that again.”
let c = number.square // “I’m glad I don’t have to do that again.”
// Unless there has been a mutation since last time.
number.value = 10000
let d = number.square // “This is taking forever!”
let e = number.square // “I’m glad I don’t have to do that again.”
// The cache even persists across copies...
var anotherNumber = number
let f = anotherNumber.square // “I’m glad I don’t have to do that again.”
// ... until they mutate.
anotherNumber.value = 100
let g = anotherNumber.square // “This is taking forever!”
作为一个更现实的例子,我在日期结构上使用了这种技术,以确保日历系统之间转换的重要计算尽可能少运行。