Swift Tip: Mixing and Matching Imperative and Functional Code
Swift is a hybrid language: it knows all the typical imperative control flow statements and supports mutable variables and properties; at the same time, Swift also supports typical functional programming features, like value types and first class functions.
In Swift, we often have the choice: do we want to express a piece of logic in an imperative or in a functional way?
As a simple example, we'll sum up the total width of an array of views:
var width: CGFloat = 0
for view in subviews {
width += view.frame.width
}
This is a straightforwardly imperative way to express this logic. However, we could also choose the functional alternative:
let width = subviews.reduce(0) { result, view in
result + view.frame.width
}
Both snippets do the same thing, with one small difference: in the imperative case we're left with a mutable width
variable after the calculation is done, whereas we have an immutable width
variable in the functional case.
To make the code a bit more interesting, we'll try summing up the total width of the views where isHidden
is false
. Once the logic becomes more complex, the alternative ways of expressing it start to diverge.
Here's the imperative version:
var width: CGFloat = 0
for view in subviews {
guard !view.isHidden else { continue }
width += view.frame.width
}
Of course, there are other ways of expressing this logic imperatively, for example using a where
clause:
for view in subviews where !view.isHidden {
width += view.frame.width
}
Let's try a functional version that accomplishes the same thing. One option is to first filter out the hidden views and then use reduce
to calculate the total width:
let visibleViews = subviews.filter { !$0.isHidden }
let width = visibleViews.reduce(0) { result, view in
result + view.frame.width
}
Alternatively, we can include the isHidden
check within the reduce function:
let width = subviews.reduce(0) { result, view in
view.isHidden ? result : result + view.frame.width
}
A nice feature of the previous version — first filtering, then reducing — is that the reduce
part of the logic is the same, no matter if we sum up the width of all views, only the even views, or any other subset of them. This sameness is a clear suggestion that the logic can be abstracted out, for example by using a computed property:
extension Array where Element == UIView {
var totalWidth: CGFloat {
return reduce(0) { result, view in result + view.frame.width }
}
}
Now, the total width of the even views is simply:
let width = visibleViews.totalWidth
Once again, the implementation of this extension and the extraction of the visible views could also be written imperatively:
extension Array where Element == UIView {
var totalWidth: CGFloat {
var result: CGFloat = 0
for view in self {
result += view.frame.width
}
return result
}
}
var visibleViews: [UIView] = []
for view in subviews where !view.isHidden {
visibleViews.append(view)
}
let width = evenViews.totalWidth
Swift allows mixing and matching of imperative and functional programming techniques, and often which technique you prefer is a matter of taste. We use both in our own code; for some problems we naturally use map
or reduce
, for other problems we write an imperative loop and aggregate the result as we go along.
No matter which you choose, breaking your problem down into smaller parts, as we have done with the totalWidth
extension above, is a very useful technique for both.
If you'd like to learn more about functional approaches in Swift, our book Functional Swift is a great place to start 🙂