Functional JavaScript

Notes from Functional JavaScript First Steps lections
Updated 22 Apr 2021  · 

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

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

Recursive functions have two parts:

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:

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