What are Higher Order Functions in Swift?

Higher order functions are functions that take one or more functions as arguments or return a function as its result. They are powerful tools that can help us write concise and expressive code. Swift provides some built-in higher order functions for collections, such as forEach, map, compactMap, flatMap, filter, reduce, sorted and more. Higher order functions enable developers to abstract common patterns of function applications and make code more concise and reusable. In Swift, higher order functions are a powerful tool for manipulating and transforming collections of data.

In this post, we will learn how to create our own higher order functions for custom purposes by implementing some examples.

Example 1: myMap

Let’s say we want to create a higher order function that applies a given function to every element of an array and returns a new array with the results. This is similar to the map function, but we will call it myMap for distinction. Here is how we can implement it:

// Define a generic function that takes an array and a function as arguments
// and returns a new array with the results of applying the function to each element
func myMap<T, U>(_ array: [T], _ function: (T) -> U) -> [U] {
    // Create an empty array to store the results
    var result = [U]()
    // Loop through the array
    for element in array {
        // Apply the function to the element and append the result to the result array
        result.append(function(element))
    }
    // Return the result array
    return result
}

// Test the myMap function with some examples
let numbers = [1, 2, 3, 4, 5]
let strings = ["apple", "banana", "carrot", "date", "elderberry"]

// Apply a function that doubles each number
let doubled = myMap(numbers) { $0 * 2 }
print(doubled) // [2, 4, 6, 8, 10]

// Apply a function that reverses each string
let reversed = myMap(strings) {String($0.reversed()) }
print(reversed) // ["elppa", "ananab", "torrac", "etad", "yrrebredle"]

As you can see, the myMap function is generic and can work with any types of elements and functions. We can also use shorthand arguments like $0 to make the code more concise.

We can also add an extension on Array to make it easier to use the myMap function:

// Define an extension on Array that adds a myMap method
extension Array {
    // Define a generic method that takes a function as an argument
    // and returns a new array with the results of applying the function to each element
    func myMap<U>(_ function: (Element) -> U) -> [U] {
    // Create an empty array to store the results
    var result = [U]()
    // Loop through the array
    for element in self {
        // Apply the function to the element and append the result to the result array
        result.append(function(element))
    }
    // Return the result array
    return result
    }
}

// Test the myMap method with some examples
let numbers = [1, 2, 3, 4, 5]
let strings = ["apple", "banana", "carrot", "date", "elderberry"]

// Apply a function that doubles each number using dot syntax
let doubled = numbers.myMap { $0 * 2 }
print(doubled) // [2, 4, 6, 8, 10]

// Apply a function that reverses each string using dot syntax
let reversed = strings.myMap {String($0.reversed()) }
print(reversed) // ["elppa", "ananab", "torrac", "etad", "yrrebredle"]

Example 2: myCompactMap

Another common higher order function is compactMap, which takes an array and a transform function as arguments and returns a new array with only the non-nil results of applying the transform function to each element. This is useful for filtering out nil values from an array. Swift already provides this function for collections, but let’s see how we can implement it ourselves:

// Define a generic function that takes an array and a transform function as arguments
// and returns a new array with only the non-nil results of applying the transform function to each element
func myCompactMap<T, U>(_ array: [T], _ transform: (T) -> U?) -> [U] {
    // Create an empty array to store the results
    var result = [U]()
    // Loop through the array
    for element in array {
        // Apply the transform function to the element and check if it returns a non-nil value
        if let value = transform(element) {
            // If yes, append it to the result array
            result.append(value)
        }
    }
    // Return the result array
    return result
}

// Test the myCompactMap function with some examples
let numbers = [1, 2, nil, 4, nil]
let strings = ["apple", "", "carrot", nil, "elderberry"]

// Apply a transform function that squares each number if it is not nil
let squared = myCompactMap(numbers) { $0.map { $0 * $0 } }
print(squared) // [1, 4, 16]

// Apply a transform function that returns each string if it is not empty or nil
let nonEmpty = myCompactMap(strings) { $0?.isEmpty == false ? $0 : nil }
print(nonEmpty) // ["apple", "carrot", "elderberry"]

