One of the great features of Swift’s collection implementation is the first party support of various “higher order” functions like map, filter and reduce. When Objective-C still reigned supreme, the iOS open-source community created their own abstractions in an attempt to make Objective-C more functional. While the community did a wonderful job of making a verbose language more terse, it never really felt like Objective-C was meant for it. This time around however, Swift gives us built in support through the SequenceType protocol. The Swift Developer Library defines SequenceType as being any “type that can be iterated with a for…in loop.” This means that any collection that can be iterated over (i.e. Array, Set) can adopt the SequenceType protocol, gaining access to its useful top-level functions. You can even adopt SequenceType within your own custom collections!
The SequenceType protocol aims to solve a number of common problems in a functional and expressive way, making your code more readable and succinct. Here’s a rundown of what SequenceType has to offer:
Say we start with a standard Swift array like so.
var numbers = [10,2,60,100]
// [10,2,60,100]
After passing this collection around our app, we need to manipulate it by adding a ‘$’ sign before each number. Easy, let’s use Map.
.map({"$\($0)"})
numbers// ["$10","$2","$60","$100"]
What’s happening here is that the whole ‘numbers’ array is iterated over, appending each number into a string along with a ‘$’ sign. The ‘$0’ is shorthand in Swift for the first argument to this closure. Let’s contrast this to what it would look like without Map.
var money = [String]()
for number in numbers {
.append("$\(number)")
money}
// ["$10","$2","$60","$100"]
Way more verbose. While this is still understandable and standard code, I don’t like the thought process of it. This naive approach I tend to read as “Initialize an array called money that accepts String types, then iterate over each number in the numbers array appending a string that concatenates $ and number”. Bleh. The Map version on the other hand can be read “for each number in numbers, map $ and number to a string”.
Our goal while programming should always be to write code that is easily understood. The Map function lets us easily do this by clearly conveying intent, while still remaining as succinct as possible. Lets take a look at the other instance methods of the SequenceType protocol.
Need to “filter” your array by a comparator? Use Filter.
.filter({$0 > 10})
numbers// [60, 100]
What about “reducing” each number in my array to a single sum? Go use Reduce.
.reduce(0, combine: {$0 + $1})
numbers// 172 (i.e. 10 + 2 + 60 + 100)
This can even be further “reduced” since operators are also methods in Swift.
.reduce(0, combine: +)
numbers// 172 (i.e. 10 + 2 + 60 + 100)
Lets drop the first number in numbers
.dropFirst()
numbers// [2, 60, 100]
Woops, I meant the last.
.dropLast()
numbers// [10, 2, 60]
Nevermind, the first two actually
.dropFirst(2)
numbers// [60, 100]
Can we check if the number 2 exists in the array? Sure.
.contains(2)
numbers// true
What about 10000?
.contains(10000)
numbers// false
Darn, lets see if this other array matches our good old number array.
.elementsEqual([10,2, 60, 100])
numbers// true
Woo! What about this array?
.elementsEqual([10])
numbers// false
Shucks. Can we “join” all of our numbers together into one String? Sure, but only if they are Strings in the first place.
let numberStrings = numbers.map({"\($0)"}) // Map to string values
.joinWithSeparator(" | ") // then join
numberStrings// "10 | 2 | 60 | 100"
You know what, lets just print all of our numbers out, since SequenceType functions don’t overwrite our mutable data.
.enumerate()
numbers// Outputs:
// 0 : 10
// 1 : 2
// 2 : 60
// 3 : 100