Overview
- What is “Higher-Order Function”?
- How can we use higher order function to reduce code duplication?
Higher-Order Function
In a common programming language, you can create a function to reduce a repeated operation. For instance, if you were repeating an addition of three numbers like below
val added1 = 1 + 2 + 3
val added2 = 2 + 3 + 4
...
You could simply create a function that adds three parameters
def add(first: Int, second: Int, third: Int): Int = {
first + second + third
}val added1 = add(1,2,3)
val added2 + add(2,3,4)
Well, though it doesn’t reduce a whole lot of code duplication, you get the idea :)
In a similar context, higher-order function is a function that takes a function as its parameter. In Scala, passing a function to another function can help you reduce code duplication. For instance, exampleFunction
takes a function called anotherFunction
which takes a String as a parameter and returns a Boolean value.
def exampleFunction(anotherFunction: String => Boolean) = {
val name = "mj park"
anotherFunc(name)
}
I am going to show you real world examples below.
Problem
Suppose you implemented a program that allows a user to explore the files in the current directory. Later, a user requested a new feature that filters files with the suffix query. So, you ended up writing a function like below
// original code
object FileMatcher {
private def filesHere = (new File(".")).listFiles
def filesEnding(suffix: String) = {
for {
file <- filesHere
if file.getName.endsWith(suffix)
} yield file
}
}
In the next version, you want to support a feature that filters files with the prefix query. The first thing that could come up in your mind is using a similar function filesStarting
// original code
object FileMatcher {
private def filesHere = (new File(".")).listFiles
... def fileEnding(suffix: String) = {
...
} def filesStarting(prefix: String) = {
for {
file <- filesHere
if (file.getName.startsWith(prefix))
} yield file
}
}
Well, this is whole lot of repetition, and we should avoid it.
Idea
Thinking about common components between two functions — filesEnding
and filesStarting
, we could easily notice that it returns a list of files using a different operation on each fileName. What would look like if we were to get a “function” as a parameter that does the same operation?
def filesMatching(query: String, FUNCTION) = {
for {
file <- filesHere
if (file.getName.FUNCTION(uqery))
} yield file
}
As shown here, we could just apply passed “FUNCTION” to each file to filter. Using this idea, we pass the function as an argument and turn it into something like below
object FileMatcher {
private def filesHere = (new File(".")).listFiles
def filesEnding(query: String) = {
getMatchingFiles(query, (fileName, query) => fileName.endsWith(query))
}
def fileStarting(query: String) = {
getMatchingFiles(query, (fileName, query) => fileName.startsWith(query))
}
private def getMatchingFiles(query: String, func: (String, String) => Boolean) = {
for {
file <- filesHere
if (func(file.getName, query))
} yield file
}
}
getMatchingFiles
applies passed function in its function body. In fact, we could make even simpler using a placeholder (_)
object FileMatcher {
private def filesHere = (new File(".")).listFiles
def filesEnding(query: String) = {
getMatchingFiles(_.endsWith(query))
}
def fileStarting(query: String) = {
getMatchingFiles(_.startsWith(query))
}
private def getMatchingFiles(func: String => Boolean) = {
for {
file <- filesHere
if func(file.getName)
} yield file
}
}
Here is what’s happening in filesEnding
and filesStarting
- It passes a function (WHAT TO DO) with
query
- A helper function,
getMatchingFiles
, applies passed function using the filename, returning a list of files that passed the condition
Conclusion
I think it does take some practices until you get familiar with higher-order function. But the key is to extract the common components out of the functions. I hope this short article gives a better understanding on how to apply higher-order function to reduce code duplication.
Reference
Chapter 9.1 Reducing code duplication — Programming in Scala