Suppose you have a protocol to describe types capable of performing some statistical sampling, as follows,
public protocol Sampling: CustomStringConvertible
{
var size: Int {get}
var total: Double {get}
var minimum: Double {get}
var maximum: Double {get}
var mean: Double {get}
var variance: Double {get}
var standardDeviation: Double {get}
mutating func updateWithNewValue(x: Double)
}and a struct type that conforms to that protocol:
public struct StatisticalSample: Sampling
{
public init() {}
public private(set) var size = 0
public private(set) var total: Double = 0
public private(set) var minimum: Double = 0
public private(set) var maximum: Double = 0
public var mean: Double { return newMean }
public var variance: Double { return newVar }
public var standardDeviation: Double
{
if !cachedStdDevIsValid
{
cachedStdDev = sqrt(newVar)
cachedStdDevIsValid = true
}
return cachedStdDev
}
public var description: String
{
return "[sample size: \(size), total: \(total), min: \(minimum), max: \(maximum)," +
" mean: \(mean), var: \(variance), std: \(standardDeviation)]"
}
public mutating func updateWithNewValue(x: Double)
{
let n = Double(size)
let np1 = Double(size+1)
total += x
newMean = (x + n * oldMean) / np1
if size > 0
{
let nm1 = Double(size-1)
let xsq = x * x
let oldMeanSq = oldMean * oldMean
let newMeanSq = newMean * newMean
newVar = oldMeanSq + (xsq + nm1 * oldVar - np1 * newMeanSq) / n
minimum = min(x, minimum)
maximum = max(x, maximum)
}
else
{
minimum = x
maximum = x
}
oldMean = newMean
oldVar = newVar
size += 1
}
private var oldMean: Double = 0
private var newMean: Double = 0
private var oldVar: Double = 0
private var newVar: Double = 0 { didSet { cachedStdDevIsValid = false } }
private var cachedStdDevIsValid = false
private var cachedStdDev: Double = 0
}The above will not compile, because standardDeviation: Double is implemented as a computed property and its implementation is mutating the instance. Now, of course, I could use a class instead of a struct and be done with it. In fact, that's how my code was to begin with (and with no protocol) but it occurred to me that StatisticalSample is a good candidate for a value type so I thought I'd turn it into a struct.
I understand why computed properties should not be allowed to mutate the value-type instances they belong to but, in this example and other cases of dealing with cached properties, the mutation is an implementation detail that the client code doesn't ever need to worry about. There is no way in the code above that the client code could accidentally, or even intentionally, corrupt the internal state of the instance because of the mutation.
Alternatively, I could change the protocol so that standardDeviation: Double is no longer a property, but a method,
public protocol Sampling: CustomStringConvertible
{
var size: Int {get}
var total: Double {get}
var minimum: Double {get}
var maximum: Double {get}
var mean: Double {get}
var variance: Double {get}
mutating func standardDeviation() -> Double
mutating func updateWithNewValue(x: Double)
}but that's asymmetrical, confusing, and ugly. It's asymmetrical because it treats differently what would otherwise be similar elements (variance and standard deviation) and it's confusing to users of the protocol because it raises the questions of why they should be treated differently, why the standard deviation method should be mutating, and what exactly it is that it's mutating. Since the mutation associated with the computation of the standard deviation is an implementation detail with no effects visible to the client, it should not be exposed to the client. Compare it to the mutation associated with the update method. That is exposed to client as part of the protocol - and should be - because that mutation is not 'internal' to the type.
It seems to me that there are situations (such as this) where it should be possible to implement a computed property which is also mutating, in a value type. Again, in this particular case, I could go back to using a class and move on but what about cases where using a class is not desirable?