# Chapter 5 - Higher-Order Functions

## Abstraction

compare this:

``````Put 1 cup of dried peas per person into a container. Add water until the peas are well covered. Leave the peas in water for at least 12 hours. Take the peas out of the water and put them in a cooking pan. Add 4 cups of water per person. Cover the pan and keep the peas simmering for two hours. Take half an onion per person. Cut it into pieces with a knife. Add it to the peas. Take a stalk of celery per person. Cut it into pieces with a knife. Add it to the peas. Take a carrot per person. Cut it into pieces. With a knife! Add it to the peas. Cook for 10 more minutes.
``````

to this:

``````Per person: 1 cup dried split peas, half a chopped onion, a stalk of celery, and a carrot.

Soak peas for 12 hours. Simmer for 2 hours in 4 cups of water (per person). Chop and add vegetables. Cook for 10 more minutes.
``````

It is a useful skill, in programming, to notice when you are working at too low a level of abstraction.

## Abstracting repetition

``````function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}

repeat(3, console.log);
// → 0
// → 1
// → 2
``````

or creating our function:

``````let labels = [];
repeat(5, i => {
labels.push(`Unit \${i + 1}`);
});
console.log(labels);
// → ["Unit 1", "Unit 2", "Unit 3", "Unit 4", "Unit 5"]
``````

## Higher-order functions

Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions.

``````function greaterThan(n) {
return m => m > n;
}
let greaterThan10 = greaterThan(10);
console.log(greaterThan10(11));
// → true
``````

functions that change other functions:

``````function noisy(f) {
return (...args) => {
console.log("calling with", args);
let result = f(...args);
console.log("called with", args, ", returned", result);
return result;
};
}
noisy(Math.min)(3, 2, 1);
// → calling with [3, 2, 1]
// → called with [3, 2, 1] , returned 1
``````

new types of control flow:

``````function unless(test, then) {
if (!test) then();
}

repeat(3, n => {
unless(n % 2 == 1, () => {
console.log(n, "is even");
});
});
// → 0 is even
// → 2 is even
``````

There is a built-in array method, forEach, that provides something like a for/of loop as a higher-order function:

``````["A", "B"].forEach(l => console.log(l));
// → A
// → B
``````

## Script data set

SCRIPTS is an array of objects like this one:

``````{
name: "Coptic",
ranges: [[994, 1008], [11392, 11508], [11513, 11520]],
direction: "ltr",
year: -200,
living: false,
}
``````

## Filtering arrays

``````function filter(array, test) {
let passed = [];
for (let element of array) {
if (test(element)) {
passed.push(element);
}
}
return passed;
}

console.log(filter(SCRIPTS, script => script.living));
// → [{name: "Adlam", …}, …]
``````

Like forEach, filter is a standard array method:

``````console.log(SCRIPTS.filter(s => s.direction == "ttb"));
// → [{name: "Mongolian", …}, …]
``````

## Transforming with map

``````function map(array, transform) {
let mapped = [];
for (let element of array) {
mapped.push(transform(element));
}
return mapped;
}

let rtlScripts = SCRIPTS.filter(s => s.direction == "rtl");
console.log(map(rtlScripts, s => s.name));
// → ["Adlam", "Arabic", "Imperial Aramaic", …]
``````

Like forEach and filter, map is a standard array method.

## Summarizing with reduce

``````function reduce(array, combine, start) {
let current = start;
for (let element of array) {
current = combine(current, element);
}
return current;
}

console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0));
// → 10
``````

The standard array method reduce:

``````console.log([1, 2, 3, 4].reduce((a, b) => a + b));
// → 10
``````

To use reduce (twice):

``````function characterCount(script) {
return script.ranges.reduce((count, [from, to]) => {
return count + (to - from);
}, 0);
}

console.log(SCRIPTS.reduce((a, b) => {
return characterCount(a) < characterCount(b) ? b : a;
}));
// → {name: "Han", …}
``````

## Composability

Same code without higher-order functions:

``````let biggest = null;
for (let script of SCRIPTS) {
if (biggest == null ||
characterCount(biggest) < characterCount(script)) {
biggest = script;
}
}
console.log(biggest);
// → {name: "Han", …}
``````

Higher-order functions start to shine when you need to compose operations.
You can see it as a pipeline.

``````function average(array) {
return array.reduce((a, b) => a + b) / array.length;
}

console.log(Math.round(average(
SCRIPTS.filter(s => s.living).map(s => s.year))));
// → 1165
console.log(Math.round(average(
SCRIPTS.filter(s => !s.living).map(s => s.year))));
// → 204
``````

You could definitely also write this computation as one big loop:

``````let total = 0, count = 0;
for (let script of SCRIPTS) {
if (script.living) {
total += script.year;
count += 1;
}
}
console.log(Math.round(total / count));
// → 1165
``````

But it’d be a lot more work to extract something like average into a separate function.

## Strings and character codes

``````function characterScript(code) {
for (let script of SCRIPTS) {
if (script.ranges.some(([from, to]) => {
return code >= from && code < to;
})) {
return script;
}
}
return null;
}

console.log(characterScript(121));
// → {name: "Latin", …}
``````

## Strings and character code

``````// Two emoji characters, horse and shoe
let horseShoe = "🐴👟";
console.log(horseShoe.length);
// → 4
console.log(horseShoe);
// → (Invalid half-character)
console.log(horseShoe.charCodeAt(0));
// → 55357 (Code of the half-character)
console.log(horseShoe.codePointAt(0));
// → 128052 (Actual code for horse emoji)
``````

JavaScript’s charCodeAt method gives you a code unit, not a full character code.
The codePointAt method, added later, does give a full Unicode character.
But the argument passed to codePointAt is still an index into the sequence of code units.

``````let roseDragon = "🌹🐉";
for (let char of roseDragon) {
console.log(char);
}
// → 🌹
// → 🐉
``````

## Recognizing text

``````function countBy(items, groupName) {
let counts = [];
for (let item of items) {
let name = groupName(item);
let known = counts.findIndex(c => c.name == name);
if (known == -1) {
counts.push({name, count: 1});
} else {
counts[known].count++;
}
}
return counts;
}

console.log(countBy([1, 2, 3, 4, 5], n => n > 2));
// → [{name: false, count: 2}, {name: true, count: 3}]
``````