Skip to content

Function Types and Closures

In Functions, you learned that you can create reusable units of code by declaring functions. You can pass data into and out of a function through its parameters and return value. In this chapter, you’ll learn how function types and closures let you pass code into and out of a function.

Function types

Swift is a strictly typed language, and functions are no exception. A function’s type includes its parameter and return types. For example:

swift
func toFahrenheit(celsius: Double) -> Double {
  celsius * 1.8 + 32
}

The type of this function is (Double) -> Double because it takes a Double and returns a Double.

What about functions that don’t take any parameters or return a value? For example:

swift
func printGreeting() {
  print("Hello")
}

The type of this function is () -> Void. Here’s what this means:

  • The parentheses indicate an empty parameter list. Like a function declaration, a function type must include parentheses around its parameter list, even when that list is empty.

  • Void is a special type that means empty, or nothing. You use it as the return type for functions that don’t return a value:

    swift
    func printGreeting() -> Void {
      print("Hello")
    }

    As you can see, you can make Void an explicit return type. However, an implicit -> Void is preferred.

You can use a function type like any other type. For example:

swift
var conversion: (Double) -> Double

This code declares conversion as a variable of type (Double) -> Double. This variable can hold any function that takes a Double and returns a Double, such as toFahrenheit:

swift
conversion = toFahrenheit

You refer to a function by name when you assign it to a variable. You can also use the function’s full name, in case its base name is ambiguous:

swift
conversion = toFahrenheit(celsius:)

Of course, type inference supports function types as well:

swift
var task = printGreeting

Here, the compiler infers the type of task as () -> Void.

After you’ve declared a constant or variable of a function type, you can call it just like a function:

swift
conversion(0)
task()

Note how the parentheses disambiguate between a function and a function call:

  • task is a function.
  • task() is a call to this function.

Higher-order functions

A function can take other functions as parameters and even return a function. It then becomes a higher-order function. The type of a higher-order function includes other function types.

In the previous chapter, you learned that you can sort a dictionary using the sorted(by:) method. This method returns the key/value pairs of the dictionary in a sorted array. To sort the grades dictionary from the previous chapter, you’d call sorted(by:) with an argument of the following type:

swift
((key: String, value: Int), (key: String, value: Int)) -> Bool

You should recognize this type as a function that takes two key/value pairs and returns a Bool. sorted(by:) uses this function to compare two student/grade pairs and determine their order. If the comparison function returns true, the first pair is sorted before the second pair; if it returns false, the second pair is sorted first.

Here’s an example:

swift
var grades = ["Alice": 20, "Bob": 7, "Charlie": 14]

func nameAscending(
  _ pair1: (student: String, grade: Int),
  _ pair2: (student: String, grade: Int)
) -> Bool {
  pair1.student < pair2.student
}

func gradeDescending(
  _ pair1: (student: String, grade: Int),
  _ pair2: (student: String, grade: Int)
) -> Bool {
  pair1.grade > pair2.grade
}

grades.sorted(by: nameAscending)
grades.sorted(by: gradeDescending)

This example declares two comparison functions, nameAscending and gradeDescending. You can pass either of these to sorted(by:), which then sorts accordingly.

Closures

Having to declare nameAscending and gradeDescending up front isn’t practical. You only use these functions as arguments to sorted(by:), so it would be better if you could declare them inline as part of the function call. Closures provide a way to do this.

A closure is a function without a name. You specify its parameters, return type, and implementation, and you assign it to a variable or use it as a function argument.

Here’s how you call sorted(by:) with a closure that compares by grade:

swift
grades.sorted(by: {
  (
    pair1: (student: String, grade: Int),
    pair2: (student: String, grade: Int)
  ) -> Bool in
  return pair1.grade > pair2.grade
})

Here’s the closure on its own:

swift
{
  (
    pair1: (student: String, grade: Int),
    pair2: (student: String, grade: Int)
  ) -> Bool in
  return pair1.grade > pair2.grade
}

This closure is similar to the declaration of gradeDescending with the following differences:

  1. The keyword func and function name are gone.
  2. The opening brace moves to the front so that the closure is fully enclosed in braces.
  3. The keyword in separates the closure’s parameters and return type from its body, a task previously performed by the opening brace.