The myCompactMap function is also generic and can work with any types of elements and transform functions. We can also use shorthand arguments like $0 and optional chaining like $0? to make the code more concise.

We can also add an extension on Array to make it easier to use the myCompactMap function:

// Define an extension on Array that adds a myCompactMap method
extension Array {
   // Define a generic function that takes an array and a transform function as arguments
    // and returns a new array with only the non-nil results of applying the transform function to each element
    func myCompactMap<U>(_ transform: (Element) -> U?) -> [U] {
        // Create an empty array to store the results
        var result = [U]()
        // Loop through the array
        for element in self {
            // Apply the transform function to the element and check if it returns a non-nil value
            if let value = transform(element) {
                // If yes, append it to the result array
                result.append(value)
            }
        }
        // Return the result array
        return result
    }
}

// Test the myCompactMap method with some examples
let numbers = [1, 2, nil, 4, nil]
let strings = ["apple", "", "carrot", nil, "elderberry"]

// Apply a transform function that squares each number if it is not nil using dot syntax
let squared = numbers.myCompactMap { $0.map { $0 * $0 } }
print(squared) // [1, 4, 16]

// Apply a transform function that returns each string if it is not empty or nil using dot syntax
let nonEmpty = strings.myCompactMap { $0?.isEmpty == false ? $0 : nil }
print(nonEmpty) // ["apple", "carrot", "elderberry"]

Example 3: myFilter

Another useful higher order function is filter, which takes an array and a predicate function as arguments and returns a new array with only the elements that satisfy the predicate. Swift also provides this function for collections, but let’s see how we can implement it ourselves:

// Define a generic function that takes an array and a predicate function as arguments
// and returns a new array with only the elements that satisfy the predicate
func myFilter<T>(_ array: [T], _ predicate: (T) -> Bool) -> [T] {
    // Create an empty array to store the results
    var result = [T]()
    // Loop through the array
    for element in array {
        // Check if the element satisfies the predicate
        if predicate(element) {
            // If yes, append it to the result array
            result.append(element)
        }
    }
    // Return the result array
    return result
}

// Test the filter function with some examples
let numbers = [1, 2, 3, 4, 5]
let strings = ["apple", "banana", "carrot", "date", "elderberry"]

// Filter out the odd numbers using dot syntax 
let even = myFilter(numbers) { $0 % 2 == 0 }
print(even) // [2,4]

// Apply a function that checks if a string has more than 5 letters
let long = myFilter(strings) { $0.count > 5 }
print(long) // ["banana", "carrot", "elderberry"]

We can also add an extension on Array to make it easier to use the myFilter function:

// Define an extension on Array that adds a myFilter method
extension Array {
    // Define a generic function that takes an array and a predicate function as arguments
    // and returns a new array with only the elements that satisfy the predicate
    func myFilter(_ predicate: (Element) -> Bool) -> [Element] {
        // Create an empty array to store the results
        var result = [Element]()
        // Loop through the array
        for element in self {
            // Check if the element satisfies the predicate
            if predicate(element) {
                // If yes, append it to the result array
                result.append(element)
            }
        }
        // Return the result array
        return result
    }
}

// Test the myFilter method with some examples
let numbers = [1, 2, 3, 4, 5]
let strings = ["apple", "banana", "carrot", "date", "elderberry"]

// Apply a function that checks if a number is even using dot syntax
let even = numbers.myFilter { $0 % 2 == 0 }
print(even) // [2, 4]

// Apply a function that checks if a string has more than 5 letters using dot syntax
let long = strings.myFilter { $0.count > 5 }
print(long) // ["banana", "carrot", "elderberry"]

Example 4: myReduce

Another useful higher order function is reduce, which takes an array, an initial value and a combining function as arguments and returns a single value by applying the function to each element and accumulating the result. Here is my attempt:

