I wrote two versions of code for a Swift programming exercise from an online programming exercise website. The exercise is as follows:
Find the difference between the square of the sum and the sum of the squares of the first N natural numbers. The square of the sum of the first ten natural numbers is (1 + 2 + ... + 10)² = 55² = 3025. The sum of the squares of the first ten natural numbers is 1² + 2² + ... + 10² = 385. Hence the difference between the square of the sum of the first ten natural numbers and the sum of the squares of the first ten natural numbers is 3025 - 385 = 2640.
The code for the first version is:
struct Squares {
let squareOfSum : UInt64
let sumOfSquares : UInt64
let differenceOfSquares : UInt64
init(_ number: UInt64) {
var sum = ((1 + number) * number) / 2
squareOfSum = sum * sum
sum = 0
for i in 1...number {
sum = sum + (UInt64)(i * i)
}
sumOfSquares = sum
differenceOfSquares = squareOfSum - sumOfSquares
}
}
let sqrs = Squares(5)
print(sqrs.differenceOfSquares)
It passes compilation and runs without problem.
The code for the second version is:
struct Squares {
let num : UInt64
lazy var squareOfSum: UInt64 = {
return ((1...num).reduce(0, +)).squared
}()
lazy var sumOfSquares: UInt64 = {
return ((1...num).map{$0.squared}).reduce(0, +)
}()
lazy var differenceOfSquares: UInt64 = {return squareOfSum - sumOfSquares }()
init(_ number: UInt64) {
num = number
}
}
extension Numeric {
var squared: Self {
return self * self
}
}
let sqrs = Squares(5)
print(sqrs.differenceOfSquares)
When the code is compiled, the compiler produces the following messages:
/tmp/0C9C2EC3-543D-486F-BCB0-D181EC48DC82.8NCVch/main.swift:25:7: error: cannot use mutating getter on immutable value: 'sqrs' is a 'let' constant
print(sqrs.differenceOfSquares)
^~~~
/tmp/0C9C2EC3-543D-486F-BCB0-D181EC48DC82.8NCVch/main.swift:24:1: note: change 'let' to 'var' to make it mutable
let sqrs = Squares(5)
^~~
var
If I change let
into var
for sqrs
, as suggested by the compiler, the code will run well. Alternatively, if I change the type struct
into class
to make the object mutable, the code will also run well. However, in the code in my first version, there's not problem with the combination of a struct
object and a let
mutability delimiter. How come in the second version, these two become incongruent? While I don't know why, I suspect that it may have to do with the lazy initializers in the second version.
Can anyone please help explain this? Thank you very much in advance.
Lazy properties have mutating getters, i.e. accessing a lazy property will potentially mutate the struct. Why? Think about the first time you access a lazy property. Before the first access, that property has no value, after you access it, the expression on the right of =
is evaluated and assigned to the property. Now the property has a value. This is how accessing a lazy property can change the struct. As a result, you need a var
to access lazy properties.
Changing the struct into a class also helps, because now sqrs
stores a reference, and let sqrs
only makes the reference to the object constant, rather than making the object itself constant.
The first version initialises all the properties in the initialiser. You are allowed to assign to each let
property once in the initialiser, which is what the first version did. And since the properties are not lazy in the first version, you can access them with a let
constant.