Functional programming is a style that stresses the mutation, combination and use of functions. Even though Javascript
Functional programming is coming back. Originally relegated to the annals of computer science academia, functional programming has had a recent renaissance. In this article we show how can be used in the real world.
Functional programming is a style that stresses the mutation, combination and use of functions. Even though Javascript is not a pure functional language, it treats functions as objects (first-class functions).
This means that, in Javascript, functions can be passed as arguments to other functions, assigned to variables and created dynamically inside a function.
Higher-order functions
These are functions that accept other functions as arguments and/or return new functions. Below we will show a very simple example: a function accepts a number as argument and returns a new function that always returns that same number. Then we will use it to create two functions: return5 and return42, which always return 5 and 42, respectively.
// Returns a function that always returns x function returnX(x) { return function() { return x; } } // return5 is a function that always returns 5 var return5 = returnX(5); // return42 is a function that always returns 42 var return42 = returnX(42); console.log(return5()); // prints 5 console.log(return42()); // prints 42
This works because the functions “remember” the context in which they were created, so when the new function is created they remember the value of x and they can return it correctly.
The following example shows a function that accepts an argument and returns a new function.
This new function also receives an argument and returns the sum of them:
function plus(x) { return function(y) { return y+x; } } var plus10 = plus(10); console.log(plus10(5)); // Prints: 15
In this case, the function we return accepts a new argument; this argument will be added to the variable x that has been saved in the context of the function.
The following example shows a function which, apart from returning a function, receives a function as argument.
function consoleLogFunction(prefix, fn) { return function(x) { var result = fn(x); console.log(prefix, result); return result; } } var consoleLogSqrt = consoleLogFunction('sqrt =', Math.sqrt); consoleLogSqrt(144); // Prints: sqrt = 12 consoleLogSqrt(256); // Prints: sqrt = 16 function square(x) { return x*x; } var consoleLogSquare = consoleLogFunction2('square =', square); consoleLogSquare(12); // Prints: sqrt = 144 consoleLogSquare(16); // Prints: sqrt = 256
In the previous example, the function consoleLogFunction receives two arguments: prefix and fn. Prefix is a string which will be shown throughconsole.log, together with the result obtained from executing fn. The problem posed by this implementation is that consoleLogFunction returns a function which accepts only one argument; therefore, if we want to do the same using a function that receives two arguments, we will have to create a new function:
function consoleLogFunction2Arguments(prefix, fn) { return function(x, y) { var result = fn(x, y); console.log(prefix, result); return result; } } var consoleLogPow = consoleLogFunction2Arguments('Pow = ', Math.pow); consoleLogPow(2, 8); // Prints: Pow = 256
This is not the best solution, since we will have to create a consoleLogFunction for each argument we need. In order to generalize our function, first we have to see the following.
Call, apply and arguments
Javascript functions are also objects, and they have some associated methods, such as call and apply. Both methods are used to execute functions, and both of them receive as first argument the object that will be used as this inside the function. The difference between them is that call receives the arguments as a list, and apply receives them as an array.
In the following example we can see those differences:
Math.sqrt.call(null, 144); // 12 Math.sqrt.apply(null, [144]); // 12 Math.max.call(null, 1, 3, 2); // 3 Math.max.apply(null, [1, 3, 2]); // 3 function printThis() { console.log(this); } printThis.call({a: 1}); // Prints: { a: 1 } printThis.apply({a: 1}); // Prints: { a: 1 }
Additionally, all the functions have access to a local variable called arguments which makes reference to an object with all the arguments passed to the function. First of all, let us see the structure of the object arguments:
function printArgumentsObject() { console.log(arguments); console.log('arguments.length: ' + arguments.length); } // Prints: // { '0': 3, '1': 2, '2': 1, '3': 'test' } // arguments.length: 4 printArgumentsObject(3, 2, 1, 'test');
As we can see, the structure of the object arguments resembles an array, we can access arguments individually as arguments[n], and we can obtain the number of arguments passed using arguments.length.
If we want to turn the object arguments into an array, the recommended way is as follows:
function printArgumentsAsArray() { var args = new Array(arguments.length); for(var i = 0; i < args.length; i ++) { args[i] = arguments[i]; } console.log(args); } // Prints: // [ 3, 2, 1, 'test' ] printArgumentsAsArray(3, 2, 1, 'test');
Now we are ready to create the function consoleLogFunction in a generic way:
Application examples
Spy functions
Spy functions modify other functions to save information about their execution, such as the number of times it was executed and a history of the arguments it received.
In the following example, we create a Spy function that receives an object and a method inside the object. Then it modifies and wraps this method inside a context, where we save the number of times it was executed (ret.count) and the history of the arguments it received (ret.argHistory).
function Spy(target, method) { var ret = { count: 0, argHistory: [] }; var oldMethod = target[method]; target[method] = function() { ret.count ++; ret.argHistory.push(arguments); return oldMethod.apply(this, arguments); } return ret } module.exports = Spy; var spy = Spy(console, 'error'); console.error('1'); console.error('3'); console.error('2'); // Prints: 3 console.log(spy.count); // Prints: [ { '0': '1' }, { '0': '3' }, { '0': '2' } ] console.log(spy.argHistory);
Memoization
Memoization is a technique used to optimize pure functions (the return value only depends on the arguments and there are no side-effects), caching results obtained according to their arguments. Therefore, every time we execute a “memoized” function, first it checks if the result is in the cache and returns it without executing the function; if it is not in the cache, the function is executed and the result is saved in the cache for future calls.
By using higher-order functions, it is possible to write a function that can “memoize” any other functions, where the cache is an object existing within the context of the returned function. Below we will show how to write this function, as well as a use example:
function memoize(func) { var cache = {}; var slice = Array.prototype.slice; return function() { // Get arguments as array as seen before var args = new Array(arguments.length); for(var i = 0; i < args.length; i ++) { args[i] = arguments[i]; } // Check cache if (args in cache) { console.log('Cache hit') return cache[args]; // Return cached value } else { console.log('Cache miss') return (cache[args] = func.apply(this, args)); } } } var memoizedPow = memoize(Math.pow); memoizedPow(2, 8); // Cache miss memoizedPow(2, 4); // Cache miss memoizedPow(2, 8); // Cache hit
Next we will see some higher-order functions provided by Javascript which are very useful.
Map
Map is an array method that receives a function which will be executed once per each element in the array, and it will receive this element as parameter. Then it will return a new array with all the values returned by the function.
In the following example we first use map together with the function Math.sqrt to return an array with the square root of all the elements. Then we use map to return an array with all the elements divided by 2.
var arr = [144, 256, 1024]; // Prints: [12, 16, 32] console.log(arr.map(Math.sqrt)); var half = arr.map(function (element) { return element/2; }) // Prints: [72, 128, 512] console.log(half);
Filter
The filter method creates a new array with all the elements that pass the condition implemented by the function passed as argument.
In the following example, we filter the even numbers out of the array.
var arr = [1, 3, 4, 6, 4, 7]; even = arr.filter(function (element) { return element%2 === 0; }); // Prints: [ 4, 6, 4 ] console.log(even);
Every, Some
The every method checks that all the elements of the array meet a given condition, whereas the some method checks that at least one element meets the condition.
In the following example we use every and some with different arrays, by means of a function that checks if a number is positive.
var allPositives = [1, 2, 3]; var oneNegative = [3, -1, 2]; var allNegatives = [-2, -1]; function positive(x) { return x > 0 } console.log(allPositives.every(positive)); // true console.log(allPositives.some(positive)); // true console.log(oneNegative.every(positive)); // false console.log(oneNegative.some(positive)); // true console.log(allNegatives.every(positive)); // false console.log(allNegatives.some(positive)); // false
Reduce
The reduce method applies the function between an inside accumulator and each element in the array; then, it returns the accumulator and reduces the array to only one value.
In this example we will show you how to use reduce to obtain the sum of all the elements of the arrangement:
var arr = [1, 2, 3]; function sum (a, b) { return a + b; } // Prints: 6 console.log(arr.reduce(sum));
Further information
Official reference for native methods:
The book Eloquent Javascript, available online, has a chapter on higher-order functions: