How to make a lazy derived property in a mutating structure in Swift?

I am creating a mutating structure with a really expensive computed value. So I want to do this in order to calculate this derived value lazily and save the result until the structure is mutated again, after which the derived value ceases to be valid and needs to be recalculated.

(failed) Option 1: Generated property

If the derived value is a generated property (as shown below), the correct value is always returned, but always recalculated.

(crash) Option 2: Lazy-loaded property

If this is a lazy property, the calculation is done only once ... ever. Therefore, when the structure is mutated, the derived value is erroneous and will not be recounted. Also, I cannot access the property if I assign a constant value from the structure.

Is there any possible solution in Swift 1.2 or do I need to file a radar?

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 
+6
source share
2 answers

Using the built-in class bypasses the restrictions on mutation of the structure. This allows you to use the type by value, which does not run expensive calculations until they are needed, but still remembers the result later.

The Number struct example below calculates and remembers its square property in a way that behaves the same as you describe. Math itself is ridiculously inefficient, but it's a simple way to illustrate a solution.

 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. // (ie 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 } } } 

And here is how it works:

 // 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!" 

As a more realistic example, I used this method in date structures to make sure that nontrivial calculations for conversion between calendar systems are done as little as possible.

+3
source

This is a really interesting question. Here I have a few different ideas that could help.

First, you are slightly abusing the idea of ​​a lazy property. You may have lazy stored properties , because all that is lazy is the execution of the delay until it is first run. This value is then stored in the property next. You are dealing with a computed property that cannot be used in this way. You can, of course, file a radar, but I think this is a lost reason, because your use case is not a valid IMO lazy case.

With that said, I think you have several options.

Option 1 - Using a Class with Property Observers

 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() } } 

The advantage is that you can still lazily compute derivedValue at the point at which the property is called. The disadvantage is that you no longer use the "by value" object.

Option 2 - Calculation of the Value in the Mutate Method

 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 } } 

The advantage of this approach is that you can still save your object "by value", but you need to calculate an expensive value during the mutation. You cannot do this inside the derivedValue property, because you cannot mutate self inside the computed property for the structure.

Option 3 - Using Static Struct to Track Value Changes

 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() } } 

This approach allows you to save the object "by value", and also allows you to lazily calculate an expensive value. However, the main problem is that this will only work for one object. If you create multiple objects, this is a bad approach.

Summary

Unfortunately, this is an invalid use case for a lazy property. However, there are other approaches to solving this problem. I hope one of them will be enough. Based on all the information you provided, I would venture to suggest that Option 2 is probably the best choice.

+3
source

Source: https://habr.com/ru/post/983430/


All Articles