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