JavaScript ~ Functional programming - rohit120582sharma/Documentation GitHub Wiki

Functional programming is a programming paradigm to structure your code, meaning that it is a way of thinking about software construction based on some fundamental, defining principles.

Functional programming favours:

  • Pure functions instead of shared state & side effects
  • Immutability over mutable data
  • Function composition over imperative flow control
  • Declarative rather than imperative


Techniques

First Class Object

Every function is an object in JavaScript. It is a combination of code-body and object. It is a special object which has some default properties like name, and bind(), call(), apply() methods etc.

Everything you can do with other types, you can do with functions:

  • Create them on the fly
  • Assign them to variables and properties of other objects
  • Pass them as arguments into functions
  • Return them as values from functions

Higher order functions

Higher-order functions keep our code dry by generalising the operations

A Higher-Order function is a function that operate on other functions, either by receiving a function as an argument or by returning the function as output

The function we pass in is a Callback function. In other words, a function you give to another function, to be run when the other function is finished is called Callback function.

For example, Array.prototype.map, Array.prototype.filter and Array.prototype.reduce are some of the built-in Higher-Order functions

/**
 * Instead of writing same type of operations in `add3` and `multiplyBy2` functions separatly
 * Generalize the operations into `copyArrayAndManipulate` function and pass in the specific functionality as a function argument
 * Resemble the `Array.prototype.map` functionality
 */
const add3 = input => input + 3;
const multiplyBy2 = input => input * 2;
const copyArrayAndManipulate = (array, instructions) => {
    const output = [];
    for (let i = 0; i < array.length; i++) {
        output.push(instructions(array[i]));
    }
    return output;
}

const addResult = copyArrayAndManipulate([1, 2, 3], add3);
const multiplyResult = copyArrayAndManipulate([1, 2, 3], multiplyBy2);

Reducer

The reduce() method accepts two parameters:

  • Reducer function: It is a function combining logic/code/functionality which is run on each member of the array resulting in a single output value
  • Value: It is an initial-value also called accumulator (array, string, number)

Every time the reducer function is called on each value in the array, the accumulator keeps the result of previous operation returned from the reducer function, and the currentValue is set to the current value of the array.

/**
 * Flatten the array
 */
var flatten = (arr) => {
    return arr.reduce((accumulator, item) => {
        if (Array.isArray(item)) {
            return accumulator.concat(...flatten(item));
        }
        accumulator.push(item);
        return accumulator;
    }, []);
}
console.log(flatten([1,2, [3, [4,5,[6]]]]));

Function composition

Function composition is the process of combining two or more functions to produce a new function where each function receives input and hands over its output to the next function.

Chaining with dots relies on JavaScript prototype feature. What if I want to chain functions that just return a regular output e.g. multiplyBy2, add3, divideBy5 etc. So we’re combining a function with a value to get a result then combining that result with another function to get another result and so on.

For example, pipe, compose etc. The pipe goes left-to-right, where compose goes right-to-left.

const multiplyBy2 = x => x*2;
const add3 = x => x+3;
const divideBy5 = x => x/5;

/**
 * Pipe
 */
const pipe = (array, howToCombine, buildingUp) => {
    for(let i=0; i<array.length; i++) {
        buildingUp = howToCombine(buildingUp, array[i]);
    }
    return buildingUp;
}
const reducer = (input, fn) => fn(input);
const pipeOutput = pipe([multiplyBy2, add3, divideBy5], reducer, 11);

/**
 * Compose
 */
const compose = (...fns) => (input) => {
    return fns.reduceRight((accumulator, fn) => fn(accumulator), input);
}
const composeOutput = compose(divideBy5, add3, multiplyBy2)(11);

Closure

Set up persistent permanent memories attached to the functions

When a function is defined, it gets a bond to the surrounding Local Memory (lexical environment) of live data in which it has been defined also called backpack. In other words, A closure is the combination of a function and the scope object (the lexical environment) in which it was created.

This backpack of live data that is attached to a function is known as the closure

The backpack (or closure) of live data is attached to a function through a hidden property known as [[scope]] which persists when the inner function is returned out

So when a function is run, it will first look in its own local memory for any data it needs, but then in its backpack before it looks in global memory

Function decoration and Partial Application are best examples of Closure


