Tracks
/
Swift
Swift
/
Syllabus
/
Higher Order Functions
Hi

Higher Order Functions in Swift

1 exercise

About Higher Order Functions

Function types

We have seen before how function signatures are defined using a function name followed by comma-separated list of parameter labels along with their types, followed by -> and the type of the values returned by the function.

If one were to remove all of the names and labels from this signature, they would be left with the type of the function.

func shoppingList(item: String, quantity: Int) -> String {
  "You need to buy \(quantity) \(item)."
}
// shoppingList: (String, Int) -> String

func repeater(_ phrase: String, times: Int) -> String {
  var result = ""
  for _ in 1...times {
    result += phrase
  }
  return result
}
// repeater: (String, Int) -> String

func printString(line: String) {
  print(line)
}
// printString: (String) -> ()

Notice how the types of the first two functions are the same, (String, Int) -> String, even though the two functions work very differently. They both take a String and an Int as input and return a String. And, as one can see from the third function's type, even though the function doesn't appear to return anything, it actually returns (), the only value of type Void. Note that () and Void are interchangeable as type names, so the type of printString could also be written as String) -> Void.

These types can be used in the same manner as any other type in Swift, for example, they can be used in the declaration of variables or constants. And since functions are values in Swift, already existing functions can be assigned to these constants/variables:

var stringAndIntToString: (String, Int) -> String

stringAndIntToString = shoppingList
stringAndIntToString("carrots", 3)
// => "You need to buy 3 carrots."

stringAndIntToString = repeater
stringAndIntToString("carrots", 3)
// => "carrotscarrotscarrots"

As with other variables/constants, the value you assign to it must be of the correct type:

stringAndIntToString = printShoppingList
// Error: Cannot assign value of type '(String) -> ()' to type '(String, Int) -> String'

Function types as parameter types

As function types can be used anywhere other types can be used, it follows that they can be used as parameter types and passed into other functions and called from within those functions.

func apply3(to str: String, function f: (String, Int) -> String) -> String {
  f(str,3)
}
// apply3: ([String], (String, Int) -> String) -> [String]

apply3(to: "eggs", function: shoppingList)
// => "You need to buy 3 eggs."

apply3(to: "eggs", function: repeater)
// => "eggseggseggs"

func apply(functions fs: [(String, Int) -> String],
           to string: String,
          ) -> [String] {
  var result = [String]()
  for f in fs {
    result.append(f(string, 3))
  }
  return result

}
// apply: ([(String, Int) -> String], String) -> [String]


apply(functions: [shoppingList, repeater], to: "carrots")
// => ["You need to buy 3 carrots.", "carrotscarrotscarrots"]

Function types as return types

Similarly, function types may be used as return types for functions. In other words, one can write functions that create and return other functions. When creating these functions, the function that is returned can use the other parameters passed into the function or the local variables created inside the parent function.

func makeAdder(base: Int) -> (Int) -> Int {
  func adder(_ i: Int) -> Int {
    base + i
  }
  return adder
}
// makeAdder: (Int) -> (Int) -> Int

let add10 = makeAdder(base: 10)
// add10: (Int) -> Int

let subtract20 = makeAdder(base: -20)
// subtract10: (Int) -> Int

add10(5)
// => 15

subtract20(5)
// => -15

Capturing values

As seen with the makeAdder(base:) function above, nested functions are able to access the parameters and variables of their surrounding functions. Additionally, they are able to maintain access to these values after the enclosing function terminates. This action of obtaining and maintaining access is known as capturing.

Higher-order functions

Functions that take functions as parameters and/or return functions are known as higher order functions. Swift has many higher-order functions in its various libraries. For example, the sorted(by:) method available on Swift collections such as Array which for an array with elements of type T takes a function of type (T, T) -> Bool that determines which of two values should come before the other in a sorted Array, returning true if the left-hand value should appear first and false if the right-hand value should appear first.

Another example is the contains(where:) method on collections which for a collection with elements of type T, takes a function of type (T) -> Bool which returns true if the element satisfies some property and false otherwise. contains(where:) then returns true if some element in the collection satisfies this property.

func longer(_ strA: String, _ strB: String) -> Bool {
  strA.count > strB.count
}
// longer: (String, String) -> Bool

["apple", "ball", "carrot"].sorted(by: longer)
// => ["carrot", "apple", "ball"]

["apple", "ball", "carrot"].sorted(by: >)
// => ["carrot", "ball", "apple"]


func isBig(_ i: Int) -> Bool {
  i > 100
}
// isBig: (Int) -> Bool

func isVowel(_ char: Character) -> Bool {
  switch char.lowercased() {
  case "a", "e", "i", "o", "u": return true
  default: return false
  }
}
// isVowel: (Character) -> Bool

[1, 75, 3, 99].contains(where: isBig)
// => false

"Owl".contains(where: isVowel)
// => true

Edit via GitHub The link opens in a new window or tab

Learn Higher Order Functions