Computed Properties vs Functions – Why Does It Matter?

by

Cookies

What’s your favorite chocolate chip cookie recipe? I bet you could ask that question to 5 different people and get 5 totally different recipes… brown sugar vs white sugar, cake flour vs all purpose, dark chocolate vs milk chocolate. All of these recipes result in a chocolate chip cookie but the process by which we get there is a matter of personal preference.  If you were to ask multiple developers to solve a problem, it’s doubtful that any two developers write identical code.  It’s not that any one solution is necessarily better than the others… the resulting code is likely just a matter of personal preference.

I started seeing a pattern during code reviews on a recent project.  Quite often I would see something along the lines of “Can you make this a computed property instead of a function?”.  At first I just thought it was a matter of personal preference until I started seeing a variation of that comment over and over again.  I thought there’s got to be a reason WHY computed properties are preferred to functions even though on the surface each would appear to achieve the same end result. In my search to understand why one solution appears to be overwhelmingly preferred, I was led to Bertrand Meyer’s Uniform Access Principle:

All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation.

Consider that we have a CookieFactory class that we can initialize with an array of ingredients. We would need an Ingredient class. Using Swift it would look something along the lines of:

enum UnitOfMeasure {
    case gram
    case ounce
}

class Ingredient {
    let name: String
    let unitOfMeasure: UnitOfMeasure
    let amount: Float
    init(name: String, unitOfMeasure: UnitOfMeasure, amount: Float) {
        self.name = name
        self.unitOfMeasure = unitOfMeasure
        self.amount = amount
    }
}

What if our CookieFactory wanted to know if its ingredients were using Imperial or Metric system of measurement? That information should probably live within our UnitOfMeasure enum. However for demonstration purposes I’m going to add it as a function into the Ingredient class:

func systemOfMeasurement() -> SystemOfMeasure {
    switch unitOfMeasure {
    case .gram: return .metric
    case .ounce: return .imperial
    }
}

Or as a computed property:

var systemOfMeasurement: SystemOfMeasure {
    switch unitOfMeasure {
    case .gram: return .metric
    case .ounce: return .imperial
    }
}

Both options implement systemOfMeasurement through computation and they both return the same thing – WHO CARES?!  Think about how we would access this value.

  • As a function: myIngredient.systemOfMeasurement()
  • As a computed propertymyIngredient.systemOfMeasurement

What if in the future we choose to change systemOfMeasurement from a computed to a stored value? If we originally chose the function implementation we’d have to go through our code and remove a whole bunch of parenthesis. Sure, plenty of IDEs would make this a quick task but really it’s a task that doesn’t need to exist.  If we had implemented systemOfMeasurement as a computed property, no changes would need to be made.

If functions can be written as computed properties and be accessed through the same notation as a stored value, why would we ever write a function?

If any call is considered to be expensive, can throw an error, or returns different results when invoked multiple times – a function is preferred. If a call is cheap, does not throw errors, returns the same result or caches the result of its first invocation – a computed property will probably suit your needs. For instance, if we wanted our CookieFactory class to bake some cookies we would need it to look at the ingredients and decide what kind of cookies to make. It would need to check how many ingredients were available to it in order to determine a batch size.  It would then need to portion and create the cookies. Sounds complex, so a function would be the way to go here. A delicious, chocolatey function…

Leave a Reply

Your email address will not be published. Required fields are marked