Clojure & JavaScript Higher-order Functions
It was not easy for me to learn JavaScript. And when it came to learning higher-order functions, I had to take a break — literally get up and take a walk around the block — because I couldn’t seem to understand it. But through my pain, I’ve put together a short guide that hopefully makes it clearer for you.
Higher-order functions are functions that take other functions as arguments or return them as output, as opposed to normal functions that take as arguments and return non-functions. Functions that are passed as an argument to another function are what we refer to as callback functions. In this post we learn about higher-order functions through examples in JavaScript and Clojure, two functional languages with very different syntax.
Why are they necessary?
Flexibility! Higher-order functions allow you to change how a function behaves by adding your own behavior to its existing behavior. You are able to create something complex that is specific to your needs hence people refer to higher-order functions as a “powerful abstraction tool.”
In this post, I will focus on the three most commonly-used higher-order functions: filter, reduce, and map.
Filter
This function takes a callback function and a collection. It returns a sequence in Clojure, or an array in JavaScript, containing the items in the collection which meet the condition of the function.
Example in Clojure
(def numbers [1 20 23 4 75 3])
(filter #(>= % 20) numbers)
;;; output
;;; (20 23 75)
Example in Javascript
const numbers = [1, 20, 23, 4, 75, 3];
const greaterNumbers = numbers.filter(num => {
return num >= 20 });
console.log(greaterNumbers);
// output
// [20, 23, 75]
Map
Map takes a function and a collection. It then returns a lazy sequence in Clojure, or an array in JavaScript, that is the result of applying the function to each item in the collection.
Example in Clojure
(map #(* % 10) [1 2 3 4 5])
;;; output
;;; (10 20 30 40 50)
Example in Javascript
const numbers = [1, 2, 3, 4, 5]
const multipleNumbers = numbers.map(number => {
return number * 10 });
console.log(multipleNumbers)
// output
// [10, 20, 30, 40, 50]
Reduce
Reduce also takes a callback function and a collection. The callback function should be a binary function, that is it should take two arguments. If on calling the callback is passed a value, the callback function takes that value and the first item in the collection to give a result. If it is no passed a value on the first iteration, the callback function begins by taking the first and second items in the collection as arguments, and returns a result. Let’s see this in action:
Example in Javascript
Without a value
const numbers = [1, 2, 3, 4];
const summedNums = numbers.reduce((accumulator, currentValue) => {
return accumulator + currentValue });
console.log(summedNums);
// output
// 10
In the above example, the callback function took the first two items and added them together, then used the result of that operation as the first argument in the second iteration. The below type describes the steps in this iteration:
With a value
const numbers = [1, 2, 3, 4];
const summedNums = numbers.reduce((accumulator, currentValue) => {
return accumulator + currentValue }, 100);
console.log(summedNums);
// output
// 110
In this case we pass 100 as the first argument to the callback function, and the iteration proceeds as in the table below:
Example in Clojure
Without a value
(reduce + [1 2 3 4])
;;; output
;;; 10
Here the + is the callback function that will take the first and second item and loop through.
With a value
(reduce + 100 [1 2 3 4])
;;; output
;;; 110
That is it! At the end of the day, higher-order functions are quite similar regardless of the programming language you are using. As long as you understand the general concepts and goals of higher-order functions in the language of your choice, you will be able to reapply that knowledge and use those same higher-order functions in other languages.
With this knowledge, I can now confidently create my own higher-order functions to write maintainable code in multiple languages. Higher-order functions allow me to write code that uses fewer lines while still being understandle. With higher-order functions the code is less repetitive and more efficient.