/ FUNCTIONAL PROGRAMMING

Functions in Swift: Pure, Higher-Order and First-Class Functions, Currying and Partial Application

Functions are the central concept of functional programming (FP). Understanding them inside and out is essential in order to take advantage of functional programming in Swift. In this article, we will study all the things that even a non-FP programmer should know about functions:

  • What is a pure function?
  • What does it mean to have a function as a first-class type?
  • What is a higher-order function?
  • How to compose functions with currying and partial application?

If you are new to FP, Practical Functional Programming in Swift: The Fundamentals will get you up to speed.

Pure Functions

What exactly is a function? This is an important question to ask since, in functional programming, the term function has special meaning.

In math we write things like f(x) = y, describing some function f that takes a parameter x and maps to a value y. In functional programming, functions work just like they do in math.

A Swift function must follow four rules to behave like a function in math:

  • A function must always take an argument.
  • A function must always return a value.
  • A function must not depend on anything that changes.
  • A function must not change anything by itself.

A function with such properties is called pure. Calling a pure function with the same arguments always returns the same result.

A pure function is just a combination of its arguments.

Why are pure functions important? They make it easy to reason about your code. If you want to know what a given piece of code does, you just need to look at that specific code. You don’t need to worry about how the state of your program affects it. We can also run pure functions in parallel since we know that they don’t depend on each other. Furthermore, we can test pure functions in isolation since they are not coupled to the rest of our code.

A pure function is self-contained and doesn’t depend on anything that was run before it.

Here is an example of such a function in Swift:

func add(x: Int, y: Int) -> Int { x + y }

In functional programming we strive to have as many lines of code as possible in pure functions and as few as possible in impure functions.

First-Class Functions

A programming language is said to have first-class functions when functions are no different from any other variable. Functions can be used as arguments, can be returned as values from other functions, and can be assigned to a variable.

Having functions as first-class citizens is very important. This is what makes Swift functional programming viable in the first place.

If you’ve ever passed a completion callback to a network request, you’ve already used a first-class function:

let url = URL(string: "https://vadimbulavin.com")!

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    ...
}

We can reference a function with a variable and pass it as a callback:

func onResponse(data: Data?, response: URLResponse?, error: Error?) { ... }

// `completionHandler` is a reference to the `onResponse` function
let completionHandler = onResponse

let task = URLSession.shared.dataTask(with: url, completionHandler: completionHandler)

And build a function at runtime:

let multiply = { (x: Int, y: Int) -> Int in x * y }

multiply(2, 3) // 6

First-class functions allow us to abstract out any repetitive computation from our code, and ultimately enable us to write functions that produce other functions [1]. These functions are referred to as higher-order functions.

Higher-Order Functions

A function is called higher-order if it takes a function as an argument or returns a function as a result. Higher-order functions are the natural consequence of having functions as first-class types.

When doing functional programming, we write these all the time. Why are they useful?

  • Higher-order functions raise the level of abstraction. They allow us to abstract over actions, not just values. Thus, we don’t have to reason about lower-level details.
  • Higher-order functions are key to parameterizing code by behavior. They allow us to define common programming patterns and write functions about functions.

Some examples of Swift higher-order functions that you probably already know are map(), filter() and reduce().

Currying and Partial Application

Two important techniques of functional composition are currying and partial application.

Currying transforms a function with many arguments into a series of functions that each take one argument.

A good reason to curry a function is that we want to call it based on some of its arguments, but not all of them:

func add(_ x: Int) -> (_ y: Int) -> Int {
    { y in return x + y }
}

The function add() takes a single argument x and returns another function that adds y to x. Calling add(1)(2) produces the final result since add is really a series of two functions.

Given a curried function, we can apply it partially.

Partial application means binding some arguments to a function without fully evaluating it. This produces another function that takes less arguments than the first one.

Here is how we can put both currying and partial application at work:

let addTwo = add(2)
let oneTwoThree = [1, 2, 3]
let threeFourFive = oneTwoThree.map(addTwo) // [3, 4, 5]

When we create the addTwo function, we say that the add function is partially applied. The addTwo function binds the x argument of the add function to 2. It takes the remaining argument y and adds 2 to it.

Why are currying and partial application useful?

  • They let us build specialized functions from more generic ones.
  • They let us bind commonly used arguments. For example, we can declare an increment function as follows: let inc = add(1).

Summary

We’ve learned several essential concepts of functional programming and how they apply to Swift – pure, first-class and higher-order functions, currying and partial application. You’ll make good use of them in your everyday Swift development, whether you are doing functional or object-oriented programming.


Thanks for reading!

If you enjoyed this post, be sure to follow me on Twitter to not miss any new posts.