25 Steps to Functional Programming in JS

A Comprehensive Journey

(1) Imperative Programming

(2) Object-Oriented Programming

(3) Declarative Programming

(4) Functions

function name(parameter) {
return returnValue;
}

(5) First Class and Higher Order

  • 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)

(6) Builtin Higher-Order Functions

  • 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.

(7) Closures and Scope

var  prefix = 'the answer is';function log(n) {
console.log(prefix + ' ' + n);
}
log(42); // > the answer is 42
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

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

return {
success: false,
message: 'Server not answering'
}
const Status = Tuple(Boolean, String);
return Status(false, 'Server not answering');

(10) Currying and Arity

// 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

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

(13) Memoization

//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);
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

  • 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)
// 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

//impure
const appendFullName = user => {
user.fullName = `${user.firstName} ${user.firstName}`;
return user;
}
//pure
const appendFullName = user =>
({...user, fullName: `${user.firstName} ${user.firstName}`});
const arr = Object.freeze([2, 4, 6]);
arr.push(8); //Uncaught TypeError: Can't add property 3, object is not extensible
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

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

  • 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

(18) Pipelines and Compose

// 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

const identity = a => a;
const tap = fn => a => {fn(a); return a;};
const log = tap(console.log.bind(console));
log('test'); //logs test and returns test
const alternate = (fn, altFn) => a => fn(a) || fnAlt(a);
const user = alternate(getUSerFromCache, getUserFromServer);
const sequence = () => {
const funcs = [...arguments];
return a => funcs.forEach(func => func(a));
};
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

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);
Wrapper.prototype.fmap = wrap(Wrapper.prototype.map);const add2 = a => a + 2;wrap(5).fmap(add2); //returns a Wrapper with 7 inside

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

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

(22) Outlook 1: Promises - Functional and Asynchronous

(23) Outlook 2: Reactive Programming

(24) Outlook 3: Very Useful Libraries

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store