Drilling HOF, Higher order Function



Oh ya!!! you saw that exactly, Drilling but not the “making a hole” thingy. Rather It is the military version of training. Here on this two blog-post series, we will drill on Higher order Function aka HOF through Swift implementation. So tighten the boots, it gonna be muddy.

If you are the guy or gal who can use the HOF; ahhh pretty much I guess 😉.
But you are interested on what happens behind the scene, then you are on the proper pool.

We will break down the HOF drill into two parts.

Content:

Background:

Hello I am HOF

Though the name HOF seems to be a tough bite to digest, actually on reality it is not. The only tough thing on HOF is the understanding of when to use which HOF.
HOF is a very simple concept. HOF, Higher Order Function, basically is a function having the following two property:

  • Takes a closure with/without other param as argument
  • Returns a function or some Cumulative value

In a nutshell, HOF will facilitate a repetitive operation. Now what does this two keywords repetitive and operation means?
The keyword repetitive; you already have guessed it, right? Yes it will only be functional on collection types, like Array Dictionary Set.  Each of the element of a collection will be included.
And the second thing, operation which is basically a mathematical operation. We can define the operation as f(x).  So a simple example of HOF comes down to the following:

Yeah thats it. Simple isn’t it?
I am sure this simple concept break down, now makes you think that HOF is not a UFO after-all. 😏

Why do we Care for HOF

  • Not being an Alien among, some smart Software Engineer. Because those guys already using the HOF and expect others to be  knowledgeable or at least has the will to know.
  • Producing good precise clean code. 
  • Functional programming.

Things need to be known before using HOF

  • HOF will only operate on Collection-type ie Array, Dictionary and Set.
  • The most important thing we have to know before using HOF, is the closure. 
    A side note: Function are special type of closure.

We will drive to some properties and formats of closure to facilitate HOF study.

Closure Properties needed for HOF:

  1. Can infer the type of param & return from surround context
  2. return keyword is unnecessary for a single-expression closure
  3. Shorthand argument name

Closure expression

{ (params) -> return-type in
    //Code
}

For this section on closure we will use the following array of Jedi.

let jedi = ["Yoda", "Obi-Wan Kenobi", "Anakin Skywalker", "Luke Skywalker"]

Regular Closure, having both param type as String and return as Bool

jedi.sorted { (firstJedi: String, secondJedi: String) -> Bool in
    return firstJedi < secondJedi
}

Inferring param and return type:

jedi.sorted { (firstJedi, secondJedi) in
    return firstJedi < secondJedi
}

Removing return

jedi.sorted { (firstJedi, secondJedi) in
    firstJedi < secondJedi
}

Shorthand argument name

Shorthand argument name is provided by Swift for inline closure. The values of closure’s argument/param can be referred as $0 $1 $2and so on.

jedi.sorted(by: { $0 < $1 })

Form now on we will use the shorthand way when ever possible. We can come back to the above discussion always when we are having some confusion about closure on HOF.

So Swift what HOF do you have?

On this section we will talk about some of the popular HOF on Swift.

map:

map are used to perform a common operation to all the element of a collection.

[1, 2, 3].map({ $0 + 5 })
//[6, 7, 8]

We can always revisit the closure properties needed for HOF if we are having any confusion for closure.

When to use map:
A common operation need to perform on a collection.

compactMap:

Lets consider the following example and state the use of compactMap from there:

let jedi = [nil, "Obi-Wan Kenobi", "Anakin Skywalker", "Luke Skywalker"]
print( jedi.map({ "Name: \($0)" }) )
//[nil, Optional("Obi-Wan Kenobi"), Optional("Anakin Skywalker"), Optional("Luke Skywalker")]

print( jedi.compactMap({ $0 }))
//["Obi-Wan Kenobi", "Anakin Skywalker", "Luke Skywalker"]

So as you can see, its simple compactMap will return an array of non-nil instance,  typed instance, in this case [String]. Whereas map will return non-nil and nil instance, optional type, in this case [String?].

So we can consider compactMap as a map function which has a builtin nil filter.

When to use compactMap :
When a common operation need to perform on a collection, optionally-typed or not, and the final result need to be typed collection.

compactMap is extremely useful on the case of failable initializer, where the init can fail and return a nil instance. On some future post we will cover the failable initializer.

["40", "unknown", "28"].compactMap({ Int($0) })
//[40, 28]

You can always refer to closure details for clarification.

We can find the Int‘s failable initializer from the Documentation. The declaration is convenience init?(_ description: String). Again we will talk about the convenience init? on some future post.

So by using the compactMap we can eliminate the nil ones and get the proper instance of Int

flatMap is now deprecated. We need to use compactMap instead of flatMap.

filter:

As the name suggests the filter HOF will filter a collection. We need to feed the filter with a closure which will act as a filter constrain.
If we put it simply filter will operate on a collection with some filter-operation and finally will give us a filtered collection.
Lets see an example.

let jedi = ["Yoda", "Obi-Wan Kenobi", "Anakin Skywalker", "Luke Skywalker"]

