JavaScript:
Higher-order Functions
Leturer: Иo1lz
OUTLINE
- Preface
- Abstraction
- Higher-order Functions
- Summarizing with Reduce
Preface
let total = 0, count = 1;
while(count <= 10){
total += count;
count += 1;
}
console.log(total);
console.log(sum(range(1, 10)));
![](https://thefinanser.com/wp-content/uploads/2019/06/wtf-serious.png)
Abstraction
- hide details
- give us the ability to talk about problems at a higher (or more abstract) level
console.log(sum(range(1, 10)));
the functions sum、range
![](https://www.vibrantplate.com/wp-content/uploads/2018/06/Pea-soup-01-683x1024.jpg)
As an analogy, compare two reipes for pea soup.
![](https://media.tenor.com/images/786d5d58ae961e0caef2e4fa0170b0dc/tenor.gif)
![](https://www.vibrantplate.com/wp-content/uploads/2018/06/Pea-soup-01-683x1024.jpg)
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.
![](https://www.vibrantplate.com/wp-content/uploads/2018/06/Pea-soup-01-683x1024.jpg)
Per person: 1 cup dried split peas, half a chopped onion, 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.
The second is shorter and easier to interpret.
But ... you do need to understand a few more cooking related words.
In programming, to notice when you are working at too low a level of abstraction.
Abstracting Repetition
for(let i = 0;i < 10;i++){
console.log(i);
}
Can we abstract "doing something N times" as a function?
function repeatLog(n){
for(let i = 0;i < n;i++){
console.log(i);
}
}
But what if we want to do something other than logging the number?
function repeat(n, action){
for(let i = 0;i < n;i++){
action(i);
}
}
repeat(3, console.log);
// -> 0
// -> 1
// -> 2
We don't have to pass a predefined function to repeat
let labels = [];
// define repeat() here
repeat(5, i => {
labels.push(`Unit ${i + 1}`);
});
console.log(labels);
// -> ["Unit 1", "Unit 2", "Unit 3", "Unit 4", "Unit 5"]
Higher-order Functions
Function that operate on other functions, either by taking them or by return them.
function greaterThan(n){
return m => m > n;
}
let greaterThan10 = greaterThan(10);
console.log(greaterThan10(11));
// -> true
1. functions that create new 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
2. functions that change other functions
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
3. functions that provide new types of control flow
There is a built-in array method => forEach
["A", "B"].forEach(l => console.log(l));
// -> A
// -> B
Script Data Set
One area where higher-order functions shine is data process
To process data, we'll need some actual data.
The example data set contains some pieces of information about the 140 scripts defined in Unicode
![](https://media.tenor.com/images/b846e630b6c00bdde91f708946a0d460/tenor.gif)
{
name: "Coptic",
ranges: [[994, 1008], [11392, 11508], [11513, 11520]],
direction: "ltr",
year: -200,
living: false,
link: "https://en.wikipedia.org/wiki/Coptic_alphabet"
}
Download the file scripts.js
require('./<path>/scripts.js');
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", ...}, ...]
console.log(SCRIPTS.filter(s => s.direction == "ttb"));
// -> [name: "Mongolian", ...}, ...]
Like forEach, filter is a standard array method
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
Another common thing to do with arrays is to compute a single value from them.
The higher-order operation that represents this pattern is called 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 has an added convenience
console.log([1, 2, 3, 4].reduce((a, b) => a + b));
// -> 10
Use reduce to find the script with the most characters
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
Consider how we would have written the previous example 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
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
Recognizing Text
We have a characterScript function and a way to correctly loop over characters.
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}, ...]
function textScripts(text){
let script = countBy(text, char => {
let script = characterScript(char.codePointAt(0));
return script ? script.name : "none";
}).filter(({name}) => name != "none");
let total = scripts.reduce((n, {count}) => n + count, 0);
if (total == 0) return "No scripts found";
return srcipts.map(({name, count}) => {
return `${Math.round(count * 100 / total)} % ${name}`;
}).join(", ");
}
console.log(textScripts('英國的狗說"woof", 俄羅斯的狗說"тяв"'))
// -> 61% Han, 22% Latin, 17% Cyrillic
Thanks for listening.
JavaScript: Higher-order Functions
By Иo1lz
JavaScript: Higher-order Functions
This is the slide that refer from "Eloquent JavaScript" chapter 5.
- 146