The Slow Shift to Swift: Calculated Properties

Aug 1, 2015

One of the biggest hurdles with object oriented programming, specifically when dealing with reference as opposed to value, is mutating state and variability.

Consider the following.

class Eagle {
    var feathers = [Feather]()
    var color: String {
        if feathers.count > 0 {
            return feathers[0].color
        }
        return “nothing”
    }
}

class Feather {
    var color = “Brown”
}

let eagle = Eagle()
// eagle.color = “nothing”

let feather = Feather()
eagle.feathers = [feather]
// eagle.color = “Brown”

feather.color = “Yellow”
// eagle.color = “Yellow”

Notice we are able to change the color of the Eagle by changing the color of a single feather, without letting the Eagle know its color got changed?

This is the mutating state problem within object oriented programming.

Basically, the Feather we changed to yellow is not a new Feather, with a new color, instead it is the same feather; therefore, it is possible to mutate a property under our Eagle, without the Eagle being aware.

In my coaching practice I have come across many instances where we try to remove variability et al, as if all variability is bad, “More standard operating procedures!” some scream. However, if we look at variability as natural, sometimes desirable, or even as the basic requirement of innovation, we open ourselves up to a whole new realm of possibilities.

In the example above, if we had a knee-jerk reaction to the problem, we might identify the root problem as relying on the Feather to tell the world what color the Eagle is.

Pretty easy to solve.

class Eagle {
    var feathers = [Feather]()
    var color: String = “nothing”
}

class Feather {
    private(set) var color = “nothing”
    init(color: String) {
        self.color = color

    }
}

let eagle = Eagle()
var feather = Feather()

feather.color = “Brown”
// feather.color = “Brown”

eagle.feathers = [feather]
eagle.color = “Brown”
// eagle.color = “Brown”

feather = Feather(“Yellow”)
eagle.feathers = [feather]

print(eagle.color)
// eagle.color = “Brown”

But now we have a different problem. The returned value for color doesn’t actually match the truth; we have a yellow feather on an Eagle who insists its color is brown. Further, we would require more knowledge of the system to know that every time we update the feathers, we should update the color of the Eagle.

So, why not just do this?

class Eagle {
    var feathers = [Feather]() {
        didSet {
            if feathers.count > 0 {
                color = feathers[0].color

            } else {
                color = “nothing”

            }
        }
    }
    var color: String = “nothing”
}

class Feather {
    var color = “nothing”
}

That is an alternative, and takes advantage of Swift’s ability to let us in on the mutation lifecycle.

However, we’re back where we started; three variables in total that can be mutated without knowledge or consent. Further, if we get unexpected results, we have to find where color is being set instead of going straight to color.

So, let’s try to reason about what we’re really doing here.

  1. We want to have an Eagle.
  2. The Eagle should have one or more Featherss.
  3. The Feathers dictate the overall color of the Eagle.
  4. All Feathers are of the same color.
  5. Just for kicks, let’s allow changing the color of the Eagle.
  6. However, we should not be allowed to change the color of a Feather already on the Eagle; thereby, changing the color out from under the Eagle.
  7. And, as maintainers of this code, we don’t want to have to go searching if something goes wrong with the color of the Eagle.
class Eagle {
    private(set) var feathers = [Feather]()
    var color: String {
        if feathers.count > 0 {
            return feathers[0].color

        }
        return “nothing”
    }

    init(feathers: [Feather]) {
        self.feathers = feathers
    }

    func changeColorTo(color: String) {
        var newFeathers = [Feather]()
        for feather in feathers {
            let newFeather = Feather(color: color)
            newFeathers.append(newFeather)

        }
        feathers = newFeathers
    }
}

class Feather {
    private(set) var color = “nothing”
    init(color: String) {
        self.color = color
    }
}

var feather = Feather(color: “Brown”)
// feather.color = “Brown”

let eagle = Eagle(feathers: [feather])
// eagle.color = “Brown”

feather.color = “Yellow”
// does not compile

eagle.changeColorTo(“Light brown”)
// eagle.color = “Light brown”
  1. We’ve made it so we can only set the color of a Feather at initialization, and cannot be changed from the outside; therefore, each Feather becomes the holder of its own truth.
  2. We’ve made it so we can only set the Feathers of the Eagle at initialization and cannot be changed from the outside; therefore, each Eagle is the holder of its own truth.
  3. We cannot change the color of the Eagle despite the Feathers not being that color, because we are asking for ultimate truth from the proper source (Feather color dictates Eagle color).
  4. Further, we can ask the Eagle to change colors (and it will) without having the maintenance or knowledge overhead of knowing we have to set the color property on the Eagle (and we wind up with the same number of feathers as before).
  5. Finally, as developers, we can see the color property is derived from the Feathers; therefore, if the Eagle isn’t the right color, know to look at the Feathers.

The Search for Truth

Variability comes from external forces being able to act on an object without having to communicate the desired change on that object.

When we use a calculated property we run into the problem that if outside forces act on the external source object directly without our knowledge, consent, or a way of handling that potentiality, we end up in an unknown state.1 This unknown state breaks our expectations. Unmet expectations are the root of disappointment.

So, before reacting to variability in a knee-jerk fashion, try to take a step back and assess: What are you really trying to accomplish?

Perfect time for protocols.

Protocols

Protocols are contracts objects can opt in to, which informs other objects and the compiler what to expect. In the case of our Eagle and Feather, and based on our reasoning, we might get something like this.

protocol Bird {
    var feathers: [Feather] { get }
    var color: String { get }

    func changeColorTo(color: String)
}

protocol FeatherProtocol {
    var color: String { get }
}

Now, any object could become a basic Bird; all it has to do is sign up for the protocol and implement the requisite behavior. Further, we might create another protocol called BirdOfPrey, which inherits from the basic Bird protocol; thereby, requiring implementation of properties and methods from the BirdOfPrey protocol and the Bird protocol.

Working this way helps to remove us from the inheritance problem of object oriented programming, but that’s a different article; that I am not saying I will write.

However, notice the protocols do not go to the implementation level; or, even details like access control (this may change)?

Instead, the protocol just says, you need to have these properties and methods, and I don’t care what that looks like to you. When you get into a situation where you do want the system to dictate the implementation of a method or property, that’s where inheritance and the use of the final keyword comes into play; think of it as the my-way-or-the-highway of object oriented programming.


  1. Imagine being let go from your job, but never being told about it, a fixed glitch, that causes a glitch: Office Space - fixed the glitch return to text

###