jedi.filter({ $0.contains(" ") })
// ["Obi-Wan Kenobi", "Anakin Skywalker", "Luke Skywalker"]

Here $0 is the shorthand argument name and the contains() is the regular closure/function of String
On the above example we are filtering-out the names which does not have two part separated by a space. So at the end we are getting a collection on two part Jedi names.

Let us see another example.

[20, 23, 21, 5, 0, 45].filter({ $0 % 5 == 0 })
//[20, 5, 0, 45]

Here we are filtering the Int array as if the reminder of 5 is equal to zero.

When to use filter :
When ever we need to filter on a collection.

reduce:

The feature of reduce is to combine the elements of a collection into a cumulative return.
Kind of reducing multiple elements into one. 
Among the HOF we discussed here, the reduce will be the toughest one. Oh Don’t tens we got it cover. 👌

Let us jump-in with some simple example and the longer format of closure to get it first. Then gradually we will move to optimal format.

So here is the reduce signature.

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

Result is a generic type instance, we gonna cover it up on some future post. But for now informed that generic type represents a set of types, which may or may not be bounded by a or multiple protocol.
The <Result> at the start of the first bracket tells the compiler that the Result is a generic instance.

On the other hand, Element is the associatedtype instance that represents the elements of the corresponding collection. We published a talk on associatedtype in Protocol, please have a look for a better understanding of associatedtype in the Protocol.

And the throws and rethrows are used for exception handling.  Please have a visit to our error handling blog post if you are interested.

So on the following section we will forget the ResultElement, throw and rethrow.  

We will now replace the generic type with our well-known type like Int and Double, our input will be an Int array and the cumulative result will be be Double. Now reduce becomes more easier to digest, at-least for the very first time.

func reduce(_ initialResult: Double, _ nextPartialResult: (Double, Int) throws -> Double) rethrows -> Double

A bit easy for eye. Let us make it more easier. Now let us remove the throw and rethrow, just for the understanding !!!

func reduce(_ initialResult: Double, _ nextPartialResult: (Double, Int) -> Double) -> Double

Now lets reduce the reduce to even granular level. Basically we can now find these three sections on this method signature.

  • initialResult which is Double type.
  • nextPartialResult which is a closure, the signature is (Double, Int) -> Double. So thats mean the first argument is a Double type second one is Int type and the return of the closure is Double type.
  • The final part is, the return of reduce which is Double type.

🤓

Things are very easy now. So whats now. Yes must be an example. Lets start with the easy one.

let numbers = [0, 1, 2, 4]
let initialResult = 1.9

numbers.reduce( initialResult, { (result: Double, num: Int) -> Double in
    var total = result
    (total += Double(num))
    return total
})
// 8.9

Remember as we said closure are smart when we said closure Can infer the type of param & return from surround context. So by removing the types,  next version will be:

numbers.reduce( initialResult, { (result, num) in
    var total = result
    (total += Double(num))
    return total
})
//8.9

Also closure can have shorthand form. So:

numbers.reduce(initialResult, { $0 + Double($1) })
// 8.9

Ahem smarter. 🕶️ 

Image image image please !!! give me some image…

Now there is another version of reduce . It looks like nearly the same.

func reduce<Result>(into initialResult: Result, _ updateAccumulatingResult: (inout Result, Element) throws -> ()) rethrows -> Result

The first difference between the previous version of reduce and the current version of reduce is the inout version of Result. So what is inout? When an argument is labeled as inout thats mean we can overwrite that argument.
The second one is the closure updateAccumulatingResult which does not return, rather assign the result to the Result typed instance.
And obviously the method signature is a bit different among two, this one has the into.

And I know we can break down this one as we did for the previous version. Go ahad and break it down. Believe me it is worthy 👩‍💻.

So lets update our sum of numbers with the inout version of reduce

numbers.reduce(into: initialResult) { (result, num) in
    result += Double(num)
}
// 8.9

Aha suddenly it becomes short. Have a look at the changes from the previous version.
Yep you got it, the inout version of result. As we can write on result, so we are directly using the addition assignment operator, += to update result. For the same reason, inout, we are not returning any result from the closure. 

Ok, we are not forgetting the shorthand form on closure, right? Here it is:

numbers.reduce(into: initialResult, { $0 += Double($1) })

So every thing is clear now. 

When to use reduce:
When we need a cumulative result from a collection.

We know how the reduce HOF works. But are we forgetting something? Come on… Jedi

let jedi = ["Yoda", "Obi-Wan Kenobi", "Anakin Skywalker", "Luke Skywalker"]
jedi.reduce(into: "Available Jedi: ", { $0 += "\n \($1)" })
/*
Available Jedi: 
 Yoda
 Obi-Wan Kenobi
 Anakin Skywalker
 Luke Skywalker
*/

Now its complete. ⌛

Just remember when ever you have confusion about HOF, just go and break down that HOF. Then come up with the longer version of closure, then gradually move to shorter closure format.

See you soon on the next post. Till then Happy Mobi Dev talks.

2 thoughts on “Drilling HOF, Higher order Function

Leave a Reply

Notifications for mobidevtalk! Cool ;) :( not cool