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.

4.7 (27) · 1 Review

Download materials
Save for later
Share
Update note: Warren Burton updated this tutorial to Swift 4.2. Joe Howard wrote the original tutorial and Niv Yahel made a previous update.

Swift’s grand entrance to the programming world at WWDC in 2014 was much more than just an introduction of a new language. It facilitated new approaches to software development for the iOS and macOS platforms.

This tutorial focuses on one of these approaches: Functional Programming, or FP for short. You’ll get an introduction to a broad range of ideas and techniques used in FP.

As you go through this tutorial, you’re going to work in a Playground. You can find the completed Playground by using the Download Materials button at the top or bottom of this page.

Getting Started

Create a new empty playground in Xcode so you can follow along with the tutorial by selecting File ▸ New ▸ Playground….

create new playground

Set up your playground so you can see the Results Panel and the console by dragging the splits.

configure playground

Now, delete everything from the playground, then add this line:

import Foundation

You’ll start by reviewing some basic theory to warm up your brain.

Imperative Programming Style

When you first learned to code, you probably learned imperative style. How does imperative style work?

Add the following code to your playground:

var thing = 3
//some stuff
thing = 4

That code is normal and reasonable. First, you create a variable called thing that equals 3, then you command thing to be 4 later in time.

That’s imperative style in a nutshell. You create a variable with some data, then you tell that variable to be something else.

Functional Programming Concepts

In this section, you’ll get an introduction to some key concepts in FP. Many papers that discuss FP single out immutable state and lack of side effects as the most important aspects of FP, so you’ll start there.

Immutability and Side Effects

No matter which programming language you learned first, one of the initial concepts you likely learned was that a variable represents data, or state. If you step back for a moment to think about the idea, variables can seem quite odd.

The term “variable” implies a quantity that varies as the program runs. Thinking of the quantity thing from a mathematical perspective, you’ve introduced time as a key parameter in how your software behaves. By changing the variable, you create mutable state.

For a demonstration, add this code to your playground:

func superHero() {
  print("I'm batman")
  thing = 5
}

print("original state = \(thing)")
superHero()
print("mutated state = \(thing)")

Holy mysterious change! Why is thing now 5? That change is called a side effect. The function superHero() changes a variable that it didn’t even define itself.

By itself, or in a simple system, mutable state is not necessarily a problem. Problems arise when connecting many objects together, such as in a large object-oriented system. Mutable state can produce headaches by making it hard to understand what value a variable has and how that value changes over time.

For example, when writing code for a multi-threaded system, if two or more threads access the same variable concurrently, they may modify or access it out of order. This leads to unexpected behavior. That unexpected behavior 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 vanish. Systems that work like this have immutable state, meaning that state is not allowed to change over the course of a program.

The key benefit of using immutable data is that the units of code that use it are free of side effects. The functions in your code don’t alter elements outside of themselves, and no spooky effects appear when function calls occur. Your program works predictably because, without side effects, you can easily reproduce its intended effects.

This tutorial covers FP at a high level, so it’s helpful to consider the concepts in a real world situation. In this case, imagine you’re building an app for an amusement park, and that the park’s back-end server provides ride data via a REST API.

Creating a Model Amusement Park

Set up the data structure by adding this code to your playground:

enum RideCategory: String, CustomStringConvertible {
  case family
  case kids
  case thrill
  case scary
  case relaxing
  case water

  var description: String {
    return rawValue
  }
}

typealias Minutes = Double
struct Ride: CustomStringConvertible {
  let name: String
  let categories: Set<RideCategory>
  let waitTime: Minutes

  var description: String {
    return "Ride –\"\(name)\", wait: \(waitTime) mins, " +
      "categories: \(categories)\n"
  }
}

Next, create some data using that model:

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.

Try to modify one of the items in the array, via the following:

parkRides[0] = Ride(name: "Functional Programming",
                    categories: [.thrill], waitTime: 5.0)

That produces a compiler error, which is good. You want the Swift compiler to stop you from changing the data.

Now, remove those lines so you can continue with the tutorial.

Modularity

Working with modularity is like playing with children’s building bricks. You have a box of simple bricks that you can use to build a large and complex system by joining them together. Each brick has a single job. You want your code to have the same effect.

Suppose you need an alphabetical list of all the rides’ names. Start out doing this imperatively, which means by utilizing mutable state. Add the following function to the bottom of the playground:

func sortedNamesImp(of rides: [Ride]) -> [String] {

  // 1
  var sortedRides = rides
  var key: Ride

  // 2
  for i in (0..<sortedRides.count) {
    key = sortedRides[i]

    // 3
    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)
      }
    }
  }

  // 4
  var sortedNames: [String] = []
  for ride in sortedRides {
    sortedNames.append(ride.name)
  }

  return sortedNames
}

let sortedNames1 = sortedNamesImp(of: parkRides)

Your code accomplishes the following tasks:

  1. Create a variable to hold the sorted rides.
  2. Loop over all the rides passed into the function.
  3. Sort the rides using an Insertion Sort sort algorithm.
  4. Loop over the sorted rides to gather the names.

Add the following test to the playground to verify that this function behaves as intended:

func testSortedNames(_ names: [String]) {
  let expected = ["Bumper Boats",
                  "Crazy Funhouse",
                  "Grand Carousel",
                  "Mountain Railroad",
                  "Raging Rapids",
                  "Spinning Tea Cups",
                  "Spooky Hollow",
                  "Thunder Coaster"]
  assert(names == expected)
  print("✅ test sorted names = PASS\n-")
}

print(sortedNames1)
testSortedNames(sortedNames1)

You now know that if you change your sorting routine in the future (for example, to make it functional :]), that you can detect any bug that occurs.

From the perspective of a caller to sortedNamesImp(of:), it provides a list of rides, and then outputs the list of sorted names. Nothing outside of sortedNamesImp(of:) changes.

You can prove this with another test. Add the following code to the end of the playground:

var originalNames: [String] = []
for ride in parkRides {
  originalNames.append(ride.name)
}

func testOriginalNameOrder(_ names: [String]) {
  let expected = ["Raging Rapids",
                  "Crazy Funhouse",
                  "Spinning Tea Cups",
                  "Spooky Hollow",
                  "Thunder Coaster",
                  "Grand Carousel",
                  "Bumper Boats",
                  "Mountain Railroad"]
  assert(names == expected)
  print("✅ test original name order = PASS\n-")
}

print(originalNames)
testOriginalNameOrder(originalNames)

In this test, you gather the names of the list of rides that you passed as a parameter and test that order against the expected order.

In the results area and console, you’ll see that sorting rides inside of sortedNamesImp(of:) didn’t affect the input list. The modular function you’ve created is semi-functional. The logic of sorting rides by name is a single, testable, modular and reusable function.

The imperative code inside sortedNamesImp(of:) made for a long and unwieldy function. The function is hard to read and you cannot easily work out what it does. In the next section, you’ll learn techniques to simplify the code within a function like sortedNamesImp(of:) even further.