Swift functions vs computed properties
https://softwareengineering.stackexchange.com/questions/304077
-
09-12-2020 - |
Question
Say I have have a class Event
as follows:
class Event {
private var attendees: [Person] = []
// Case 1
//*******
// Should I use a func…
func countOfAttendees() -> Int {
return attendees.count
}
// …or a var
var countOfAttendees: Int {
return attendees.count
}
// Case 2
//*******
// Should I use a func…
func countOfPaidAttendees() -> Int {
return attendees.filter({$0.hasPaid}).count
}
// …or a var
var countOfPaidAttendees: Int {
return attendees.filter({$0.hasPaid}).count
}
}
Is it best practice to use functions or computed properties in the 2 cases indicated above?
Solution
Follow the 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
To me, this means that I don't write funcs that take no arguments and return a value. I always use computed properties. That way, if I later decide to change the computed property into a stored property, I can do so without having the urge to remove the parens everywhere in my app and without having a separate "getter" method that just returns the value of a stored property, which seems pretty wasteful IMHO.
And if I change a stored property into a computed one, I don't have to add parens to the end of it, and everywhere that it is used in the app.
OTHER TIPS
I would say it depends on complexity of calculation vs. usage frequency.
- If it's
O(1)
/*
, then use computed property. - If it's
O(N)+
/rare-use
, then use function. - If it's
O(N)+
/frequent-use
, think whether in the future you might decide to use caching or other "smart" techniques to compensate for the complexity, if "yes" then use property, if "no-no-no, it's just heavy" then use function.
I recently started learning Kotlin and they have a great heuristic about when to use computed properties:
Functions vs Properties
In some cases functions with no arguments might be interchangeable with read-only properties. Although the semantics are similar, there are some stylistic conventions on when to prefer one to another.
Prefer a property over a function when the underlying algorithm:
- does not throw
- has a O(1) complexity
- is cheap to calculate (or caсhed on the first run)
- returns the same result over invocations
-- https://kotlinlang.org/docs/reference/coding-conventions.html
In Swift, functions without parameters and computed properties have almost the same capabilities (there may be a difference that a function without parameter is also a closure, while a computed property isn't).
The difference is semantically. If your code performs an action and returns for example a description of the outcome of that action, then I would use a function. If your code calculates a property but from the user's point of view this could have been a stored property, or maybe a stored property that requires updating some cached value first, then I would use a calculated property.
A big difference: What happens if you call the function or the calculated property twice? For a calculated property I expect that x = property; y = property has exactly the same behaviour as x = property; y = x except it might run a tiny bit slower. For functions, I wouldn't be surprised if the behaviour was different.
Use countOfAttendees
and countOfPaidAttendees()
.
A computed variable is one that returns a calculated value each time it is accessed. That is, it doesn’t store a value. Internally it is implemented as a function.
What is the difference with a function?
- Semantically, a variable is state, a function is an action.
- A function regulates access to private storage. A calculated variable may do the same in a more compact way. Example.
- A computed variable can be used with KVO, passed as a #keypath, and has facilities for observing: willSet, didSet.
You should use a variable when
- it does not throw
- it returns a simple property
- it doesn’t have a side effect or a verb in its name
- it’s O(1), that is, it doesn’t incur a significant cost. In your example it will be O(n).
- it is idempotent. Multiple identical invocations return the same value or set the object to the same state.
Irrelevant reasons to prefer a variable over a function
- A computed variable saves you from typing (). However, clarity is more important than brevity, so this is a weak argument.
- A read only variable can be overriden as read/write. A function indicates it is always read only. However, Apple uses properties for read-only variables like array.count. When in doubt seek consistency with the platform.
Resources
From WWDC 2014 - 204 What’s new in Cocoa > 24:40 When to use a @property
Use property for anything that is about the value or state of an object or its relationship to other objects. Bad candidates:
- Methods that do things: load, parse, toggle, …. They have verbs in its name.
- Generators: init, copy, enumerated, …. These methods are not idempotent.
- Methods which change state: nextObject.
From Swift Style by Erica Sadun > Computed Properties vs. Methods
A property expresses an inherent quality of an instance, while a method performs an action.
- Methods have parameters; properties don’t. Prefer methods for any call with side effects. If a method does something (for example, it loads, parses, toggles, or prints) or has a verb name, it should not be a property.
- Prefer properties for simple values that you can get and/or set.
- Properties should express a semantic intrinsic quality of a type instance.
- Properties allow you to add observers via willSet and didSet. Unlike stored instance properties, stored type properties must always be given a default value.
From Kotlin coding conventions > functions vs properties. See Daniel’s answer above.
Other resources with no relevant information:
I'd use a func
. Object-oriented programming works just fine without computed properties. Because you are getting a value back that was computed/filtered some may argue that a computed property feels right. But here's my complaint, if you do that then readability takes a hit, because it feels like a value.
In this context it wouldn't make sense to try to assign the computed value (and luckily the IDE helps us avoid this) but what if I try to assign something that is computed but looks like a value?
event.countOfAttendees = 0; // not possible
While using func the caller know's you aren't dealing with a value directly:
event.countOfAttendees()
I think if its a behavioral object it should look like it behaves rather than looking like a data structure. If your object is dumb and doesn't have any behavior then why try to encapsulate it? In that case you might as well just have attendees be public