top of page

Higher-Order Functions: The Slick One


A city view from height

Well, it is one of those topics that every beginner finds hard to deal with and almost every time forget to use it in code. When I first saw Higher-Order Functions in code, it seemed really stylish and fancy (hence the title: The Slick one).

They are not that hard to understand, you just have to practice it and above all, remember to use it inside your code.

Before starting with Higher-Order Functions, you should be aware of what closures are and how to use them. Worry not, I've got you covered. Check out my article on Closure in Swift . You should not proceed without knowing closure syntax, shorthand properties, and other stuff.


 

Back so early? Nice! Let’s get started.

Higher-Order Functions are simply functions that take other function/closure as arguments or maybe even return a function/closure.

You can use Higher-Order Functions with any Sequence say Array , Dictionary , Set etc. But for simplicity, in all our examples, we will use Array .

The three biggest stars under the banner are:

  1. Map

  2. Filter

  3. Reduce

Out of these three, you will use the first two a lot. That doesn’t mean Higher-Order Functions are just limited to these three, there are others like flatMap, compactMap, sort or sorted, contains, partition, etc. Let’s start with the king of Higher Order Functions: Map.

Map

If you look into the definition of it, you’ll find this:

func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

I know, that’s a lot to take in. Let me break it down a bit.

  • This function, map , is a generic one.

  • The function takes a closure/function as a parameter named transform.

  • It works on the array provided to it, applies the transform(closure/function) to those array values, and returns a modified array

Now the key here is that the input Array and the output Array might not be of the same type. Why so? If you look closely, we have two generics — Element and T . Element refers to the value inside the input array and T refers to the value of the output array. The reason we have two is that the closure/function that we provided as a parameter to the map might mutate the value and create a different result type altogether (which is, in the real world, the use case of the map)

Enough theory, let’s dive into some code!

Let’s say we have to square all elements inside the array, we can either use a closure or a function, but I will use both!

Here, the type of input array and the output array will be the same, Int


Using Closure as parameter to map
Using Closure as parameter to map

when we use the map with closure, we need to use {} with map.


Using function as a parameter to map
Using function as a parameter to map

when we use the map with function, we need to use () with map.

Don’t worry about the generic function, I have just used it for it to be more generic (pun intended). It can work without generic as well but then you have to make sure that the input array type and function’s input parameter type has to be the same.

But how does the map really work?

  • It takes the single element of the input array at a time, represented with $0

  • Modifies it according to the closure/function logic

  • Stores it inside the output array

  • Then the same for next and so on.

Let’s do a comparison:

ta-da !!!!!
ta-da !!!!!

From here, I will not explain each Higher Order Function in detail, will give you a gist of how to use it and will leave it to you to do some magic and explore!

Filter

Filter the definition looks something like this:

func filter<T>(_ isIncluded: (T) throws -> Bool) rethrows -> [T]

This is somewhat simpler. What it does is, the closure/function that we use as a parameter to filter the method should return a Bool value indicating whether the input array value should be included in the output array. A true return value indicates that the array value should be included inside the output array.

If you noticed here, we have just one generic, T unlike 2 in case of map . What this means is that the type of input array and output array will be the same as we are not modifying any input value, we are just deciding whether it should be included in the output array or not!

That’s the main difference between Map and Filter

Difference between map and filter

Reduce

Reduce is somewhat less used but is equally important. It is useful in the case where you want to collapse the whole sequence(array in our case) to just a single value, hence the name, Reduce .

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

Wow, That’s scary!

Don’t worry , all the function demands are two things :

  • An initial value upon which the initial computation will be performed (say added to, subtracted from and stuff).

  • A closure/function which will define the logic of what to do with the incoming values from the array and how to combine it into the value modified so far. The key here to note is that the closure will have two input parameters as compared to one we had so far. First will be the value that we have computed so far and second will be the input value from our array

An example of reduce

It’s a fairly simple example where we add all the elements of the array. Since we had 2 parameters to our function and the last one was a closure, we used the concept of trailing closure. And since we had 2 input parameter to out closure, we are using $0 and $1 .

However, if all you want to do is sum all the elements, you can further reduce it down (pun intended) to:

FlatMap and CompactMap

These two Higher-Order Functions are really confusing to some developers out there because prior to Swift 4.1, there was only flatMap and no compactMap and flatMap used to perform the function of compactMap as well. If you want to check the motivation behind compactMap , check out this Swift Evolution Proposal.

What flatMap essentially does is, it flattens multiple arrays into one. First of all, flatMap works for Array inside Array. It then combines all of those arrays into one.


Flat map example

What compactMap does is, it automatically remove any nil values from the array, which can be really handy if used correctly. Consider this, we have an array of String type and we want to convert them to Int . Using compactMap , it will automatically remove all those elements that cannot be converted to Int or in other words, which turns out to be nil while converting to Int .


compact map example

Chaining Higher-Order Functions

The true power of Higher Order Functions is unleashed when we chain different Higher-Order Functions together to narrow down our lengthy code to a single line.

Let’s consider a scenario where we have two arrays inside an array of type, we need to convert them all Int , square them all, and then add them all. Sounds complicated? Well not exactly.


chaining higher order functions

Conclusion

There are a few other Higher-Order Functions as well but that’s for you to explore and try out yourself. I know it’s in our nature that whenever we see an array and we have to work with it, we always go for a for-in loop. But we have to break that habit.

Whenever we see a sequence (especially an array) and we have to mutate it and convert it to something else, always think of Higher Order Functions first. I know it’s a bit hard to get into the habit of using Higher-Order Functions frequently but we have to as we have seen how much the amount of code gets reduced to.

Related Posts

Comentários


bottom of page