Drilling HOF, Higher order Function
Oh ya!!! you saw it exactly. Drilling: not the “making a hole” thingy, but the military version of training. Here on this two blog-post series, we gonna drill on Higher order Function
aka HOF
. So tighten the boots, it will 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:
- The first part, means this blog-post, will have the detail talk on HOF.
- On the second part, we will generate a complex case study which we will solve by using HOF. So that at the end we will have some good grab on HOF.
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
andSet
. - 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:
- Can infer the type of param & return from surround context
return
keyword is unnecessary for a single-expression closure- 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
$2
and 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 which represents the elements of the corresponding collection. Again we will have some future post for associatedtype
.
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 Result
, Element
, 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 isDouble
type.nextPartialResult
which is a closure, the signature is(Double, Int) -> Double
. So thats mean the first argument is aDouble
type second one isInt
type and the return of the closure isDouble
type.- The final part is, the return of
reduce
which isDouble
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.9numbers.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 it is 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.
This story is basically the first one of a two series blog post from mobidevtalk.com