Understanding Generators in JavaScript

March 17, 2020

Redux is built on top of functional programming principles which is foreign to a lot of developers. If you've tried redux and were confused, that's probably because you didn't have a good background in functional programming. In this section, we're going to cover the essential functional programming concepts.

Understanding these concepts is crucial to build redux applications.

Functional Programming

What is functional programming?

Functional programming is one of the many paradigms or styles of programming:

  • Functional

  • Object-oriented

  • Procedural

  • Event-driven

In a nutshell, functional programming is about decomposing a problem into a bunch of small and reusable functions that takes some input and return a result. They don't mutate or change data.

With this structure, we can compose these functions to build more complex functions.

What are the benefits?

These small functions tends to be more concise, easier to debug, easier to test, and more scalable - because we can wrap many function calls in parallel and take advantage of the multiple cores of a CPU.

There are languages that are specifically designed for functional programming such as Clojure and Haskell. JavaScript is a multi-paradigm programming language, it's not a pure functional language, so it has some caveats that you need to be aware of, but you can still apply functional programming principles in JavaScript.

Functions as First-class citizens

In JavaScript, functions are first-class citizens which means that you can treat them just like any other variables. We can:

  1. assign them to a variable
function sayHello() {
  return "Hello World";
}
// assigning func to a variable
let fn = sayHello;
fn();
  1. pass them as an argument
function sayHello() {
  return "Hello World";
}
// greet() takes in fnMessage() as an arg
function greet(fnMessage) {
  // logging out the return value of fnMessage()
  console.log(fnMessage());
}
// calling greet() and passing a reference to the sayHello()
greet(sayHello);
  1. return them from other functions
// returning a function from another function
function sayHello() {
  return function () {
    return "Hello World";
  };
}

let fn = sayHello();
let message = fn();

In the last two examples above, the greet() and sayHello() are called Higher-order functions.

A higher-order function is a function that takes a function as an argument, or returns a function, or does both.

// takes a func as an argument
function greet(fn) {
  console.log(fn());
}

// returns a function
function sayHello() {
  return function () {
    return "Hello World";
  };
}

Instead of working on Strings, Numbers or Booleans, it goes higher to operate on functions. This is the reason why they are called Higher-order functions.

Chances are that you've worked with higher-order functions without being aware of it. A common example of a higher-order function in JavaScript is the Array map() method.

// map() takes a function as an argument
let numbers = [1, 2, 3];
numbers.map((number) => number * 2);

Another example is the setTimeout() function.

// setTimeout() takes a function as an argument
setTimeout(() => console.log("Hello"), 1000);

Function Composition

Earlier, we noted that the idea of functional programming is to write a bunch of small and reusable functions and then compose them to build more complex functions for solving real-world problems. Here's a real example;

Let's say we have a variable input and we equate it to JavaScript , we want to get the input , trim it, and wrap it inside a div element.

  • Non- functional programming style
let input = "    JavaScript    ";
let output = "<div>" + input.trim() + "</div>";
  • Functional programming style
// trim the input
const trim = (str) => str.trim();
// wrap in div
const wrapInDiv = (str) => `<div>${str}</div>`;
// convert to lowercase
const toLowerCase = (str) => str.toLowerCase();

const result = wrapInDiv(toLowerCase(trim(input)));

On the right hand side of our result variable is what is known as function composition. A few problems with this approach is that the value of our result variable has to be read from right to left to actually understand what's going on, and also we have a problem of multiple parenthesis, in our code, which may increase the chances of errors in a much larger function.

A way to simply the cod in our functional programming example is to use a library - Lodash.

Composition and Piping

In case you are not familiar with Lodash, it's a popular JavaScript utility library, it also has a package with a lot of functions for functional programming.

// npm
npm i lodash

// yarn
yarn add lodash

Pure Functions

Another important concept in functional programming is pure functions. We say a function is pure if every time we call it and give it the same argument, it returns the same result.

// this function is not a pure function because any time we call it
// Math.random() generates a new number so the result of this function
// is going to change.
function myFunction(number) {
  return number * Math.random();
}

// In contrast, this function is pure because every time we call it
// and give it 1, we'll always get 2.
function myFunction(number) {
  return number * 2;
}

In pure functions, we:

  • cannot use random values

  • cannot use current date/time

  • cannot read or change global state (DOM, files, db, etc)

  • cannot mutate parameters

When building apps with Redux, we have to make sure that our reducers are pure, other functions can be impure.

What are the benefits of pure functions? They are:

  • Self documenting

  • Easily testable

  • Concurrency

  • Cacheable

Immutability

A concept that goes hand in hand with pure functions is immutability. Which basically means, once we create an object, we cannot change or mutate it. If you want to change an object, you have to take a copy and then change that copy. In JavaScript, objects and arrays are not immutable. Remember, JavaScript is not a pure functional programming language, in pure functional languages, you cannot mutate data period. In JavaScript, we can mutate objects and arrays because JavaScript wasn't designed to be a functional programming language, it's a multi-paradigm programming language, but we can still apply functional programming principles when writing JavaScript code.

Note: const prevents re-assignment not make data immutable

Why Immutability?

  • Predictability

  • Faster change detection

  • Concurrency

Cons of immutability

  • performance

  • memory overhead

If you're building apps in Redux, you should not mutate data, that's a fundamental principle in Redux.

Updating objects

const person = {
  name: "John",
  address: { country: "USA", city: "San Francisco" },
};
// const updated = Object.assign({}, person, { name: 'Bob', age: 30 });
// const updated = { ...person, name: 'Presh' };

// doing a deep copy with nested objects
const updated = {
  ...person,
  address: { ...person.address, city: "New York" },
  name: "Bob",
};
console.log(updated);

Updating arrays

const numbers = [1, 2, 3];
// const added = [4, ...numbers];

// adding
const index = numbers.indexOf(2);
const added = [...numbers.slice(0, index), 4, ...numbers.slice(index)];
console.log(added);

// removing number 2
const removed = numbers.filter((n) => n !== 2);
console.log(removed);

// updating
const update = numbers.map((n) => (n === 2 ? 20 : n));
console.log(update);

Immutable JS

// using immutable;

import { Map } from "immutable";

let book = Map({ title: "Harry Porter" });

function publish(book) {
  return book.set("isPublished", true);
}

book = publish(book);

console.log(book.toJS());

Immer JS

// using immer;
import { produce } from "immer";

let book = { title: "Harry Porter" };

function publish(book) {
  return produce(book, (draftBook) => {
    draftBook.isPublished = true;
  });
}

let updatedBook = publish(book);
console.log(updatedBook);

Redux

A reducer is a function that takes the current instance of the store and returns the updated store. How does the reducer know what properties in the store it should update? We need another building block action.

function reducer(store, action) {
    const updated = {...store};
    updated.products = ???
}

Steps to build an app with Redux

  • Design the store: What do you want to keep in the store

  • Define the actions: What are the actions the users can perform in this application

  • Create one or more reducer: These reducers take an action and return the updated state

  • Set up the store based on the reducer

type is the only property that redux expects in your action object

Preferred naming convention: "bugAdded", past tense, an action represents a verb, and one that has just happened.