Join Scott Gardner for an in-depth discussion in this video Use higher-order functions, part of Swift 3 Essential Training: Beyond the Basics.
- [Instructor] There are several functions in the Swift standard library that are influenced by functional programming concepts. Before I cover them, I just want to briefly explain what functional programming is. In pure functional programming, functions become the main building blocks of a program. Functions can take other functions as parameters and/or return functions. These are generally referred to as higher order functions in Swift. It is through this compositional use of functions that we can write more modular code and achieve separation of concerns. That's a very high level explanation of functional programming and I would encourage you to read up on it more.
A quick online search will return several interesting and informative articles on the subject. I also feel that a goal of functional programming is to write more succinct code at the call site in a declarative style that is easier to follow. So now I'd like to show you a few of the higher order functions in Swift that I find most useful particularly when working with sequences and collections. The first is forEach. You're no doubt familiar with the for in loop in Swift, which I covered in the basics course. ForEach iterates over a sequence or a collection. For example, I can call forEach on an array of int values.
As you can see here on the code suggestion, forEach takes a closure that takes an int parameter, which is inferred by the value type of the array. And it doesn't return a value. It also happens to be a throwing function, so it'll throw an error if something predictable goes wrong so that you can handle it instead of getting an exception. I'll select a parameter placeholder and press return to implement the template for it. Because this is a trailing closure, it can be moved outside of the parenthesized parameter list, and because forEach takes no other parameters, the parameter list can be removed altogether, and it's unnecessary to explicitly write that it returns void.
So that is also removed from the template code. I'll assign i for the value past as an argument to the closure and then simply print out that value. A more succinct way to write this is to use the default argument name $0 here. And now I can write the whole thing in a single line of code. You might think I'm done, but no. Instead of writing a trailing closure, I can actually pass any value directly as the parameter to forEach that matches the function type.
I'll define a printOut function before this line of code that simply prints out the value passed to it. I'm doing this because the print function takes multiple parameters. So it doesn't match the function type required by forEach, even though two of its three parameters have default values and can be omitted when calling it, but my printOut function does match. So now I can pass printOut to forEach, and note that you just write the function name without its parameter list.
printOut's value parameter is inferred to be each value sequentially exposed by the forEach method. I'll leave the trailing closure syntax there as a comment for reference. Isn't that elegant? It practically reads like a sentence. I had to do a bit more work here once to set up this line of code, but now multiple callers of forEach can write very succinct code at the call site. I can also use forEach with a sequence or range. For example, calling forEach on a closed range of one through three does the same thing as the line above it.
ForEach doesn't return anything, so let's say you wan to modify the values in a collection. For that you would use that map function. Map takes a closure that takes each element from a collection and returns a new collection of the same type. I'll define a new array constant and assign to it the value returned from calling map on an array of ints, multiplying each element by two. Once again, I'm not done yet. The multiply operator's function type takes two int parameters, again, inferred by the array type, and it returns an int.
It doesn't match map's function type though. By the way the T placeholder types you see in this definition are generic placeholders. I'll cover generics later in the course, but basically, what's happening here is that the type returned by map will match the type of its input parameter. So calling map on array of ints will return a new array of ints. I'll define a double closure this time that does match the parameter type of map because remember, a function is just a name closure.
Double will take an int in this case and it returns an int. And now I can pass double directly to map. There's filter function that takes a closure that filters each element returning a bool true or false if the element passes the conditions specified and returns a new array of those passing elements. But if I define a function type that matches its function type, and I'll just cut and paste the closure from the filter call, and paste it to assign to it a closure I'll define is even.
Now I can pass is even directly to filter. The reduce function can be used to aggregate values. It takes two parameters, a starting value and a combining function. And it returns a new value of the same type after calling the combining function on all elements in the collection it is called on. The plus operator matches the required function type so I can pass it directly. And although there are additional higher order functions to explore in the Swift standard library, last but not least, I'll show you flatMap.
FlatMap can be used to filter out nils from an array of optional values, optionally transforming the non-nil values. flatMap can also be used to flatten an array of nested arrays. This time I'll transform the values, which are arrays, by calling reduce and passing zero and the plus operator to reduce.
If I want to extract that trailing closure so that I can pass a value directly to flatMap, I'll need to explicitly define the closure value type. In this example, a closure that takes an array of doubles and returns a double. I'll be using simple values in these examples just to keep them easy to follow. But keep in mind that these higher order functions can work with any type and dictionary and sets too.
For example, I'll define a dictionary of string keys and int values. Now I'll get an array of its keys upper cased using map. Notice that the closure argument is a tuple with named elements key and value forEach key value pair. And I'll use reduce to get the sum of the values in dictionary.
Reduce takes two parameters. I'll pass the initial value of zero and then use the trailing closure syntax for the accumulator. Take some time to make sure all this makes sense before moving on. Try using these higher order functions on your own. Up next, I'll walk through an example of chaining higher order functions together.
- Adding source files, resources, links, and literals
- Adding pages to a playground
- Using overflow operators and bitwise operators
- Using ranges with strings
- Creating complex sequences
- Chaining higher-order functions
- Defining lazy properties
- Using failable initializers
- Mutating methods
- Working with singletons
- Nesting function types
- Creating error types and recursive enumerations
- Extending concrete types
- Referencing selectors and key paths
- Working with protocol-oriented programming
- Defining class-only protocols and optional protocols
- Using option sets, type checking, and casting operators