One basic feature of functional programming languages is to use functions like variables. Therefore functions may be used as parameters in calculations or even as parameters for other functions. This allows to write very clean code which reduces redundancy. Within this article I want to show you how to write Higher-order functions which solve common tasks and help to reduce the redundancy in you source code. The example code is written in F#.
Higher-order functions are functions which have one or more other functions as input parameters. To show this principle we will start with an example implementation. We want to implement the following functions:
– Increase a value multiple times
– Decrease a value multiple times
– Double up a value multiple times
– Append one or more whitespaces to a string
All four functions therefore have a value as input and an integer parameter which contains the number of times the calculation should be executed. For example “Increase multiple times” with the value 5 as input and a count of 3 should increase the value 5 three times by one, which results in a function return value of 8. Of course this functionality may be implemented by using the “+” operator but in this example we will implement functions which execute their work by using recursion.
The following source code shows the four functions.
let rec increaseMultipleTimes(x,n) = if n = 0 then x else 1 + increaseMultipleTimes(x, n-1) let rec decreaseMultipleTimes(x,n) = if n = 0 then x else decreaseMultipleTimes(x, n-1) - 1 let rec doubleMultipleTimes(x,n) = if n = 0 then x else 2 * doubleMultipleTimes(x, n-1) let rec appendWhitespaceMultipleTimes(x,n) = if n = 0 then x else appendWhitespaceMultipleTimes(x, n-1) + " " let a = increaseMultipleTimes(2,5) let b = decreaseMultipleTimes(7,3) let c = doubleMultipleTimes(4,3) let d = appendWhitespaceMultipleTimes("Hello World",3)
If you execute this source code it will show the following output:
val a : int = 7
val b : int = 4
val c : int = 32
val d : string = „Hello World “
If you implement the four functions, latest on the third function you will do the following: You copy and paste the previous function, rename it and make some little adaptions. If you implement something by copy, paste and adapt it is an indicator that you will violate the “Don’t Repeat Yourself” rule. In such cases you will often implement redundant source code.
Therefore we have to ask our self how we may improve the implementation to remove redundancy. In this case we can use Higher-order functions. All four functions have one common task, they do something multiple times. Therefore it is possible to implement a function which executes “something” in other words which executes a function multiple times. The following source code shows an according function.
let rec executeMultipleTimes(f,x,n) = if n = 0 then x else f (executeMultipleTimes(f,x,n-1))
This Higher-order function takes another functions as parameter and like int the previous functions we have Parameters for the value and the number of repetitions. The input function will be executed multiple times on the value to calculate the result.
Now we have to implement four functions which increase, decrease, double a value or add the needed whitespace.
The following source code shows the full implementation with the Higher-order function, the four calculation functions and how to use all these functions.
let rec executeMultipleTimes(f,x,n) = if n = 0 then x else f (executeMultipleTimes(f,x,n-1)) let increase x = x + 1 let decrease x = x - 1 let double x = 2 * x let appendWhitespace x = x + " " let a = executeMultipleTimes(increase,2,5) let b = executeMultipleTimes(decrease,7,3) let c = executeMultipleTimes(double,4,3) let d = executeMultipleTimes(appendWhitespace,"Hello World",3)
Of course, this source code creates the same output like the first example.
If you compare the source code with and without the Higher-order function, you will see different things. I think the second example is easier to read, easier to understand and maybe easier to change because it does not contain redundant code. But there is one big issue with the new solution. It is not compatible with the previous one. If the source code was used in a larger context, then it cannot longer be used without adaptations because instead of the origin four functions with two parameters we now have one function with three parameters. But this issue may be solved easily by providing the origin interface. The following source code shows this enhancement.
let rec executeMultipleTimes(f,x,n) = if n = 0 then x else f (executeMultipleTimes(f,x,n-1)) let increase x = x + 1 let decrease x = x - 1 let double x = 2 * x let appendWhitespace x = x + " " let increaseMultipleTimes(x,n) = executeMultipleTimes(increase,x,n) let decreaseMultipleTimes(x,n) = executeMultipleTimes(decrease,x,n) let doubleMultipleTimes(x,n) = executeMultipleTimes(double,x,n) let appendWhitespaceMultipleTimes(x,n) = executeMultipleTimes(appendWhitespace,x,n) let a = increaseMultipleTimes(2,5) let b = decreaseMultipleTimes(7,3) let c = doubleMultipleTimes(4,3) let d = appendWhitespaceMultipleTimes("Hello World",3)
One big advantage of the refactored code with the Higher-order function can be shown if we try to extend the source code with a new function. What if we have a new requirement and we need a new function to triple up a value multiple times. The following source code shows what we have to implement in the three cases shown previously.
//case 1: without higher order function let rec tripleMultipleTimes(x,n) = if n = 0 then x else 3 * tripleMultipleTimes(x, n-1) //case 2: with higher order function let triple x = 3 * x //case 3: with higher order function and additional adapter function let triple x = 3 * x let tripleMultipleTimes(x,n) = executeMultipleTimes(triple,x,n)
In this example you see the big advantage of Higher-order functions. In case 1 you have to add additional redundant code. In case 2 and 3 it is sufficient to implement only the single task to triple up a value.
Higher-order functions are a very strong feature of functional programming languages. They are ease to create, easy to use and may remove redundant source code. Therefore most functional programming languages offer a wide range of already implemented Higher-order functions to execute common tasks, for example analyse, filter and map data.
The source code of this example may be improved by using another language feature: “anonymous functions”. Within the next article I will show you how to create anonymous functions to further improve the source code of the example implementation.