Functional Programming in JavaScript

Immutability

Youtube Immutable.js

Since JavaScript isn't a purely functional programming language, it does not natively have immutable data structures in it. However, there are available libraries that provide immutable data structures, the most popular of which is Immutable.js.

Immutable.js will not allow you to take, for example, an array, and append to it. That would involve mutating it. It instead returns a copy of the array with the new value appended to it. The old version is preserved.

Copying an array over and over again should make working with an immutable array slow, and it is a bit slower, but only by a constant amount. Immutable.js maintains performance that is close to native mutable data structures by employing Directed Acyclic Graphs. Instead of completely copying an array or an map, Immutable.js will create a copies of nodes with mutations in a graph. The new node can still link to the old node's children, allowing for shared resources, and only the parents of the node need to be duplicated. This has the added benefit of being able to see old versions of the data structure.

They type of DAG Immutable.js uses are Tries, specifically, Index Tries and Hash Tries. Lookup times will be logarithmic instead of linear, which is slower than the constant time you normally get, but trivially so. Plus, there are performance optimizations that Immutable.js does to occasionally make it's implementation of Map faster than the native one.

A major benefit to immutable data structures is that is enables parallel programming. However, JS is a single threaded language, so it cannot take advantage of this property, but JS could change in the future to do so.

Still, using immutable data structures and Immutable.js has major benefits. Since items in Lists and Maps only change when their value changes or their children change, other objects that are watching (observers) will be able to figure out when a change occurs with a simple equality check in constant time. This is especially useful in the case of React which has do run a diff on state changes to figure out what to update on the DOM. It's also useful for memoization.

Most importantly, immutable data structures simplify your code. They make it easier to reason about what values the state of your app hold at any one point.

Basic Functional Methods

A good place to start leaning the basics of functional programming is with the map, filter, and reduce array methods. They're higher order functions--they accept a function as an argument.

Array.prototype.map = function(fn) {
    var result = [];

    for (var val of this) {
        //call the function passed in as an argument on each new value of the array and push it to a new array.
        result.push(fn(val))
    }

    return result;
}

Array.prototype.filter = function(fn) {
    var result = [];

    for (var val of this) {
        // call the passed function on each value and only push value if its truthy.
        if (fn(val)) {
            result.push(val)
        }
    }

    return result;
}

Array.prototype.reduce = function(fn, accumulator) {
    for (var val of this) {
        accumulator = fn(accumulator, val)
    }

    return accumulator;
}

Point Free Style (Tacit programming)

Point free style programming is a paradigm in which you do not specify the arguments of functions. You compose functions together instead. Consider:

function add2(val) {
    return val + 2;
}

[1,2,3,4,5].map(function(val) {
    return add2(val);
});

The mapping function is passing each value in the array to the add2 function. In this case, the callback function is unnecessary. Add2 can be passed directly to map and the output will be the same:

[1,2,3,4,5].map(add2)

Do keep in mind that the native map function is JS will also pass the index value to the function, and you should be aware of this when using functions that have default values.

function add2OrOther(val1, val2 = 2) {
    return val1 + val2;
}

[1,2,3,4,5].map(add2OrOther) //[1,3,5,7,9]

In cases where you want to ensure the function accepts a specific number of arguments, you should wrap it in another function:

function unary(fn) {
    return function(arg) {
        return fn(arg);
    }
}

[1,2,3,4,5].map(unary(add2OrOther)) //[3,4,5,6,7]

Partial Application

This higher order function will return a new function instead of taking them as arguments. The variable you save it to can then be called with another value. The first value is save on account of closure, even though it's not longer in scope.

function addf(x) {
    return function(y) {
        return x + y;
    }
}

var add3 = addf(3);
// at this point, add3 will look like this:
// function add3(y) {
//     return 3 + y
//  }

add3(7) // 10

Currying

Currying is similar to partial application, but it is used to take a regular function that you wouldn't use the way you do above, and make it work like that.

// a regular function
function add(x, y) {
    return x + y;
}

add(3, 7) // 10

function curry(fn) {
    return (function resolve(...prevArgs) {
        return function(...nextArgs) {
            var currArgs = [...prevArgs, ...nextArgs];

            if (fn.length - currArgs.length <= 0) {
                return fn(...currArgs);
            } else {
                return resolve(...prevArgs);
            }
        }
    }());
}

var addf = curry(add);
var add3 = addf(3);
add3(7) // 10

Compose

The following function is taken directly from from Redux:

function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

Composed function allow you to simply to pass the results of the rightmost function down to each successive function to it's left. This will give you a simple syntax for using multiple functions to create a complex function. Always remember that compose functions are called from right to left. Also be aware that the rightmost function can accept as many arguments as it wants, but the rest must only accept one.

var sumDoubleAndAddThree = compose(add3 , double, add);
// This is the same as:
// function sumDoubleAndAddThree(x, y) {
//   return add3(double(add(x, y)));
// }

sumDoubleAndAddThree(3, 5) // 19

results matching ""

    No results matching ""