Notes from Functional JavaScript First Steps lections
Resources
Pure functions
Avoid side effects
Do nothing but return output based on nothing but input.
Imperative
let name = "Alonzo";
let greeting = "Hi";
console.log(`${greeting}, ${name}!`);
// Hi, Alonzo!
greeting = "Howdy";
console.log(`${greeting}, ${name}!`);
// Howdy, Alonzo!
Functional
function greet(greeting, name) {
return `${greeting}, ${name}!`;
}
greet("Hi", "Alonzo");
// "Hi, Alonzo!"
greet("Howdy", "Alan");
// "Howdy, Alan!
Side effects
let thesis = { name: "Church's", date: 1936 };
function renameThesis(newName) {
thesis.name = newName;
console.log("Renamed!");
}
renameThesis("Church-Turing"); // Renamed!
thesis; //{name: "Church-Turing", date: 1936}
No side effects
const thesis = { name: "Church's", date: 1936 };
function renameThesis(oldThesis, newName) {
return {
name: newName,
date: oldThesis.date,
};
}
const thesis2 = renameThesis(thesis, "Church-Turing");
thesis; // {name: "Church's", date: 1936}
thesis2; // {name: "Church-Turing", date: 1936}
Recursion
- Iteration — imperative, looping, stateful
- Recursion — functional, self-referential, stateless
Some examples of achieving the same effect through iteration and recursion.
Iteration
function sum(numbers) {
let total = 0;
for (i = 0; i < numbers.length; i++) {
total += numbers[i];
}
return total;
}
sum([0, 1, 2, 3, 4]); // 10
Recursion
function sum(numbers) {
if (numbers.length === 1) {
// base case
return numbers[0];
} else {
// recursive case
return numbers[0] + sum(numbers.slice(1));
}
}
sum([0, 1, 2, 3, 4]); // 10
Iteration
function iterativeFibonacci(n) {
if (n === 0) return 0;
if (n === 1) return 1;
let previous = 0;
let current = 1;
for (let i = n; i > 1; i--) {
let next = previous + current;
previous = current;
current = next;
}
return current;
}
Recursion
function recursiveFibonacci(n) {
if (n === 0) return 0;
if (n === 1) return 1;
return recursiveFibonacci(n - 2) + recursiveFibonacci(n - 1);
}
Higher-order function
The higher-order functions filter(), map(), and reduce() are three of the
most useful tools in a functional programmer’s toolbox. Let’s dig into how they
work & how to use them.
Link to my fork of exercises on Observable: https://observablehq.com/d/3003212404713bcf
Filter
The filter function takes a “predicate” function (a function that takes in a value and returns a boolean) and an array, applies the predicate function to each value in the array, and returns a new array with only those values for which the predicate function returns true.
Here’s a recursive implementation of the filter() function:
function filter(predicateFn, array) {
// base case
if (length(array) === 0) return [];
// recursive case
const firstItem = head(array);
const filteredFirst = predicateFn(firstItem) ? [firstItem] : [];
return concat(filteredFirst, filter(predicateFn, tail(array)));
}
wholes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
greaterThanFour = filter((n) => n > 4, wholes);
// greaterThanFour is [5,6,7,8,9,10]
Map
The map function takes a one-argument function and an array, and applies the function to each element in the array, returning a new array of the resulting values.
function map(fn, array) {
// base case
if (length(array) === 0) return [];
// recursive case
const first = head(array);
const mappedFirst = [fn(first)];
return concat(mappedFirst, map(fn, tail(array)));
}
wholes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
doubled = map((n) => n * 2, wholes);
// doubled is [0,2,4,6,8,10,12,14,16,18,20]
wholes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
fizzBuzz = map((n) => {
const fizz = n % 3 === 0 ? "fizz" : "";
const buzz = n % 5 === 0 ? "buzz" : "";
return fizz || buzz ? fizz + buzz : n;
}, wholes);
// fizzBuzz is [fizzbuzz,1,2,fizz,4,buzz,fizz,7,8,fizz,buzz]
Reduce
The reduce function is the odd one of the bunch. Unlike filter and map,
which each take an array and return another array, reduce takes in an array and
returns a single value - in other words, it “reduces” an array to a single
value.
reduce takes three arguments:
- a “reducer” function, which takes two arguments - an accumulator and the next value from the array - and returns a single value. This function will be applied to each value in the array, with the accumulator storing the reduced value so far.
- an initial value, passed to the first call of the reducer function
- the array to reduce
function reduce(reducerFn, initialValue, array) {
// base case
if (length(array) === 0) return initialValue;
// recursive case
const newInitialValue = reducerFn(initialValue, head(array));
return reduce(reducerFn, newInitialValue, tail(array));
}
wholes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
sum = reduce((accumulator, value) => accumulator + value, 0, wholes);
// sum is 55
max = reduce(
(accumulator, value) => (value > accumulator ? value : accumulator),
0,
[7, 1, 3, 5, 6, 2, 8, 10, 0, 4, 9],
);
// sum is 10