Refactoring has and always been a part of development. On this article, we’re going to investigate on how we can use higher-order functions to remove code duplication.

Supposed, we are tasked to create functions to sum all the numbers from a given ‘x’ to ‘y’ number, then sum of the doubles, then sum of the square. Then we are tasked to do the same thing but instead of sum we get the product. The first thing that we do is to make everything work first before we do the refactoring.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | // add all numbers from 'from' to 'to' function add(from, to) { let total = 0; while(from <= to) { total = total + (from++); } return total; } // add all the double of numbers from 'from' to 'to' function addDouble(from, to) { let total = 0; while(from <= to) { let processed = from + from total = total + processed; from++; } return total; } // add all the square of numbers from 'from' to 'to' function addSquare(from, to) { let total = 0; while(from <= to) { let processed = from * from total = total + processed; from++; } return total; } // multiply all the numbers from 'from' to 'to' function product(from, to) { let total = 1; while(from <= to) { total = total * (from++); } return total; } // multiply all the double of numbers from 'from' to 'to' function productDouble(from, to) { let total = 1; while(from <= to) { let processed = from + from total = total * processed; from++; } return total; } // multiply all the square of numbers from 'from' to 'to' function productSquare(from, to) { let total = 1; while(from <= to) { let processed = from * from total = total * processed; from++; } return total; } console.log(15, add(1,5)) console.log(30, addDouble(1,5)) console.log(55, addSquare(1,5)) console.log(120, product(1,5)) console.log(3840, productDouble(1,5)) console.log(14400, productSquare(1,5)) |

So, once we have the code working, we can now start refactoring our code. The first step is to see the commonality of the functions. We’re going to start with the functions that has the most common code. So, based on our example we can say that all the add can be grouped together with the most common code, then product functions can be grouped together as well.

Let’s start refactoring all of our add function. As you can see the main difference of all our add function are the lines below.

1 2 3 | let processed = ... ( add = from, addDouble = from + from, addSquare = from * from) total = total + processed; from++; |

With that in mind, let’s create a function called sum that accepts another function to do the processing and return a processed number.

1 2 3 4 5 6 7 8 9 | function sum(processNumberFn, from, to) { let total = 0; while(from <= to) { let processed = processNumberFn(from); total = total + processed; from++; } return total; } |

With our new sum function, we can then refactor all of our existing add functions.

1 2 3 4 5 6 7 8 9 10 11 | function add(from, to) { return sum(x => x, from, to); } function addSquare(from, to) { return sum(x => x * x, from, to); } function addDouble(from, to) { return sum(x => x + x, from, to); } |

With the change we were able to remove all the duplicated code on our add functions. We used arrow functions to do the processing of numbers for us as you can see from above. But, we can further refactor our code to execute a the function to return a new function. Let’s do that.

1 2 3 4 5 6 7 8 9 10 11 | function sum(processNumberFn) { return (from, to) => { let total = 0; while(from <= to) { let processed = processNumberFn(from); total = total + processed; from++; } return total; } } |

1 2 3 | var add = sum(x => x); var addSquare = sum(x => x * x); var addDouble = sum(x => x + x); |

With our recent change, we further reduced the number of lines to all of our add functions to a single-liner.

Let’s do the same thing for our product functions and see the difference from our original code above.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | function sum(processNumberFn) { return (from, to) => { let total = 0; while(from <= to) { let processed = processNumberFn(from); total = total + processed; from++; } return total; } } function multiply(processNumberFn) { return (from, to) => { let total = 1; while(from <= to) { let processed = processNumberFn(from); total = total * processed; from++; } return total; } } var add = sum(x => x); var addSquare = sum(x => x * x); var addDouble = sum(x => x + x); var product = multiply(x => x); var productSquare = multiply(x => x * x); var productDouble = multiply(x => x + x); console.log(15, add(1,5)) console.log(30, addDouble(1,5)) console.log(55, addSquare(1,5)) console.log(120, product(1,5)) console.log(3840, productDouble(1,5)) console.log(14400, productSquare(1,5)) |

We reduced our code to almost half! But, we still see code duplication between sum and multiply function. In fact, there are only two lines the differentiates the two as you can see below.

1 2 3 4 5 6 7 | let total = ...; while(from <= to) { let processed = processNumberFn(from); total = ...; from++; } return total; |

With this knowledge, we can create a generic function that can accept those changes. Let’s look at our new function.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function calculate(initialValue, operationFn, processNumberFn) { return (from, to) => { let total = initialValue; while(from <= to) { let processed = processNumberFn(from); total = operationFn(total, processed); from++; } return total; } } function sum(processNumberFn) { return calculate(0, (x,y) => x + y, processNumberFn) } function multiply(processNumberFn) { return calculate(1, (x,y) => x * y, processNumberFn) } |

We can choose to wrap the function again but then it will not be as readable as the one that we have now in my opinion.

Look at the wrapped version below and I’ll let you decide.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function calculate(initialValue, operationFn) { return (processNumberFn) => { return (from, to) => { let total = initialValue; while(from <= to) { let processed = processNumberFn(from); total = operationFn(total, processed); from++; } return total; } } } var sum = calculate(0, (x,y) => x + y); var multiply = calculate(1, (x,y) => x * y); |

Well, it’s not as bad as it looks but usually I prefer not to have functions that are too deep. Like the one above, we see a function that returns a function that returns another function. Kind of confusing isn’t it?