A fully specified closure is quite verbose. Fortunately, there are several ways you can simplify closures. The following sections explain these ways.

Inferred types

More often than not, the compiler can infer a closure’s parameter and return types. In the example above, the sorted(by:) method requires a function with two parameters of type (key: String, value: Int) and a return type of Bool. The closure doesn’t have to repeat this type information:

swift
grades.sorted(by: {
  (pair1, pair2) in
  return pair1.value > pair2.value
})

This closure only declares the names of its parameters. Their types, as well as the return type, are inferred from the type of by.

Implicit return statement

If the body of a closure consists entirely of a return statement, you can make this statement implicit and only specify the return value:

swift
grades.sorted(by: {
  (pair1, pair2) in
  pair1.value > pair2.value
})

Shorthand parameter names

If a closure’s parameter names are of no importance, you can use shorthand names instead: $0, $1, and so on. You can then omit the parameter list and the keyword in:

swift
grades.sorted(by: { $0.value > $1.value })

However, you can only use shorthand names if the closure uses its last parameter. The following example is invalid:

swift
grades.sorted(by: { $0.value > 10 })  

This closure only has one parameter and therefore doesn’t match the type of by, which has two parameters. In this case, a parameter list is required:

swift
grades.sorted(by: {
  (pair1, _) in
  pair1.value > 10
})

Trailing closures

If the last argument in a function call is a closure, you can write that closure after the argument list and discard its label (by:):

swift
grades.sorted() {
  $0.value > $1.value
}

You can even discard the parentheses if the closure is the only argument:

swift
grades.sorted {
  $0.value > $1.value
}

This syntax is known as trailing closure syntax. It may seem strange at first because it looks very different from a regular function call. However, trailing closures can be an elegant way to pass code to a function. They let you write a function argument in the same way you write the body of a control flow statement: as a block of code surrounded by braces.

A function call can have multiple trailing closures. For example, here’s a function that takes two function parameters:

swift
func when(_ value: String?, then: (String) -> Void, otherwise: () -> Void) {
  if let value, !value.isEmpty {
    then(value)
  } else {
    otherwise()
  }
}

Without trailing closures, you’d call this function as follows:

swift
var input: String? = "Hello"

when(input, then: {
  print($0)
}, otherwise: {
  print("Input was empty.")
})

With trailing closures, this call becomes:

swift
when(input) {
  print($0)
} otherwise: {
  print("Input was empty.")
}

As you can see, only the first trailing closure drops its argument label. All other trailing closures must be labeled.

Local variable capturing

Now that you know how to declare closures, let’s discuss an important aspect of their behavior.

Consider the following example:

swift
func makeCounter() -> () -> Int {
  var count = 0
  return {
    count += 1
    return count
  }
}

This function creates and returns a closure of type () -> Int. This closure works like a counter: every time you call it, it increments and returns its value. Here’s how you use it:

swift
var counter = makeCounter()
counter()
counter()

Note how this closure uses the local variable count. This variable is scoped to makeCounter, which means it’s out of scope by the time you call counter. Despite this, counter works fine, incrementing and returning count, even though count should no longer exist. Why is this?

When a closure uses a local variable, it captures this variable. A capture extends the lifetime of a variable beyond its normal scope, so it remains available to the closure that captured it. This process is known as local variable capturing.

Now consider what happens when you call makeCounter a second time:

swift
var secondCounter = makeCounter()
secondCounter()

Even though count is being kept alive by counter, a second call to makeCounter creates a new local variable, as you would expect. This means counter and secondCounter have their own separate, captured count variable.

Examples from the Standard Library

This section demonstrates some of the higher-order methods available on collection types. These methods leverage function types and are designed to be used with closures. All examples use simplified trailing closures.

contains

The contains method checks if the collection contains at least one element that satisfies a given condition.

For example, you can use contains to check if at least one student attained a perfect grade of 20:

swift
grades.contains { $0.value == 20 }

