Functional Programming in Swift [Beginner's Guide | Swift 5 | 2021]
Today, we’re going to take a look at functional programming and I’ll show you why it’s one of the best ways to write thread safe and readable code and how it differs from your more traditional imperative programming.
Imperative vs Functional Programming
Let’s say you wanted to create a function to double all of the numbers in an array. You might write something like this:
var input = [1,2,3,4,5] for i in 0..<input.count { input[i] = input[i] * 2 } // `input` now equals [2, 4, 6, 8, 10]
However, we’ve immediately run into a problem.
What happens if I wanted to access the original values in input
? We currently have no way of retrieving those values.
The code above is an example of imperative programming – a paradigm in which you execute statements that change the state of a program and its properties. In this case, by changing the input
array’s values.
Functional programming, instead, tries to prevent making any changes to the existing state of the application or introduce any side effects.
You’ll be able to find a more rigorous mathematical definition of functional programming here, but intuitively the goal is:
- Avoid mutability wherever possible.
- Use functions as the building blocks for functionality (in other words, combining functions together).
- Using pure functions where possible (a pure function is a function that will always produce the same result for the same input regardless of when and where it is being called).
Simply put, in imperative programming, changing the variable’s state and introducing side effects is permissible, in functional programming it is not.
Functional Programming Method in Swift
Let’s take a look at some examples in Swift.
You’ll notice that in all of these examples, the input
variable’s values are never changed allowing us to avoid mutability. Instead, these functions return an entirely new value rather than modifying the inputted one.
.map{}
// map: Applies a function to every element in the input and returns // a new output array. let input = [1, 2, 3, 4, 5, 6] // We'll take every number in the input, double it, and return it in a new // array. // // Notice that the input array is never modified. // // Output: [2, 4, 6, 8, 10, 12] // $0 refers to the current element in the input array `map` is operating on. let mapOutput = input.map { $0 * 2 }
.compactMap{}
// compactMap: Works the same way as map does, but it removes nil values. let input = ["1", "2", "3", "4.04", "aryamansharda"] // We'll try and convert each String in `input` into a Double. // // Notice that the input array is never modified and the values that resolve // to nil are skipped. // // compactMap is often used to convert one type to another. // Output: [1.0, 2.0, 3.0, 4.04] let compactMapOutput = input.compactMap { Double($0) }
.flatMap{}
// flatMap: Use this method to receive a single-level collection from an input // that may have some nesting. let input = [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]] // This will flatten our array of arrays structure into just a single array. // Output: [1, 2, 2, 3, 3, 3, 4, 4, 4, 4] let flatMapOutput = input.flatMap { $0 }
.reduce{}
// reduce: Allows you to produce a single value from the elements in // a sequence. let input = [1, 2, 3, 4] // This will add up all of the numbers in the sequence. // `sum` will be 10 let sum = input.reduce(0, { x, y in x + y }) // You can also write this more simply as: let sum = input.reduce(0, +) // `sum` will be 10
.filter{}
// filter: Returns an array containing, in order, the elements of the // sequence that satisfy the given constraint(s). let input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // This will only return the even numbers. // Output: [2, 4, 6, 8, 10] let filterOutput = input.filter { $0 % 2 == 0}
.forEach{}
When you use forEach
it is guaranteed to go through all items in sequence order, but map
is free to process items in any order.
// forEach: Calls the given closure on each element in the sequence in the // same order as a for-loop. let input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // This will print out the numbers in `input` just like a traditional for-loop. input.forEach { print($0) }
.sorted{}
// sorted: Returns the elements of the sequence, sorted using the given // condition as the comparison between elements. let input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // This will create an array with the numbers sorted in descending order. // Output: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] let sortedInput = input.sorted(by: { $0 > $1 })
Functional Programming with Custom Objects
These functions can be applied to objects of any type – even custom ones.
In addition, combining these calls can deliver impressive functionality for a minimal amount of code. We can use functional programming to return a list of valid calls that have a horsepower greater than 300 and are sorted by descending price all in one line:
// We'll create an array of Cars and specify some basic properties. struct Car { let name: String let horsepower: Int let price: Int } var cars = [Car?]() cars.append(Car(name: "Porsche 718", horsepower: 300, price: 60500)) cars.append(Car(name: "Porsche 911", horsepower: 379, price: 101200)) cars.append(nil) cars.append(Car(name: "Porsche Taycan", horsepower: 402, price: 79900)) cars.append(Car(name: "Porsche Panamera", horsepower: 325, price: 87200)) cars.append(nil) cars.append(nil) cars.append(Car(name: "Porsche Macan", horsepower: 248, price: 52100)) cars.append(Car(name: "Porsche Cayenne", horsepower: 335, price: 67500)) // Let's return valid cars (not nil) that have a horsepower greater than 300 // and are sorted by descending price. cars.compactMap { $0 }.filter { $0.horsepower > 300} .sorted { $0.price > $1.price }.forEach { print($0) } // Output Car(name: "Porsche 911", horsepower: 379, price: 101200) Car(name: "Porsche Panamera", horsepower: 325, price: 87200) Car(name: "Porsche Taycan", horsepower: 402, price: 79900) Car(name: "Porsche Cayenne", horsepower: 335, price: 67500)
Functional programming in iOS is becoming more and more mainstream and is critical to the SwiftUI and Combine ecosystems. Levering these language features and new paradigm correctly allows you to write optimized, thread-safe, easily testable, and readable code.
I hope you found this article useful. If you’d like to see more content like this consider checking out my YouTube channel about iOS and Swift Development.
This article is an excerpt from my book on iOS & Swift Development. Practical Tips for iOS Developers is a summary of everything I wish I knew when I was starting out and it’s completely free!