Accessing a lazy property on a struct mutates the struct - swift

I have a lazy property in a struct and every time I access it, it mutates the struct.
var numbers = [1,2,3]
struct MyStruct {
lazy var items = numbers
}
class MyClass {
var myStructPropery: MyStruct = MyStruct() {
didSet {
print(myStructPropery)
}
}
}
var myClass = MyClass()
myClass.myStructPropery.items
myClass.myStructPropery.items
myClass.myStructPropery.items
Result:
Print (didSet) will be called every time.
The ideal behaviour should be first time mutation only (since that's how lazy variables behave). Is this a bug in Swift or am I doing something wrong?

What you are seeing is is that the observed property items has notified its observer that it has been mutated because it was accessed, and a lazy variable is by definition mutating since it will be set at a later time. Therefore didSet gets called to handle this.
The lazy variable is actually only set once even though it signals that it has mutated and the property myStructPropery is only mutated once when the variable is first set but is the same instance after that.
Here is how we can verify this, first change the lazy var declaration so it's more like how we usually declare such a variable
lazy var items: [Int] = { numbers }()
and then add a print statement
lazy var items: [Int] = {
print("inside lazy")
return numbers
}()
If we now run the test code
var myClass = MyClass()
myClass.myStructProperty.items
myClass.myStructProperty.items
myClass.myStructProperty.items
we see that "inside lazy" only prints once. To verify that the property myStructProperty isn't changed we can make the struct conform to Equatable and perform a simple check inside didSet
didSet {
if oldValue != myStructProperty {
print(myStructProperty)
}
}
Now running the test we see that the print inside didSet is never executed so myStructProperty is never changed.
I have no idea if this behaviour is a bug but personally it feels like it might be complicate for the property observer to stop observing a lazy property once it was accessed or for a lazy var to not be defined as mutating once it is set.

I started debugging this with following set up -
var numbers = [1,2,3]
struct MyStruct {
lazy var items = numbers
}
class MyClass {
var myStructPropery: MyStruct = MyStruct() {
didSet {
// Changed this to make sure we are not invoking getter here
print("myStructPropery setter called")
}
}
}
let myClass = MyClass()
myClass.myStructPropery.items
myClass.myStructPropery.items
myClass.myStructPropery.items
I can reproduce the problem on Xcode 12.5 using Swift 5.4.
Attempt 1 : Turn var numbers into let numbers - Does NOT work.
let numbers = [1,2,3]
Attempt 2 : Assign the value inline without using an extra variable - Does NOT work.
struct MyStruct {
lazy var items = [1,2,3]
}
Attempt 3 : Assign the value inline using the full blown getter syntax - Does NOT work.
struct MyStruct {
lazy var items: [Int] = {
return [1,2,3]
}()
}
At this point, we are out of options to try. Even though we can clearly see that return [1,2,3] in the last attempt is executed exactly once, the MyClass.myStructPropery.modify is called repeatedly on access to items.
Maybe Swift Forums is a better place to discuss this.

Related

Why does this compile without mutating keyword?

I feel like something is broken with the value semantics here. Consider:
struct A {
var b = 0
mutating func changeB() {
b = 6
}
}
struct B {
var arr = [A]()
func changeArr() {
/* as expected this won't compile
unlss changeArr() is mutating:
arr.append(A())
*/
// but this compiles! despite that changeB is mutating!
for var a in arr {
a.changeB()
}
}
}
Why can this example mutate the struct contents without marking the function as mutating? In true value semantics, any time you change any part of the value, the whole value should be considered changed, and this is usually the behavior in Swift, but in this example it is not. Further, adding a didSet observer to var arr reveals that changeArr is not considered mutation of the value.
for var a in arr {
a.changeB()
}
This is copying an element from arr out into a and leaving arr unchanged.
If you directly access the elements inside arr via their indexes, then it will mutate and require the mutating keyword.
The reason changeArr is not mutating is because it isn't really doing anything since it is working on local copies of the A objects. If you really want the method to do something meaningful it needs to be changed to
mutating func changeArrForReal() {
for index in arr.indices {
arr[index].changeB()
}
}

Swift memoizing/caching lazy variable in a struct

I drank the struct/value koolaid in Swift. And now I have an interesting problem I don't know how to solve. I have a struct which is a container, e.g.
struct Foo {
var bars:[Bar]
}
As I make edits to this, I create copies so that I can keep an undo stack. So far so good. Just like the good tutorials showed. There are some derived attributes that I use with this guy though:
struct Foo {
var bars:[Bar]
var derivedValue:Int {
...
}
}
In recent profiling, I noticed a) that the computation to compute derivedValue is kind of expensive/redundant b) not always necessary to compute in a variety of use cases.
In my classic OOP way, I would make this a memoizing/lazy variable. Basically, have it be nil until called upon, compute it once and store it, and return said result on future calls. Since I'm following a "make copies to edit" pattern, the invariant wouldn't be broken.
But I can't figure out how to apply this pattern if it is struct. I can do this:
struct Foo {
var bars:[Bar]
lazy var derivedValue:Int = self.computeDerivation()
}
which works, until the struct references that value itself, e.g.
struct Foo {
var bars:[Bar]
lazy var derivedValue:Int = self.computeDerivation()
fun anotherDerivedComputation() {
return self.derivedValue / 2
}
}
At this point, the compiler complains because anotherDerivedComputation is causing a change to the receiver and therefore needs to be marked mutating. That just feels wrong to make an accessor be marked mutating. But for grins, I try it, but that creates a new raft of problems. Now anywhere where I have an expression like
XCTAssertEqaul(foo.anotherDerivedComputation(), 20)
the compiler complains because a parameter is implicitly a non mutating let value, not a var.
Is there a pattern I'm missing for having a struct with a deferred/lazy/cached member?
Memoization doesn't happen inside the struct. The way to memoize is to store a dictionary off in some separate space. The key is whatever goes into deriving the value and the value is the value, calculated once. You could make it a static of the struct type, just as a way of namespacing it.
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
The only way I know to make this work is to wrap the lazy member in a class. That way, the struct containing the reference to the object can remain immutable while the object itself can be mutated.
I wrote a blog post about this topic a few years ago: Lazy Properties in Structs. It goes into a lot more detail on the specifics and suggest two different approaches for the design of the wrapper class, depending on whether the lazy member needs instance information from the struct to compute the cached value or not.
I generalized the problem to a simpler one: An x,y Point struct, that wants to lazily compute/cache the value for r(adius). I went with the ref wrapper around a block closure and came up with the following. I call it a "Once" block.
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)")
I made the choice to have the OnceBlock take an input, because otherwise initializing it as a function that has a reference to self is a pain because self doesn't exist yet at initialization, so it was easier to just defer that linkage to the cache/call site (the var r:Float)

