25 Steps to Functional Programming in JS

A Comprehensive Journey

Matthias Reis
13 min readJan 1, 2021

The functional programming paradigm is very popular these days. It supports scalability and extensibility very well. And when it comes to collaborated software development, its traits of being pure and side effect free help a lot to create robust applications with a heterogenous dev community. In Javascript that is especially true due to its large user base. And so ES201x is a real step towards JS being a full-featured functional language.

But how does a typical Javascript developer get there? There‘s a path with 25 steps you can follow …

(1) Imperative Programming

As an analogy concerning programming paradigms, you may see imperative programming as a person who walks into an office. The programming task is to be the navigation system that guides this person through this building.

You may have t-junctions where you have to decide whether to go left or right (if clauses). And you may have the need to repeat some steps like going up some stairs (for loops).

So imperative programming tells the program where to go. And of course,
javascript is capable of writing programs imperatively. This is daily business.

Functions are also a part of the imperative programming paradigm, where they are typically called procedures. But they don’t use the full power that functions have in javascript.

In this case, the add was refactored (1) and now uses a procedure (2).

(2) Object-Oriented Programming

Back to our analogy, object orientation adds commonly shared structures to the mindset of the person in the office which is one possibility of scaling. Just like a postman that has to deliver hundreds of packages and visits every department several times, object-oriented programming serializes its programming tasks by defining new types with rich characteristics. Member variables or properties keep data in a defined way and methods work on this dataset.

Javascript has a prototype-based concept for object orientation (which is not covered by this article). But it incorporates typical building blocks like the inheritance of properties and methods and abstraction.