Cons

  • Whenever JavaScript executes a function, a scope object is created to hold the local variables created within that function. It is initialised with any variables passed in as function parameters. Normally JavaScript's garbage collector would clean up the scope object created for outer-function, but if the returned inner-function maintains a reference back to that the object. Then, the scope object will not be garbage-collected until there are no more references to the inner-function that outer-function returned.

Pros

  • Closures have access to the outer function’s variable even after the outer function returns.
  • Closures are the primary mechanism used to enable data privacy. When closures are used for data privacy, the enclosed variables are only in scope within the containing (outer) function.
const outer = () => {
    let counter = 0;
    const incrementCounter = () => {
        counter ++;
    }
    return incrementCounter
}
const newFunction = outer();
newFunction()
newFunction()

Function decoration

A decorator is a function that takes another function and extends the behavior of the later function without explicitly modifying it.

/**
 * Memoization
 */
var worker = {
    name: 'Rohit',
    getName(greeting) {
        console.log('context =', this);
        return `${greeting} ${this.name}`;
    }
};
function cachingDecorator(fn, context) {
    let cache = new Map();

    return function(x) {
        if (cache.has(x)) {
            return cache.get(x);
        }
        let result = fn.call(context, x);
        cache.set(x, result);
        return result;
    }
}
var newGetNameFunc = cachingDecorator(worker.getName, worker);
console.log(newGetNameFunc('Hi'));
console.log(newGetNameFunc('Hi'));

/**
 * Debouncing
 */
function search() {
    // Use fetch to make an API call
}
function debounce(fn, delay) {
    let timer;
    return function() {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, arguments);
        }, delay);
    }
}
let betterSearchWithDebounce = debounce(search, 300);

/**
 * Trottling
 */
function search() {
    // Use fetch to make an API call
}
function trottling(fn, limit) {
    let flag = true;
    return function() {
        if (flag) {
            flag = false;
            fn.apply(this, arguments);
            setTimeout(() => {
                flag = true;
            }, limit);
        }
    }
}
let betterSearchWithTrottling = trottling(search, 300);

Partial application and currying

Both are specialisation techniques

Partial application presets some arguments now, receives the rest on the next call

Currying doesn’t presets any arguments, receives each argument one at a time

Currying is a special form of partial application. Currying is a process in which we can transform a function with multiple arguments into a sequence of nesting functions each taking a single argument of function and eventually resolve to a value.

In other words, when a function, instead of taking all arguments at one time, takes the first one and return a new function that takes the second one and returns a new function which takes the third one, and so forth, until all arguments have been fulfilled.

The arguments are kept "alive"(via closure) and all are used in execution when the final function in the currying chain is returned and executed.

Currying doesn’t call a function. It just transforms it.

Why it’s useful ?

  • Currying helps you to avoid passing the same variable again and again.
  • It helps to create a higher order function. It extremely helpful in event handling.
  • Little pieces can be configured and reused with ease.
/**
 * Partial application
 */
const multiply = (a, b) => a * b;
function prefillFunction(instruction, prefilledValue) {
    return (liveInput) => {
        return instruction(liveInput, prefilledValue);
    }
}
const multiplyBy2 = prefillFunction(multiply, 2);
const result = multiplyBy2(5);
console.log(result);


/**
 * Currying
 */
function add(values) {
    return values.reduce((acc, item) => (acc + item), 0);
}
function curryFun(input, arrArgs=[]) {
    arrArgs.push(input);
    function recursion(nextInput) {
        if(!nextInput) {
            return add(...arrArgs);
        }
        arrArgs.push(nextInput);
        return recursion;
    }
    return recursion;
}
var output = curryFun(3)(4)(5)();
console.log(output);

Recursion

Refer currying example

Proper Tail Call (PTC) or Tail Call Optimization (TCO)

/**
 * Reverse string
 */


/**
 * Add number by chain call
 */
function curryGenerator(level, instruction) {
    var counter = 0;
    var arrArgs = [];
    function recursion(input) {
        counter++;
        arrArgs.push(input);
        if(counter === level) {
            return instruction(...arrArgs);
        }
        return recursion;
    }
    return recursion;
}
var curry = curryGenerator(3, add);
var output = curry(3)(4)(5);
console.log(output);

⚠️ **GitHub.com Fallback** ⚠️