The closure in this example is a function that takes an element of the collection and returns a Bool. Such a function is known as a predicate.

Predicates are common in the Standard Library. They describe a characteristic a value may or may not have. In this case, the characteristic is “has a perfect grade”.

allSatisfy

The allSatisfy method checks if all of the elements in the collection satisfy a given predicate. For example, you can use allSatisfy to check if all students have a passing grade:

swift
grades.allSatisfy { $0.value >= 10 }

filter

The filter method takes a predicate and returns a new collection containing only the elements that match the predicate. The original collection is unaffected.

The following example uses filter to create a dictionary of students with a passing grade:

swift
grades.filter { $0.value >= 10 }

map

The map method takes a transformation function and applies it to each element of the collection. It gathers the transformed elements in an array, which it returns. The original collection is unaffected.

The following example uses map to build an array of strings that describe the contents of grades:

swift
grades.map { "\($0.key) has a grade of \($0.value)." }

Here, the transformation function is a closure that takes an element of the collection and returns a string. map applies this function to every pair in the dictionary and returns the resulting strings.

Methods like filter and map are often chained together. In a method chain, one method is applied to the output of the previous method. For example:

swift
grades.filter { $0.value >= 10 }.map { $0.key }

This code first creates a dictionary of students with a passing grade, then maps this dictionary to an array of students.

forEach

The forEach method applies a function to every element of the collection. The goal of this function is solely to process an element; it doesn’t return anything, and neither does forEach.

The following example prints the students that have a passing grade, sorted alphabetically:

swift
grades.filter { $0.value >= 10 }
      .map { $0.key }
      .sorted()
      .forEach { print($0) }

Here’s an equivalent example that uses a for-in loop:

swift
for student in grades.filter({ $0.value >= 10 })
                     .map({ $0.key })
                     .sorted() {
  print(student)
}

As you can see, the forEach method and the for-in loop perform a similar task. However, there are a few differences:

  • A for-in loop can’t use trailing closures in its condition because the opening brace of a trailing closure would be mistaken for the opening brace of the loop’s body.
  • You can’t use break with the forEach method because there’s no loop statement to break.
  • You can’t use continue with the forEach method, though you can skip elements with a return statement as this ends the processing function.

The forEach method is designed for use in a method chain, and because it doesn’t return anything, it ends the chain.

reduce

The reduce method processes the entire collection and reduces it to a single value. It takes two parameters:

  1. An input value for the first reduction step.
  2. A reduction function that takes an input value ($0) and a single element from the collection ($1). This function should combine the element with the input value and return a new value that becomes the input for the next reduction step.

The reduce method applies your reduction function to the collection, one element at a time, and returns the output of the final step. The collection itself is unaffected.

Here’s an example that uses reduce to calculate the sum of the student’s grades:

swift
grades.map { $0.value }.reduce(0) { $0 + $1 }

This code first maps the students to their grades, then uses reduce to sum the grades. Assuming grades has the value ["Alice": 20, "Bob": 7, "Charlie": 14], the reduction goes as follows:

  • Step 1 has an input of 0 and processes the first element, 20:
    0 + 20 = 20
  • Step 2 has an input of 20 and processes the second element, 7:
    20 + 7 = 27
  • Step 3 has an input of 27 and processes the third element, 14:
    27 + 14 = 41

Finally, reduce returns the output of the last step: 41.

Note

The order of the steps may be different from what’s shown here because dictionaries are unordered. Nevertheless, the end result is the same.

In the previous example, there’s no need to write a closure for the reduction function. Swift already includes a function that returns the sum of two integers:

swift
grades.map { $0.value }.reduce(0, +)

There’s no magic involved here. Operators are functions declared in the Standard Library. They get some special treatment, but other than that, they’re just functions.

Up next

This chapter has shown how higher-order functions let you pass code into and out of a function. This code can be either a closure, a function of your own, or a function provided by Swift, such as an operator.

It’s important that you get proficient at using closures; many methods from the Standard Library accept them. You’ll get a chance to practice using closures with the upcoming exercises.

Complete these exercises first, then continue on to your second challenge, where you’ll build a game of Blackjack.