This is the same example as above. Here we create a User class (//1) to solve the problems together with some methods (//2). Now you can instantiate this class with the new operator (//3) and call its methods (//4) to solve the problem.

In the case of ES2016 environments, we even get some syntactic sugar on top of this with a class notation similar to java.

(3) Declarative Programming

With the declarative programming paradigms (where functional programming is a part of), you rather describe the office building instead of the person and her tasks to walk through the building. Thus the result is a lot more static and stable and solves issues for many persons instead of one special case.

In other words, functional parts are highly reusable and robust due to
another approach to solving the problems.

(4) Functions

Now let’s go to the core of all functional behaviour in javascript. Functions.

You declare a function in the following way (something every javascript newbie should know):

function name(parameter) {
return returnValue;
}

Functions can be anonymous. In this case, simply omit the name. Alternatively in ES2015 contexts, you can use the fat arrow function literal for anonymous functions:

(5) First Class and Higher Order

But what could you ever do with an anonymous function? This is dead code,
isn’t it?

Not really. Functions are first-class types (or citizens), which means

  • they can be assigned to variables
  • they can be passed over as parameters (often referred to as callbacks)
  • they can be returned by other functions (often referred to as factories)

As soon as a function follows one of the two latter characteristics, it is
called a higher-order function.

And now you can easily work with functions, even if they are anonymous.

Higher-order functions can be used to compose functionality and variants
into existing functions.

The function negate (//1) takes another function and returns the boolean opposite. Applied to a typical function in unit testing like isEqual (//2) you now get a new function isNotEqual (//3).

(6) Builtin Higher-Order Functions

Especially for arrays, there are already powerful existing methods provided by the ECMA Script specifications and available in almost every JS environment, that already fulfil a lot of characteristics of functional programming.

  • Array.prototype.map(fn: Function): Applies the function to all values
    in an Array and returns a new array with the applied values.
  • Array.prototype.filter(fn: Function): If the callback function returns
    true, the value is transferred to the returning array. Otherwise, it’s
    removed.
  • Array.prototype.reduce(fn: Function[, initial: Any]): Walks through all
    values of an array and accumulates them based on the callback function
    starting with an initial value.

All of these functions return a new result and leave the original one untouched. This characteristic is called immutability (more on that later).

So with this, you already have some functional features to play with and I’m sure as a Javascript developer, you already have used these extensively. Especially with ES6, you come to a concise and easy to read code style and can express a lot with only a single line statement.

(7) Closures and Scope

While variables defined by let and const (ES2015) are block-scoped, the classical var only knows the scope of functions or the global scope.

All functions on the other hand have access to their own variables and to those defined in the enclosing scope, even if the enclosing scope has already been executed (e.g. in async context). This phenomenon is called a closure.

var  prefix = 'the answer is';function log(n) {
console.log(prefix + ' ' + n);
}
log(42); // > the answer is 42

Variables in their own scope have precedence over the enclosing scope, even
if they are not yet defined, which is called hoisting.

var  prefix = 'the question is';function log(n) {
console.log(prefix + ' ' + n); // prefix is undefined
var prefix = 'the answer is';
console.log(prefix + ' ' + n);
}
log(42);

(8) Chaining and Type Compatibility

There are several ways of stitching functions together to one single statement
or to a more specialized function.

The easiest way of doing so is to build a chain. The basic rule of doing so is
that the return value of a function is an object, that supports the method to chain. Again, many built-in types in javascript are already designed like this.

const users = [
{name: 'Dan Abramov', id: 3},
{name: 'Addy Osmani', id: 4},
{name: 'Jake Archiba', id: 11},
{name: 'John Resig', id: 42},
{name: 'Douglas Crockford', id: 52},
{name: 'Brendan Eich', id: 71}
];
const even = users.map(user => user.id).filter(id => id % 2 === 0);// even = [4, 42, 52]

(9) Tuples

In order to hand over more complex data of different types in a chain, other
functional languages have the ability to define Tuples, which are finite,
immutable lists of arbitrary types like (‘John Resig’, ‘jquery’, 123).

Such a structure does not exist in javascript. Instead, you could go for plain objects:

return {
success: false,
message: 'Server not answering'
}

But you’ll miss the immutability (more on that later) and the safety. To solve
this you could easily create a poly-filled version of Tuple used like in the
following example (I leave out the implementation for now):

const Status = Tuple(Boolean, String);
return Status(false, 'Server not answering');

This data structure is of fixed type and provides a strict contract, which is
easier to handle by composed or chained functions. It can hold data for error
handling which again is a concept we discuss later on.

A small annotation here: in case, you choose to code in typescript, there are much more and better possibilities to support such cases.

(10) Currying and Arity

The amount of arguments a function takes is called Arity. Functions that
only take one single argument are for example called unary. In functional
programming, this number is directly proportional to the cyclomatic complexity of a function. So divide and conquer, the principle of designing a function to do only one thing, is very important. Everything else makes composing and chaining impossible.

But still, there are valid use cases where functions need more data from
different data sources or parts of your program.

The concept of Currying is especially useful for these cases. Currying
turns a function into a higher-order function in order to provide parts
of the arguments at different times.

An example is best for that:

// base function
let fetch = (baseUrl, resource, authentication) => {...};
// curried function (with help from the lodash library)
fetch = _.curry(fetch);
// pre configuration provided by config section
const fetchFromApi = fetch('/api');
// authenticated fetch provided by auth section
const oAuthToken = ...;
const authenticatedFetch = fetchFromApi(__, oAuthToken);
// finally the request with a simple unary function
authenticatedFetch(`/user/${userId}`).then(...);

(11) Recursion

If a function calls itself to solve a problem, this is called recursion.
This splits a bigger problem into easier, similar sub-tasks like walking
through a tree or processing mathematical sequences (like factorial or
the Fibonacci sequence).

const countdown = n => {
console.log(n);
if(n > 0) {
// the function calls itself
countdown(n - 1);
}
};
countdown(5); // 5 4 3 2 1 0

(12) Performance and Tail Call Optimization

The concept of closures makes it necessary for the compiler to keep track of
the complete stack of function calls (the so-called stack trace) and remembering every variable in it until it can be garbage collected. This is usually no problem. But in special cases like recursion or currying or the combination of both this call stack size can grow quickly and become the performance bottleneck of an application.

The countdown function for example exits with Maximum call stack size
exceeded after about 17.800 iterations on my computer. That means countdown(18000); will throw an error.

In real ES2015 environments(and by design in other functional programming languages), you can apply Tail Call Optimization to the recursive function to avoid this.

When the call to the recursion is the absolutely last statement to compute inside your function, the interpreter doesn’t need to keep the stack and optimizes it away. In the case of countdown(), this is already the case, so the result will look way better.

(13) Memoization

Some algorithms take very long to compute, so it can be helpful to provide a
result cache for its results. This is especially useful for recursive cases.

Example:

//a recursive factorial implementation
const factorial = n => (n === 1) ? 1 : (n * factorial(n - 1));
const memoizedFactorial = factorial.memoize();
//calls itself recursively 100 times
const f100 = factorial(100);
const fm100 = memoizedFactorial(100);
//calls itself recursively 101 times
const f101 = factorial(101);
//calls itself once and takes the cached result of f(100)
const fm101 = memoizedFactorial(101);

Implementation (Example):

Function.prototype.memoized = function() {
let key = JSON.stringify(arguments);
this._cache = this._cache || {};
this._cache[key] = this._cache[key] || this.apply(this, arguments);
return this._cache[key];
};
Function.prototype.memoize = function() {
let fn = this;
if (fn.length === 0 || fn.length > 1) {
return fn;
}
return function() {
return fn.memoized.apply(fn, arguments);
};
};

(14) Pure Functions, Referential Transparency

In order to create robust functional programs, you need to be aware of your
data structure and all the possible side effects and concentrate those side
effects into some, not many clearly defined areas.

Possible side effects are:

  • server communication and other IO
  • user input and browser events
  • altering and accessing the dom (which is a special IO operation)
  • mutating data in several different regions of your program
  • language and browser limits (stack overflows, unsupported language features)

The rest of your program should consist of so-called Pure Functions, which
calculate something based on their given arguments and leave the
objects provided as arguments untouched.

Referential Transparent Functions are even better. In addition, they always
provide the identical result based on the given input.

Examples:

// impure approach
let i = 0;
const getIncrement = () => return i++;
const one = getIncrement(); // 1
const two = getIncrement(); // 2
// pure approach
let i = 0;
const getIncrement = number => return number++;
const one = getIncrement(i); // 1
const two = getIncrement(i); // 2
// example of a pure function that is not referential transparent
const getTime = () => Date.now();

(15) Immutable Data and Freezing

Pure functions have one important feature already mentioned. They must not alter (or mutate) the data of their arguments or any other variable in scope. While basic data types like Strings, Booleans or Numbers are immutable on a language level, Objects aren’t. So please beware.

//impure
const appendFullName = user => {
user.fullName = `${user.firstName} ${user.firstName}`;
return user;
}
//pure
const appendFullName = user =>
({...user, fullName: `${user.firstName} ${user.firstName}`});

In order to explicitly prevent someone from altering your data, ES5 has introduced Object.freeze() to make objects immutable.

const arr = Object.freeze([2, 4, 6]);
arr.push(8); //Uncaught TypeError: Can't add property 3, object is not extensible

But here lies another caveat, because the freezing doesn’t happen deeply.

const user = Object.freeze({
name: 'John Resig',
tags: ['javascript', 'jquery']
});
user.url = 'http://ejohn.org/'; // DOESN'T WORK
user.tags.push('sizzle'); // WORKS

16 Lenses and Selectors

In object-oriented programming, you can provide methods to access and mutate the internal data (like user.getLastName() or user.setLastName('Abramov')).

In functional programming, you still handle object (or even class) structures, but they usually don’t contain accessors and mutators. Instead, you can write pure functions to extract or mutate this data (called Lenses; for the getter part, you’ll also sometimes hear the term Selector, for example in the Redux framework).

const getUserTags = user => user.tags;const addTagToUser = (tag, user) =>
Object.assign({}, user, {tags: [...user.tags, tag]});
const user = new User(
'Dan Abramov', ['javascript', 'redux']);
const newUser = addTagToUser('react', user);
const tags = getUserTags(user); // ['javascript', 'redux']
const newTags = getUserTags(newUser); // ['javascript', 'redux', 'react']

(17) Decomposition

What we’ve learned so far:

  • functions are small units of a program that do exactly one thing
  • so they are highly reusable
  • and very predictable
  • and therefore easily testable
  • by chaining or composing, these small functions define the control flow
  • side effects should be well known and encapsulated into evil impure
    functions

So to work functionally and to compose, you first must decompose your problem into small, atomic parts.

(18) Pipelines and Compose

Composition is one of the core concepts of functional programming. Instead of evaluating one statement after the other, you describe your complex application behaviour by putting simpler functions together — just like a UNIX shell script.

The result is again a function that itself handles the complete flow. This also means that absolutely nothing is happening until you call this final function.

// use the lodash implementation
const compose = _.flowRight;
const pipe = _.flow;
const getWords = str => str.split(/\s+/);
const count = arr => arr.length;
//These are identical
const wordCountSimple = str => count(getWords(str));
const wordCountCompose = compose(count, getWords);
const wordCountPipe = pipe(getWords, count);
//Yoda quote
wordCountCompose('Train yourself to let go of everything you fear to lose');
//Result: 11

(19) Functional Combinators

There are some useful functions that add logical flow to your function. They
are derived from mathematical combinatory logic.

I combinator (identity)

const identity = a => a;

Useful for flow logic, unit testing and data extraction (see later);

K combinator (tap)

const tap = fn => a => {fn(a); return a;};
const log = tap(console.log.bind(console));
log('test'); //logs test and returns test

Useful for analyzing results or piping to other subprocesses

OR combinator (alternate)

const alternate = (fn, altFn) => a => fn(a) || fnAlt(a);
const user = alternate(getUSerFromCache, getUserFromServer);

If the first function evaluates to false, the second function is called.

S combinator (sequence)

const sequence = () => {
const funcs = [...arguments];
return a => funcs.forEach(func => func(a));
};

This combinator executes several functions in sequence but doesn’t return
anything.

fork and join

const fork = (rightFn, leftFn, joinFn) => a => joinFn(rightFn(a), leftFn(a));const sum = arr => arr.reduce((sum, n) => sum + n, 0);
const len = arr => arr.length;
const average = fork(sum, len, (sum, len) => sum / len);average([2,4,6]); // returns 4

(20) Wrappers and Functors

The final chapters now go for the cream of the crop of functional programming.

In functional programs errors are suppressed, because they would kill the control flow of your complex function. So exceptions and null values must be handled differently.

First approach: Wrap potentially erroneous parts into a wrapper.

class Wrapper {
constructor(value) {
this._value = value;
}
map(fn) {
return fn(this._value);
};
}
const wrap = val => new Wrapper(val);//wrap some value into the container
const wrapped = wrap(5);
//retrieve the value from the container with the I combinator
const val = wrapped.map(identity);

The advantage is that an error or a null value is not directly handed over to the next function in the chain. It’s up to the implementation of the applied function to provide the appropriate handling.

To apply functions and still keep the dangerous code in a container, you simply re-wrap the result of map.

Wrapper.prototype.fmap = wrap(Wrapper.prototype.map);const add2 = a => a + 2;wrap(5).fmap(add2); //returns a Wrapper with 7 inside

The container together with the fmap() function that unpacks a
secured value, applies a function and repacks it again is called a Functor.

You all know Array.map(). It’s exactly the same — a Functor on the type
Array.

(21) Monads: Handling Edge Cases with Maybe, Either

Monads or Monadic types are special functors, where the fmap() is replaced by a richer and more intelligent implementation with the ability to extend the functionality of the composing step and handle edge cases properly.

Monads therefore are used to describe a desired side effect.

The Wrapper was a bit too silly to handle Errors correctly. A more appropriate approach is a Monad called Maybe, which applies a function on map if a value is available or passes through null or undefined.
Here’s a very simplified implementation.

class Maybe {
static of(value) {
return (this.value === null || this.value === undefined) ?
new Nothing() :
new Just(this.value);
}
constructor(value) {
this._value = value;
}
}
class Just extends Maybe {
getOrElse() {
return this._value;
}
map(fn) {
return Maybe.of(fn(this._value));
}
}
class Nothing extends Maybe {
getOrElse(elseVal) {
return elseVal;
}
map(fn) {
return this;
}
}
const add2 = a => a + 2;Maybe.of(5).map(add2).getOrElse(0); // 7
Maybe.of(undefined).map(add2).getOrElse(0); // 0

Another case is to handle Errors and unwanted cases. This is the world of the
Either() monad with a Left() and a Right() side that can be handled
separately. Implementation is up to you.

(22) Outlook 1: Promises - Functional and Asynchronous

The final three steps are about quick looks into further topics, that belong directly or indirectly to functional programming, but can’t be handled deeply enough in this article. So to be complete … here are three hints for further investigations.

Javascript provides a special functional approach called Promise to handle
asynchronous actions. This is in fact a monad where then() is the Functor
that handles the transformation.

(23) Outlook 2: Reactive Programming

Another one is the handling of multiple events in a descriptive manner. So the control flow is executed more than once.

The library RX.js handles this very well and is a very useful extension in a functional toolbelt.

(24) Outlook 3: Very Useful Libraries

Lodash
Most widely used modular functional library — Recommendation: use its variant lodash/fp.

Ramda.js
Library similar to Lodash focused on composability

functional.js
Lightweight variant

Elm
A plain functional language compiled to js

--

--