beginning javascript
higher order functions

Higher-order Functions

A higher-order function is a function that accepts another function as an argument value (hofFunc1 below) or a function whose return value is another function (hofFunc2 below):

function hofFunc1 (argumentFunc) {
  console.assert(typeof argumentFunc === 'function');
}

hofFunc1(function() {});

// **************

function hofFunc2 () {
  return function() {};
}

console.assert(typeof hofFunc2() === 'function');

typeof and console.assert

This is the first time you are seeing the typeof keyword. It is a special operator that returns a string indicating the type of its operand:

typeof 3.14 === 'number';
typeof 'hello' === 'string';
typeof true === 'boolean';
typeof {a: 1} === 'object';
typeof [1, 2, 4] === 'object';
typeof new Date() === 'object';
typeof function() {} === 'function';
Note how the type of arrays and dates were reported as objects. Do not rely on typeof to check for those types.

This is also the first time you are seeing the console.assert function. Assertions give us a simple tool to use to test our code. There are many assertion libraries that you can use, but the simplest assert function is the one that is built into the console object, which is available in your browser. If the expression you pass to console.assert is evaluated as false, you get an Assertion failed error. The expressions used for the above assertions are all true.

Callbacks

We have used higher-order functions in previous articles but they did not have the fancy name yet back then.

Here is an example:

function squareNumbersInList(list) {
  return list.map(number => number * number);
}

The map function above is a higher-order function because when we called it we passed another function as its only argument value. The map function applies its argument function to each element of the list on which it was called.

The function that we pass in as the argument of a higher-order function is sometimes referred to as a callback. The callback arguments and return value are both important and what they mean depends on the higher-order function that is consuming the callback.

In the map example above, the callback function is an inline anonymous function.

To make the definition of higher-order functions more obvious, we can define a separate function, name it square, have it take one argument, and return its squared value:

function square(e) {
  return number * number;
}

We can now pass this new square function reference as the callback argument value for the map function.

list.map(square);

Note how we pass a reference to the function. We do not invoke the square function. The map function is going to invoke the square function on every element. This is true for all higher-order functions: the value we pass to them is just a function reference that represents the definition of a function.

Exercise:

Filter is another higher-order function. In the template below, you have an array and a callback function and your task is to pass the correct argument value for the filter function.

function isOdd(e) {
  return e % 2 === 1;
}

// Complete the following line
// to make newArray = [1, 3] using a filter call
const newArray = [1, 2, 3].filter;
console.log(newArray);

Functions are First-Class Objects

The most important fundamental thing to understand about JavaScript is the function concept. The statement that functions are first-class citizens or objects simply means that functions in JavaScript can be treated just like any other JavaScript Object. Anywhere you can use an object you can also use a function.

Functions can be assigned to variables, array entries, and properties of other objects:

const add = (a, b) => a + b;
const arr = [add, 1, 2];

const obj = {
  operation: add,
  args: arr.slice(1),
};

console.log(obj.operation(...obj.args));

They can be declared with literals and we can define properties on them. We can also access those properties from within the functions:

function multiplyBy(a) {
  return multiplyBy.factor * a;
}

multiplyBy.factor = 5;

They can also be passed as other function parameters and be returned as values from other functions:

function host(func) {
  // do something with func
  return func;
}

const result = host(multiplyBy)(3);

console.log(result);

The code above makes the host function a higher-order one.

Functions are basically objects with the special capability of being callable.

Let’s Build a Simple Calculator

Now that we understand higher-order functions and the concept of functions as first-class objects in JavaScript. Let’s write a simple but funky calculator! Here is how I would like this calculator to be used:

// Define 3 operations, add, multiply, subtract
// const add = (a, b) => a + b;
// const multiply = ...
// const subtract = ...

// **** The Desired API ****
//
// calculator(3)     // Start with 3
//  (add, 4)         // Add 4 to 3
//  (multiply, 5)    // Multiply 5 by 7
//  (subtract, 6, function(result) {
//    console.log(result); // After subtracting 6 from 35
//  });

When we invoke the desired calculator function, it returns what seems to be another function that we can execute with an operation and an argument. This makes the calculator function a higher-order one simply because it returns another function.