Can a struct have lazy properties [instantiation] in Swift?

Can a struct have lazy properties [instantiation] in Swift?
I cannot find any documentation saying yes or no. Everything only uses classes as examples.
If one can, can an example be found anywhere?
Thanks
Stan
Yes a struct can have a lazy property. Consider this example:
class Stuff {
var stuff: Int
init(value: Int) {
print("Stuff created with value \(value)")
stuff = value
}
}
struct HasLazy {
lazy var object = Stuff(value: 1)
var object2 = Stuff(value: 2)
}
func testIt() {
print("in testIt")
var haslazy = HasLazy()
print("done")
haslazy.object.stuff = 17
print("\(haslazy.object.stuff)")
print("final")
}
testIt()
Output:
in testIt
Stuff created with value 2
done
Stuff created with value 1
17
final
Notice that the property marked lazy is not initialized until after "done" prints when the property is first accessed.
See it in action here, and then try it without the lazy keyword.

Swift computed property to return copy of underlying array

I have a model class written in Objective-C that I'm converting to Swift. It contains an NSMutableArray internally, but the method signature for the getter, as well as the actual return value, are NSArray. When called, it creates an immutable copy to return.
Essentially, I want callers to be able to iterate/inspect the container, but not modify it. I have this test snippet:
class Container {
internal var myItems = [String]()
func sayHello() {
"I have: \(myItems)"
}
}
let cont = Container()
cont.myItems.append("Neat") // ["Neat"]
cont.sayHello() // This causes sayHello() to print: "I have: [Neat]"
var isThisACopy = cont.myItems
isThisACopy.append("Huh") // ["Neat", "Huh"]
cont.sayHello() // This ALSO causes sayHello() to print: "I have: [Neat]"
I've been trying to find a way to override the getter for myItems so that it returns an immutable copy, but can't seem to determine how.
Attempt #1
This produces a compiler error: Function produces expected type '_ArrayBuffer<(String)>'; did you mean to call it with '()'?
internal var myItems = [String]() {
var copy = [String]()
for item in ... { // What to use in the ...?
copy.append(item)
}
return copy
}
Attempt #2
This also produces a compiler error, because I'm (understandably) redefining the generated getter Invalid redeclaration of 'myItems()':
internal func myItems() -> [String] {
var copy = [String]()
for item in myItems {
copy.append(item)
}
return copy
}
Try this:
class Container {
private var _myItems: [String] = ["hello"]
internal var myItems: [String] {
return _myItems
}
}
let cont = Container()
cont.myItems.append("Neat") //not allowed
It uses a private stored property and a computed property that returns an immutable copy. It's not possible for a stored property to use custom getters.
A better way to expose mutable properties as immutable:
class Container {
private (set) internal var myItems: [String]
}

