Swift’s grand entrance to the programming world at WWDC in 2014 was much more than just an introduction of a new language. It brought a number of new approaches to software development for the iOS and macOS platforms.
This tutorial focuses on just one of these methodologies — Functional Programming or FP for short — and you’ll get an introduction to a broad range of functional ideas and techniques used in FP.
Update Note: This tutorial has been updated to Swift 3 by Niv Yahel. The original tutorial was written by Joe Howard.
Getting Started
Create a new playground in Xcode so you can follow along with the tutorial. Keep in mind that this tutorial will cover FP at a high level, so thinking about the concepts through the filter of a real world situation will be helpful. In this case, imagine you’re building an app for an amusement park, and that the park’s ride data is provided by an API on a remote server.
Start by adding the following to your playground:
enum RideCategory: String { case family case kids case thrill case scary case relaxing case water } typealias Minutes = Double struct Ride { let name: String let categories: Set<RideCategory> let waitTime: Minutes } |
Now you’re probably thinking about loop-de-loops and the thrill of pulling multiple G’s on a wild ride, but trust me, there’s no ride quite like making the shift to FP! :]
Functional Programming Concepts
In this section, you’ll get an introduction to a number of key concepts in FP. Many treatises that discuss FP single out immutable state and lack of side effects as the most important aspects of FP, so why not start there?
Immutability and Side Effects
No matter what programming language you learned first, it’s likely that one of the initial concepts you learned was that a variable represents data. If you step back for a moment to think about the idea, variables can seem quite odd.
Add the following seemingly reasonable and common lines of code to your playground:
var x = 3 // other stuff... x = 4 |
As a developer that’s trained in imperative programming, you wouldn’t give this a second thought. But how exactly could a quantity be equal to 3 and then later be 4?!
The term variable implies a quantity that varies. Thinking of the quantity x from a mathematical perspective, you’ve essentially introduced time as a key parameter in how your software behaves. By changing the variable, you create mutable state.
By itself or in a relatively simple system, mutable state is not terribly problematic. However, when connecting many objects together, such as in a large object-oriented system, mutable state can produce many headaches.
For instance, when two or more threads access the same variable concurrently, they may modify or access it out of order, leading to unexpected behavior. This includes race conditions, dead locks and many other problems.
Imagine if you could write code where state never mutated. A whole slew of issues that occur in concurrent systems would simply vanish — poof! You can do this by creating immutable state, so data is not allowed to change over the course of a program.
The key benefit of using immutable data is that units of code that use it would be free of side effects, meaning the functions in your code wouldn’t alter elements outside of themselves and no spooky effects would appear when function calls occur.
Let’s see what happens when you make the primary data you’ll work with in this tutorial an immutable Swift constant. Add the following array to your playground:
let parkRides = [ Ride(name: "Raging Rapids", categories: [.family, .thrill, .water], waitTime: 45.0), Ride(name: "Crazy Funhouse", categories: [.family], waitTime: 10.0), Ride(name: "Spinning Tea Cups", categories: [.kids], waitTime: 15.0), Ride(name: "Spooky Hollow", categories: [.scary], waitTime: 30.0), Ride(name: "Thunder Coaster", categories: [.family, .thrill], waitTime: 60.0), Ride(name: "Grand Carousel", categories: [.family, .kids], waitTime: 15.0), Ride(name: "Bumper Boats", categories: [.family, .water], waitTime: 25.0), Ride(name: "Mountain Railroad", categories: [.family, .relaxing], waitTime: 0.0) ] |
Since you declare parkRides
with let
instead of var
, both the array and its contents are immutable. Trying to modify one of the items in the array, via the following:
parkRides[0] = Ride(name: "Functional Programming", categories: [.thrill], waitTime: 5.0) |
…produces a compiler error. Go ahead and try to change those rides! No way, buster. :]
Modularity
You’ve reached the part where you’ll add your first function, and you’ll need to use some NSString
methods on a Swift String
, so import the Foundation framework by putting this at the very top of your playground:
import Foundation |
Suppose you need an alphabetical list of all the rides’ names. You are going to start out doing this imperatively. Add the following function to the bottom of the playground:
func sortedNames(of rides: [Ride]) -> [String] { var sortedRides = rides var key: Ride // 1 for i in (0..<sortedRides.count) { key = sortedRides[i] // 2 for j in stride(from: i, to: -1, by: -1) { if key.name.localizedCompare(sortedRides[j].name) == .orderedAscending { sortedRides.remove(at: j + 1) sortedRides.insert(key, at: j) } } } // 3 var sortedNames = [String]() for ride in sortedRides { sortedNames.append(ride.name) } return sortedNames } |
Here you’re doing the following:
- Looping over all the rides passed into the function
- Performing an insertion sort
- Gathering the names of the sorted rides
From the perspective of a caller to sortedNames
, it provides a list of rides, and then outputs the list of sorted names. Nothing outside of sortedNames
has been affected. To prove this, first print out the output of a call to sorted names:
print(sortedNames(of: parkRides)) |
Now gather the names of the list of rides that were passed as a parameter and print them:
var originalNames = [String]() for ride in parkRides { originalNames.append(ride.name) } print(originalNames) |
In the results area and console, you’ll see that sorting rides inside of sortedNames
didn’t affect the list that was passed in. The modular function you’ve created could be considered quasi-functional with all that imperative code. The logic of sorting rides by name has been captured in a single, testable, modular and reusable function.
Still, the imperative code made for a pretty long and unwieldy function. Wouldn’t it be nice if there were techniques to simplify the code within a function like sortedNames
even further?
First-Class and Higher-Order Functions
In FP languages functions are first-class citizens, meaning that functions are treated just like other objects, and can be assigned to variables.
Because of this, functions can also accept other functions as parameters, or return other functions. Functions that accept or return other functions are called higher order functions.
In this section, you’ll work with some of the most common higher-order functions in FP languages, namely filter
, map
and reduce
.
Filter
In Swift, filter(_:)
is a method on Collection
types, such as Swift arrays. It accepts another function a parameter. This other function accepts as input a single value from the array, and returns a Bool
.
filter(_:)
applies the input function to each element of the calling array and returns another array that has only the array elements for which the parameter function returns true
.
Think back to your list of actions that sortedNames
performed:
- Looping over all the rides passed into the function
- Performing an insertion sort
- Gathering the names of the sorted rides
Think about this declaratively rather than imperatively.
Start by commenting out sortedNames
and the subsequent code because you are going to write this much more efficiently. At the bottom of the playground create a function in which a Ride
object will be used as an input parameter to the function.
func waitTimeIsShort(ride: Ride) -> Bool { return ride.waitTime < 15.0 } |
waitTimeIsShort
accepts a Ride
and returns true
if the ride’s wait time is less than 15 minutes; otherwise it returns false
.
Call filter
on your park rides and pass in the new function you just created:
var shortWaitTimeRides = parkRides.filter(waitTimeIsShort) print(shortWaitTimeRides) |
In the playground output, you’ll only see Crazy Funhouse and Mountain Railroad in the call to filter
‘s output, which is correct.
Since Swift functions are simply named closures, you can produce the same result by passing a trailing closure to filter
and using closure-syntax:
shortWaitTimeRides = parkRides.filter { $0.waitTime < 15.0 } print(shortWaitTimeRides) |
Here, filter
is taking each ride in the parkRides
array (represented by $0
), looking at its waitTime
property, and gauging if it is less than 15.0. You are being declarative and telling the program what you want it to do, instead of how it should be done. This can look rather cryptic the first few times you work with it.
CustomStringConvertible Detour
Before proceeding to learning about the map(_:)
method, you’ll do something to make the print output a bit easier to read.
When printing a Ride
object, by default you get the following string representation:
Ride(name: "Mountain Railroad", categories: Set([RideCategory.family, RideCategory.relaxing]), waitTime: 0.0) |
You can make this a bit easier to read by implementing CustomStringConvertible
on Ride
and providing your own string description of a Ride
object.
Right below where the Ride
struct is defined, add the following implementation of CustomStringConvertible
for RideCategory
and Ride
:
extension RideCategory: CustomStringConvertible { var description: String { return rawValue } } extension Ride: CustomStringConvertible { var description: String { return "⚡️Ride(name: \"\(name)\", waitTime: \(waitTime), categories: \(categories))" } } |
Now for each ride object you’ll see a string that looks like this:
⚡️Ride(name: "Mountain Railroad", waitTime: 0.0, categories: [family, relaxing]) |
Still may not be the easiest thing to read, but it does make it a bit nicer than before!
Map
The Collection
method map(_:)
also accepts a single function as a parameter, and in turn, it produces an array of the same length after being applied to each element of the collection. The return type of the mapped function does not have to be the same type as the collection elements.
Apply map
to the elements of your parkRides
array to get a list of all the ride names as strings:
let rideNames = parkRides.map { $0.name } print(rideNames) |
You can also sort and print the ride names as shown below, when you use the non-mutating sorted(by:)
method on the Collection
type to perform the sorting:
print(rideNames.sorted(by: <)) |
sortedNames
can now just be two lines thanks to map(_:)
, sorted(by:)
!
Re-implement sortedNames
with the following code:
func sortedNames(of rides: [Ride]) -> [String] { let rideNames = parkRides.map { $0.name } return rideNames.sorted(by: <) } print(sortedNames(of: parkRides)) |
Reduce
The Collection
method reduce(_:_:)
takes two parameters. The first is a starting value of a generic type Element
, and the second is a function that combines a value of type Element
with an element in the collection to produce another value of type Element
.
The input function applies to each element of the calling collection, one-by-one, until it reaches the end of the collection and produces a final accumulated value of type Element
.
Suppose you want to know the total wait time of all the rides in your park.
Pass in a starting value of 0.0 into reduce
and use trailing-closure syntax to add in the contribution of each ride to the accumulating total.
let totalWaitTime = parkRides.reduce(0.0) { (total, ride) in total + ride.waitTime } print(totalWaitTime) |
In this example, the first two iterations look like the following:
Iteration initial ride.waitTime resulting total 1 0.0 45.0 0.0 + 45.0 = 45.0 2 45.0 10.0 45.0 + 10.0 = 55.0 |
As you can see, the resulting total carries over as the initial value for the following iteration. This continues until reduce
iterates through every Ride
in parkRides
. This allows you to get the total with just one line of code!
Partial Functions
One of the more advanced techniques in FP is using partial functions. This concept allows you to encapsulate one function within another, which allows you to define a part of a function while passing another component of it as a parameter.
func filter(for category: RideCategory) -> ([Ride]) -> [Ride] { return { (rides: [Ride]) in rides.filter { $0.categories.contains(category) } } } |
Here, filter(for:)
accepts a RideCategory
as its parameter and returns a function that filters a [Ride]
based on the provided category. Use it to create a filter for kid rides, like so:
let kidRideFilter = filter(for: .kids) print(kidRideFilter(parkRides)) |
Pure Functions
One of the primary concepts in FP that leads to the ability to reason consistently about program structure, as well as confidently test program results, is the idea of a pure function.
A function can be considered pure if it meets two criteria:
- The function always produces the same output when given the same input, e.g., the output only depends on its input.
- The function creates zero side effects outside of it.
A pure function’s existence is closely tied to the usage of immutable state.
Add the following pure function to your playground:
func ridesWithWaitTimeUnder(_ waitTime: Minutes, from rides: [Ride]) -> [Ride] { return rides.filter { $0.waitTime < waitTime } } |
ridesWithWaitTimeUnder(:from:)
is a pure function because its output is always the same when given the same wait time and the same list of rides.
With a pure function, it’s easy to write a good unit test against the function. To simulate a unit test, add the following assertion to your playground:
var shortWaitRides = ridesWithWaitTimeUnder(15, from: parkRides) assert(shortWaitRides.count == 2, "Count of short wait rides should be 2") print(shortWaitRides) |
Referential Transparency
Pure functions are closely related to the concept of referential transparency. An element of a program is referentially transparent if you can replace it with its definition and always produce the same result. It makes for predictable code and allows the compiler to perform optimizations. Pure functions satisfy this condition.
Verify that the pure function ridesWithWaitTimeUnder
is referentially transparent by replacing its use with its function body:
shortWaitRides = parkRides.filter { $0.waitTime < 15 } assert(shortWaitRides.count == 2, "Count of short wait rides should be 2") print(shortWaitRides) |
Recursion
The final concept to discuss is recursion, which occurs whenever a function calls itself as part of its function body. In functional languages, recursion effectively replaces the looping constructs that are used in imperative languages.
When the function’s input leads to the function being called again, this is considered a recursive case. In order to avoid an infinite stack of function calls, recursive functions need a base case to end them.
Let’s add a recursive sorting function for your rides. First make Ride
conform to Comparable
using the following extension:
extension Ride: Comparable { static func <(lhs: Ride, rhs: Ride) -> Bool { return lhs.waitTime < rhs.waitTime } static func ==(lhs: Ride, rhs: Ride) -> Bool { return lhs.name == rhs.name } } |
You consider one ride less than another ride if the wait time is less, and you consider them equal if the rides have the same name.
Now, extend Array
to include a quickSorted
method:
extension Array where Element: Comparable { func quickSorted() -> [Element] { if self.count > 1 { let (pivot, remaining) = (self[0], dropFirst()) let lhs = remaining.filter { $0 <= pivot } let rhs = remaining.filter { $0 > pivot } return lhs.quickSorted() as [Element] + [pivot] + rhs.quickSorted() } return self } } |
This algorithm first picks a a pivot element. Then the collection is divided into two parts; one part holding all the elements that are less than or equal to the pivot, the other holding the remaining elements greater than the pivot. Recursion is then used to sort the two parts. Note that by using recursion, there is no need for a mutable state.
Verify your function works as follows:
print(parkRides.quickSorted()) print(parkRides) |
The second line confirms that quickSorted
didn’t modify the array that was passed in.
One thing to keep in mind is that recursive functions do have some additional memory (stack) usage and runtime overhead, however they are often significantly more elegant than their messier imperative counterparts, and this shouldn’t be an issue with smaller data sets.
Imperative vs. Declarative Code Style
By considering the following problem, you can combine much of what you’ve learned here on FP and get a clear demonstration of some of the benefits of functional programming.
Consider the following situation:
A family with young kids wants to go on as many rides as possible between frequent bathroom breaks, so they need to find which kid-friendly rides have the shortest lines. Help them out by finding all family rides with wait times less than 20 minutes and sort them by wait time (ascending).
Imperative Approach
For the moment, ignore all you’ve learned about FP so far and think about how you would solve this problem with an imperative algorithm. Your solution will likely be similar to:
var ridesOfInterest: [Ride] = [] for ride in parkRides where ride.waitTime < 20 { for category in ride.categories where category == .family { ridesOfInterest.append(ride) break } } var sortedRidesOfInterest = ridesOfInterest.quickSorted() print(sortedRidesOfInterest) |
You should see that Mountain Railroad, Crazy Funhouse and Grand Carousel are the best bets, and they’re listed in order of increasing wait time.
As written, the imperative code is fine, but a quick glance does not give a clear, immediate idea of what it’s doing. You have to pause to look at the algorithm in detail to grasp it. No big deal you say? What if you’re coming back to do some maintenance, debugging or you’re handing it off to a new developer? As they say on the Internet, “You’re doing it wrong!”
Functional Approach
FP can do better. Add the following one-liner to your playground:
sortedRidesOfInterest = parkRides.filter { $0.categories.contains(.family) && $0.waitTime < 20 }.sorted(by: <) |
Verify that this single line produces the same output as the imperative code by adding:
print(sortedRidesOfInterest) |
There you have it! In one line, you’ve told Swift what to calculate; you want to filter your parkRides
to .family
rides with wait times less than 20 minutes and then sort them. That precisely – and elegantly – solves the problem stated above.
The resulting code is declarative, meaning it is self-explaining and reads like the problem statement it solves. This is different from imperative code, which reads like the steps the computer has to take to solve the problem statement.
The When and Why of Functional Programming
Swift is not a purely functional language but it does combine multiple programming paradigms to give you flexibility for application development.
A great place to start working with FP techniques is in your Model layer, your ViewModel layer, and anywhere your application’s business logic appears.
For user interfaces, the places to use FP techniques is a little less clear. FRP (Functional Reactive Programming) is an example of an approach to FP for UI development. For example, Reactive Cocoa is an FRP library for iOS and macOS programming.
By taking a functional, declarative approach, your code can be more concise and clear. As well, your code will be easier to test when isolated into modular functions that are free from side effects.
Finally, as multi-processing cores become the norm for both CPUs and GPUs, minimizing side effects and issues from concurrency will become increasingly important, and FP will one of the most important ways to achieve smooth performance!
Where to Go From Here?
You can download the complete playground with all the code in this tutorial
here.
While you’ve reviewed many of the important concepts of FP in this tutorial, you’ve only scratched the surface of what FP is and what it can do. Once you have some experience with these basic FP ideas, take the plunge into the heart of FP:
- Monads, Endofunctors, Category Theory
- Programming languages where FP is the focus, such as Haskell and Clojure
You’ll get great insights into various aspects of Swift by studying both of the above. You might be able to do things you never dreamed possible with your new-found knowledge.
Also, I highly recommended checking out:
- Functional Swift, another excellent book written by objc.io.
- Swift and the Legacy of Functional Programming, a talk by Rob Napier.
If you have any questions or comments on this tutorial, come join the discussion below!
The post An Introduction to Functional Programming in Swift appeared first on Ray Wenderlich.