The function that calculator returns is itself a higher-order function because we are passing it other functions. We can pass it an operation (like add, multiply, and subtract), an operand value to carry that operation on the calculator value. It also accepts an optional third-argument callback function to do something with the result (like the callback for the subtract call above).

Let’s build this calculator one step at time. Try to solve the following challenges by yourself first.

Challenge #1: Multiply and Subtract

Your first simple challenge: I already defined the add function above. Go ahead and define both multiply and subtract in the same fashion. Here are some assertions you can use:

You want to make sure that all expressions in console.assert statements are true.

Solution:

We simply define multiply to return the product of its two arguments and subtract to do a subtraction operation on its two arguments.

const multiply = (a, b) => a * b;
const subtract = (a, b) => a - b;

We have our operations, so let’s now define our Calculator function.

Challenge #2: Functions that Return Functions

Your next challenge is to make the following assertion pass:

// Define the calculator function

console.assert(typeof calculator() === 'function');

Basically, you need to define a calculator function that returns another function.

Solution:

To make the assertion above pass, all we need to do is define a calculator function and make it return another function. Let’s name the returned function doOperation:

function calculator(total) {
  function doOperation() {}

  return doOperation;
}

Challenge #3: Functions that Return Functions that Return Functions

We have a function that returns another function. However, in the desired API above, we chained function calls, so not only is calculator returning a function, but the function it returns should also return a function.

Your challenge: Make the following two assertions pass.

console.assert(typeof calculator()() === 'function');

console.assert(typeof calculator()()() === 'function');

Solution:

We can simply make the doOperation function return itself.

function calculator(total) {
  function doOperation() {
    return doOperation;
  }
  return doOperation;
}

This way, every time we execute doOperation, the return value is also doOperation and we can keep doing operations as we designed in the API.

Challenge #4: Functions as Arguments

Let’s now work on the doOperation arguments. We designed the API so that this doOperation function takes two arguments and an optional third callback function. Let’s begin with the first two required arguments.

  • An operation, which itself is a function

  • An operand value to be carried as one of the operation arguments

The calculator function receives its initial value as its only argument. This is the starting state of this simple application. I named that value total. The calculator function needs to keep track of that. Operations need to change that.

function calculator(total) {
    function doOperation(operation, operand) {
      // do something on total console.log(total);
      return doOperation;
    }

    return doOperation;
  }

// Test with
calculator(3)
  (add, 4)
  (multiply, 5)
  (subtract, 6, function(result) {
    console.log(result);
  });

Your challenge: Inside the doOperation function, change the total variable to be the result of executing the operation function on both total and the operand value for operation.

When executing your solution with the desired API example, you should see three lines in the output: 7, 35, and 29.

Solution:

We simply assign a new value to total. This value is coming from invoking the operation function with two arguments of its own. The first argument is current total value and the second argument is the operand argument that we pass to doOperation.

function calculator(total) {
  function doOperation(operation, operand) {
    total = operation(total, operand);
    console.log(total);
    return doOperation;
  }

  return doOperation;
}

Challenge #5: The Optional Callback

The last argument that we can pass to doOperation is an optional callback. The API defined that to receive a result argument. The result it receives is the calculator’s total value so far.

function calculator(total) {
  function doOperation(operation, operand, callback) {
    total = operation(total, operand);

    // maybe invoke the callback...
    // "result" inside the callback is "total"

    return doOperation;
  }
  return doOperation;
}

Try to implement this callback function on your own first. Remember it is optional.

Solution:

We simply use an if statement. If there is a callback value, then we want to invoke it using the total variable as its arguments:

function calculator(total) {
  function doOperation(operation, operand, callback) {
    total = operation(total, operand);

    if (callback) {
      callback(total);
    }

    return doOperation;
  }
  return doOperation;
}

Note that while this calculator uses the higher-order function concept, it is not really completely written with a functional programming style. The calculator uses a shared mutable state (total) and it has manual instructions (like the if statement). Making the calculator above completely functional will require more code and more concepts to understand.

I think it’s okay to have a minimally contained mutable state that can be tested with many known cases. However, you need to learn how to work with all programming paradigms and then make decisions on which one to use. Each application will have different requirements.

In the next article, let’s explore some unique challenges to get you more comfortable with declarative programming.