// Define a generic function that takes an array, an initial value and a combining function as arguments
// and returns a single value by applying the function to each element and accumulating the result
func myReduce<T, U>(_ array: [T], _ initial: U, _ combine: (U, T) -> U) -> U {
    // Create a variable to store the result
    var result = initial
    // Loop through the array
    for element in array {
        // Update the result by applying the combining function to the current result and element
        result = combine(result, element)
    }
    // Return the result
    return result
}

// Test the reduce function with some examples
let numbers = [1, 2, 3, 4, 5]
let strings = ["apple", "banana", "carrot", "date", "elderberry"]

// Sum up the numbers using dot syntax 
let sum = myReduce(numbers, 0, { $0 + $1 })
print(sum) // 15

// Concatenate the strings using dot syntax 
let concat = myReduce(strings, "", { $0 + $1 })
print(concat) // applebananacarrotdateelderberry

We can also add an extension on Array to make it easier to use the myReduce function. Here is how we can implement it:

// Define an extension on Array that adds a myReduce method
extension Array {
 // Define a generic method that takes an initial value and a combining function as arguments
 // and returns a single value by applying the function to each element and accumulating the result
    func myReduceExtension<U>(_ initial: U, _ combine: (U, Element) -> U) -> U {
        // Use the previously defined myReduce function and pass self as the array argument
        return myReduce(self, initial, combine)
    }
}

// Test the reduce method with some examples
let numbers = [1, 2, 3, 4, 5]
let strings = ["apple", "banana", "carrot", "date", "elderberry"]

// Sum up the numbers using dot syntax
let sum = numbers.myReduceExtension(0) { $0 + $1 }
print(sum) // 15

// Concatenate the strings using dot syntax
let concat = strings.myReduceExtension("") { $0 + $1 }
print(concat) // applebananacarrotdateelderberry

Example 5: mySorted

Another useful higher order function is sorted, which takes an array and a sorting closure as arguments and returns a new array with the elements sorted according to the closure. Here is my attempt:

// Define a generic function that takes an array and a sorting closure as arguments
// and returns a new array with the elements sorted according to the closure
func mySorted<T>(_ array: [T], _ sort: (T, T) -> Bool) -> [T] {
    // Create a variable to store the sorted array
    var sorted = array
    // Loop through the array from the first element to the second last element
    for i in 0..<sorted.count - 1 {
        // Loop through the array from the next element to the last element
        for j in i + 1..<sorted.count {
            // Compare the current pair of elements using the sorting closure
            if sort(sorted[j], sorted[i]) {
                // Swap the elements if they are out of order
                let temp = sorted[i]
                sorted[i] = sorted[j]
                sorted[j] = temp
            }
        }
    }
    // Return the sorted array
    return sorted
}

// Test the mySorted function with some examples
let numbers = [4, 3, 1, 5, 2]
let strings = ["apple", "banana", "carrot", "date", "elderberry"]

// Sort the numbers in ascending order using dot syntax 
let ascending = mySorted(numbers, { $0 < $1 })
print(ascending) // [1, 2, 3, 4, 5]

// Sort the strings in descending order using dot syntax 
let descending = mySorted(strings, { $0 > $1 })
print(descending) // ["elderberry", "date", "carrot", "banana", "apple"]

We can also add an extension on Array to make it easier to use the mySorted function. Here is how we can implement it:

// Define an extension on Array that adds a mySorted method
extension Array {
    // Define a generic method that takes a sorting closure as an argument
    // and returns a new array with the elements sorted according to the closure
    func mySortedExtension(_ sort: (Element, Element) -> Bool) -> [Element] {
        // Use the previously defined mySorted function and pass self as the array argument
        return mySorted(self, sort)
    }
}

// Test the mySorted method with some examples
let numbers = [4, 3, 1, 5, 2]
let strings = ["apple", "banana", "carrot", "date", "elderberry"]

// Sort the numbers in ascending order using dot syntax 
let ascending = numbers.mySortedExtension { $0 < $1 }
print(ascending) // [1, 2, 3, 4, 5]

// Sort the strings in descending order using dot syntax 
let descending = strings.mySortedExtension { $0 > $1 }
print(descending) // ["elderberry", "date", "carrot", "banana", "apple"]

Thanks for reading!