Functional JavaScript
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