Optional chaining and Array in swift

Let's take these two simple classes to show my problem:
class Object{
var name:String?
// keep it simple... and useless
}
class TestClass {
var objects:AnyObject[]?
func initializeObjects (){
objects?.insert(Object(), atIndex:0) // Error
objects?.insert(Object(), atIndex:1) // Error
objects?.insert(Object(), atIndex:2) // Error
}
}
With this implementation I get 3 errors Could not find member 'insert' where I try to add object into the objects array.
Now, if I remove the optional from objects definition and the optional chain in initializeObjects it works with no problem (here the working code)
class Object{
var name:String?
}
class TestClass {
var objects:AnyObject[] = AnyObject[]() // REMOVE optional and initialize an empty array
func initializeObjects (){
objects.insert(Object(), atIndex:0) // Remove Opt chaining
objects.insert(Object(), atIndex:1) // Remove Opt chaining
objects.insert(Object(), atIndex:2) // Remove Opt chaining
}
}
I can't understand what is wrong in the first implementation.
I thought it checks with objects? if objects is not nil and at this point it adds an element using insert:atIndex:. But I'm probably wrong -.-
Arrays in Swift are structs and structs are value types.
Optionals in Swift are actually enums (Optional<T> or ImplicitlyUnwrappedOptional<T>).
When you are unwrapping an optional (implicitly or explicitly) of a value type, what you get is actually a constant copy of the struct. And you can't call mutating methods on a constant struct.
Executing objects?.insert(Object(), atIndex:0) basically means this:
if let tmp = objects {
tmp.insert(Object(), atIndex:0)
}
As a workaround, you need to assign the unwrapped value to a variable and then assign the variable back to your optional property. That's how value types work.
This is reproducible for any struct, not only Arrays:
struct S {
var value: Int = 0
}
var varS: S = S()
varS.value = 10 //can be called
let constS: S = S()
constS.value = 10 //cannot be called - constant!
var optionalS: S? = S()
optionalS?.value = 10 //cannot be called, unwrapping makes a constant copy!
//workaround
if optionalS {
var tmpS = optionalS!
tmpS.value = 10
optionalS = tmpS
}
Some relevant discussion here: https://devforums.apple.com/thread/233111?tstart=60