Functions as values

Functions are just another kind of value

Just like numbers, strings, booleans, arrays, and objects

This means …

Anywhere you can assign or otherwise use a value, you can assign or use a function as the value.

Variables

This is the case we’re used to.

const add = (a, b) => a + b;

The const add just declares a variable.

The = assigns it a value.

And the value is the function (a, b) => a + b.

» add(10, 20)
30

Assign to a different variable

const anotherName = add

This is evaluated by first evaluating the variable add which gives us the function value.

Note it does not call the function; it just gets the value.

That value is then assigned to the variable anotherName.

» anotherName(10, 20)
30

Anonymous functions

We can also use functions without giving them a name.

» ((n) => n ** 2)(10)
100

Event handlers

One scenario we’ve seen recently for assigning a function to the property of an object is to register an event handler.

Adding a click handler

const b = document.querySelector('button');

b.onclick = (e) => {
  console.log('Button was clicked');
};

This works because when a button in a web page is clicked, the browser will find the value of the button object’s onclick property and call it.

Named event handlers

We can also define a named function

const clicked = (e) => {
  console.log('Button was clicked');
};

And then use it as an event handler

const b = document.querySelector('button');

b.onclick = clicked;

Once again, note no ()s after clicked as we are not calling it, we are just getting its value.

What about function arguments and return values?

So far this year we’ve worked with functions that take numbers, booleans, strings, arrays, and objects as arguments and return them as values.

Can we pass a function as an argument to another function? Can we return a function as a value?

Yes!

Higher-order functions

Functions that take functions as arguments or return functions as a values, are called “higher-order functions” or “HOFs”.

Why is this useful?

Consider these two functions:

const addTwo = (number) => number + 2;

const addThree = (number) => number + 3;

Those are obviously silly. But there’s a pattern which we can capture with this function:

const addN = (number, n) => number + n;

I.e. we use another argument to abstract the difference between the two earlier functions.

Now how about these?

const firstEven = (numbers) => {
  for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] % 2 === 0) return numbers[i];
  }
};
const firstOdd = (numbers) => {
  for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] % 2 !== 0) return numbers[i];
  }
};

What’s the same?

What’s the difference?

The criteria:

numbers[i] % 2 === 0

vs

numbers[i] % 2 !== 0

Unlike in addTwo and addThree the difference is not just a value; it’s code.

Let’s rejigger things a bit

Define a function to capture the even test:

const isEven = (n) => n % 2 === 0;

And rewrite firstEven to use it:

const firstEven = (numbers) => {
  for (let i = 0; i < numbers.length; i++) {
    if (isEven(numbers[i])) return numbers[i];
  }
};

Works just the same as before. But now it’s calling a function to check the criteria.

Now make the function an argument

const firstEven = (numbers, p) => {
  for (let i = 0; i < numbers.length; i++) {
    if (p(numbers[i])) return numbers[i];
  }
};

Called like this:

firstEven(numbers, isEven)

Note that it’s isEven not isEven(). We’re not calling isEven; we’re passing it as a value.

Rename since it’s more general

const firstMatching = (numbers, p) => {
  for (let i = 0; i < numbers.length; i++) {
    if (p(numbers[i])) return numbers[i];
  }
};

Call as before:

firstMatching(numbers, isEven)

Or with anonymous functions.

firstMatching(numbers, (n) => n % 2 === 0) // first even
firstMatching(numbers, (n) => n % 2 !== 0) // first odd

Bonus: returning functions

We can also write functions that return functions:

const adder = (n) => {
  return (x) => x + n;
};

This function takes an argument, n, and returns a new function that takes a single argument and return that argument added to n.

Using adder

This lets us define new functions like this:

const addTwo = adder(2);
const addThree = adder(3);

And we can use them just like before:

addTwo(5) ⟹ 7

addThree(5) ⟹ 8

Function as argument and returned value

const complement = (f) => {
  return (x) => !f(x);
}

What does this do?

firstMatching(numbers, complement(isEven));

Another way to find the first odd number.