Functional Programming in Swift

Become a Subscriber

Swift makes for a great functional language with standard library support for closures, array methods like map/filter/reduce, and structs which pass-by-value. While the language started off more object-oriented, it has become one of the most popular functional languages with the last few versions and releases. This article will help you identify the core constructs, patterns and methodologies to functional programming in Swift. Still, it is equally important to understand the object-oriented principles as well, so if you are not already familiar with them, you may want to start there.

Functional Constructs

Most internal constructs in the swift standard library can be divided into “better for OOP” or “best for functional” with a few being equally suitable for both. These are the constructs that are best used for functional programming:

Structs

Stateless, passed by value, efficient. These are just some of the advantages of using Structures instead of Classes. If you are not familiar with Structures, it’s helpful to think of them like you would a model or schema. They help you provide “structure” to your data while still have named and type casted properties. With their minimalistic approach to data storage and manipulation, they have an added advantage of light memory footprints, which matters tremendously on mobile. These are often used in functional swift, providing some of the rigidity needed in an application, but being passed by value, they are the preferred method for working with state.

Immutability

Immutability is one of the core elements to functional programming. When passing by value, mutation of the original data is actually the last thing you want. You should always define variables with the let keyword to create rigid data and enforce any mutations to happen with a new declaration or returned value. This practice is what makes functional programming so modular and reusable. It also allows for quick comparison conditions and to track the history of changes to state throughout the lifecycle.

Type Inference

Type inference is awesome when working with a JSON API. At times, you may see an integer represented as a string or vice-versa. Usually you want to fetch the raw data, let type inference do it’s thing and then perform your scrubbing post-retrieval:

// The number is stored as a string. Something that may be seen when fetching from a web API
// Type inference allows you to do conversions without specifying the type on the variable
let number = "2" // "2" is a string
let two = Int(number) // 2 is an int

// However, the above methods are generally a bad practice, especially for functional programming
// It's better to specify the expected type whenever possible
let foo: Int = 2 // foo will always expect an int
let bar: Double = Double(foo) * 2.2 // bar will always expect a double

When defining variables, the best practice is to be explicit about the type except when dealing with data that you may not yet know the resulting types, such as a third-party API. In those instances, it is best to first retrieve the data and then sanitize it into typed values that you expect to work with.

High Order Functions

In computer science, a higher-order function (also known as functional, functional form or functor) is a function that does at least one of the following: takes one or more functions as arguments and returns a function as its result. These are often used in method chaining, callbacks, and so on. Swift supports a few functors in the standard library such as currying, map, reduce and enclosures.

Closures

If you came from Objective-C and have been working with it for many years, then you likely saw the introduction of blocks a few years ago. Blocks brought basic closure support to Objective-C and now are first-class citizens in Swift. Blocks/Enclosures/Callbacks are mostly synonymous terms, and are a more granular way of referring to functional programming. While a simple closure is just a function that expects another function as an argument, here is a typical real-world example where we would interact with an API and let the user know the results:

func postTweet(tweet: String, callback: @escaping (_ success: Bool, _ errorMessage: String) -> Void)
{
  // We might set success based on the API call results
  let success = false

  // If not successful, then you could pass an error message to the callback
  let errorMessage = "oh no, your tweet was too short!"

  callback(success, errorMessage)
}

postTweet(tweet: "this is my tweet") { (success: Bool, errorMessage: String) -> Void in
  let message = success ? "tweet posted successfully" : "Error: \(errorMessage)"
  print(message)
}

Map

Map is most useful for taking one set of values and converting or scrubbing those values to return a new state with the same amount of objects. The key principle here is that the structure and size of the array does not usually change, but rather the items within the array usually change type or value.

let costs = [9.99, 29.99, 59.99]
costs.map({ "$\($0)" }) // ["$9.99", "$29.99",  "$59.99"]

Reduce

You can’t have a functional language without map, filter, and reduce. These functions are really just predefined closures for arrays, but they are the type of methods functional developers have come to expect from a standard library. Swift’s reduce function operates very similarly to a reduce method seen in other functional languages: it takes an array of items and is expected to reduce size of the array by combining values. This is useful for math summations, merging duplicate data, etc:

let deposits = [200, 300, 500]
deposits.reduce(0, { $0 + $1 }) // 1000

Filter

Again, it is similar to map and filter. Filter is more similar to reduce, in the sense that it is often used to reduce the size of an array, but is often used in a way that eliminates array items based on a condition rather than combining them:

let transactions = [-50, 100, 200, -25]
let deposits = transactions.filter({ $0 > 0 }) // [100, 200]