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”.

A simple HOF

In Javascript, arrays have a number of methods that are higher-order functions.

One simple one is every which takes a function and returns true if the function returns true for every element in the array.

Suppose we have this function, which given a number from 0.0 to 1.0 says whether it should be considered “heads” (as in a coin flip).

const heads = (n) => n > 0.5;

(Incidentally, functions that return a boolean value are sometimes called “predicates”.)

Now lets make an array tosses that contains a bunch of random numbers from 0.0 to 1.0.

const tosses = [];
for (let i = 0; i < 10; i++) {
  tosses.push(Math.random());
}

Checking if all heads, without HOFs

let allHeads = true;
for (let i = 0; i < tosses.length; i++) {
  if (!heads(tosses[i])) {
    allHeads = false;
  }
}
// allHeads is either true or false

The same, with every

const allHeads = tosses.every(heads); // either true or false

Using an anonymous function

const allHeads = tosses.every(n => n > 0.5);

Some important HOF methods on arrays

filter

Takes a one-argument predicate, i.e. a function that takes one argument and returns true or false.

Returns an array containing only those elements of the original array for which the predicate returns true.

const ns = [1, 2, 3, 4]
ns.filter((n) => n % 2 === 0) ⟹ [2, 4]

map

Takes a one-argument function.

Returns an array of same size as the original but with new values produced by calling the function on each of the original elements.

const ns = [1, 2, 3, 4]
ns.map((n) => n ** 2) ⟹ [1, 4, 9, 16]

reduce

Takes a two-argument function and an initial value.

Returns a value produced by repeatedly calling the function with to either the initial value or the previous value returned by the function and the next element of the array.

const ns = [1, 2, 3, 4]
ns.reduce((tot, n) => tot + n, 0) ⟹ 10

flatMap

Takes a one-argument function that returns an array.

Returns an array with the elements of all the arrays returned by calling the function on each of the elements of the original array flattened into a single array.

const ns = [1,2,3,4]
ns.flatMap((x) => [x, x]) ⟹ [1, 1, 2, 2, 3, 3, 4, 4]

every

Takes a predicate function.

Returns a boolean which is true, if and only if the predicate returns true for every element of the array.

const ns = [1,2,3,4]
ns.every((n) => n % 2 === 0) ⟹ false

some

Takes a predicate function.

Returns a boolean which is true, if and only if the predicate returns true for some element of the array.

const ns = [1,2,3,4]
ns.some((n) => n % 2 === 0) ⟹ true

Chaining

Start with some definitions:

const ns = [1,2,3,4]
const isEven = (n) => n % 2 === 0
const square = (n) => n ** 2;
const add = (a, b) => a + b;

Then:

ns.filter(isEven) ⟹ [2, 4]
ns.filter(isEven).map(square) ⟹ [4, 16]
ns.filter(isEven).map(square).reduce(add, 0) ⟹ 20