An Introduction to Functional Programming in Swift

In this tutorial you’ll learn, step by step, how to get started with functional programming and how to write declarative, rather than imperative, code. By Warren Burton.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

First-Class and Higher-Order Functions

In FP languages, functions are first-class citizens. You treat functions like other objects that you can assign 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 three of the most common higher-order functions in FP languages: filter, map and reduce.

Filter

In Swift, filter(_:) is a method on Collection types, such as Swift arrays. It accepts another function as a parameter. This other function accepts a single value from the array as input, checks whether that value belongs and returns a Bool.

filter(_:) applies the input function to each element of the calling array and returns another array. The output array contains only the array elements whose parameter function returns true.

Try this simple example:

let apples = ["🍎", "🍏", "🍎", "🍏", "🍏"]
let greenapples = apples.filter { $0 == "🍏"}
print(greenapples)

There are three green apples in the input list, so you’ll see three green apples in the output.

Think back to your list of actions that sortedNamesImp(of:) performs:

  1. Loops over all the rides passed to the function.
  2. Sorts the rides by name.
  3. Gathers the names of the sorted rides.

Instead of thinking about this imperatively, think of it declaratively, i.e. by only thinking about what you want to happen instead of how. Start by creating a function that has a Ride object as an input parameter to the function:

func waitTimeIsShort(_ ride: Ride) -> Bool {
  return ride.waitTime < 15.0
}

The function 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:

let shortWaitTimeRides = parkRides.filter(waitTimeIsShort)
print("rides with a short wait time:\n\(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 also known as closures, you can produce the same result by passing a trailing closure to filter and using closure syntax:

let shortWaitTimeRides2 = parkRides.filter { $0.waitTime < 15.0 }
print(shortWaitTimeRides2)

Here, filter(_:) takes each ride in parkRides — represented by $0 — looks at its waitTime property and tests if it’s less than 15 minutes. You are being declarative and telling the program what you want it to do. This can look rather cryptic the first few times you work with it.

Map

The Collection method map(_:) accepts a single function as a parameter. It outputs an array of the same length after applying that function 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.

Try this:

let oranges = apples.map { _ in "🍊" }
print(oranges)

You map each apple to an orange producing a feast of oranges :].

You can 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)
testOriginalNameOrder(rideNames)

You’ve proved that using map(_:) to get the ride names does the same thing as iterating across the collection, just like you did earlier.

You can also sort the ride names as shown below, when you use the sorted(by:) method on the Collection type to perform the sorting:

print(rideNames.sorted(by: <))

The Collection method sorted(by:) takes a function that compares two elements and returns a Bool as a parameter. Because the operator < is a function in fancy clothing, you can use Swift shorthand for the trailing closure { $0 < $1 }. Swift provides the left- and right-hand sides by default.

You can now reduce the code to extract and sort the ride names to only two lines, thanks to map(_:) and sorted(by:).

Re-implement sortedNamesImp(_:) as sortedNamesFP(_:) with the following code:

func sortedNamesFP(_ rides: [Ride]) -> [String] {
  let rideNames = parkRides.map { $0.name }
  return rideNames.sorted(by: <)
}

let sortedNames2 = sortedNamesFP(parkRides)
testSortedNames(sortedNames2)

Your declarative code is easier to read and you can figure out how it works without too much trouble. The test proves that sortedNamesFP(_:) does the same thing as sortedNamesImp(_:).

Reduce

The Collection method reduce(_:_:) takes two parameters: The first is a starting value of an arbitrary type T and the second is a function that combines a value of that same type T with an element in the collection to produce another value of type T.

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.

For example, you can reduce those oranges to some juice:

let juice = oranges.reduce("") { juice, orange in juice + "🍹"}
print("fresh 🍊 juice is served – \(juice)")

Here you start with an empty string. You then add a 🍹 to the string for each orange. This code can juice any array so be careful what you put in it :].

To be more practical, add the following method that lets you know the total wait time of all the rides in the park.

let totalWaitTime = parkRides.reduce(0.0) { (total, ride) in 
  total + ride.waitTime 
}
print("total wait time for all rides = \(totalWaitTime) minutes")

This function works by passing the starting value of 0.0 into reduce and using trailing closure syntax to add how much time each ride contributes to the total wait time. The code uses Swift shorthand again to omit the return keyword. By default, you return the result of total + ride.waitTime.

In this example, the iterations look like this:

Iteration    initial    ride.waitTime    resulting total
    1          0            45            0 + 45 = 45
    2         45            10            45 + 10 = 55
    …
    8        200             0            200 + 0 = 200

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 one line of code!