DEV Community

Muhammad Ahmad
Muhammad Ahmad

Posted on • Updated on

Functional Programming in JS: 0x04 - The Functional Toolkit

If you've looked closely at the few examples presented so far, you'll notice a few methods being used that you may not be familiar with. They are the map(), filter(), and reduce() functions, and they are crucial to every functional program of any language.
They enable you to remove loops and statements, resulting in cleaner code.

The map(), filter(), and reduce() functions make up the core of the functional toolkit, a collection of pure, higher-order functions that are the workhorses of the functional method. In fact, they're the epitome of what a pure function and what a higher-order function should be like; they take a function as input and return an output with zero side effects.
While they're standard for browsers that implement ECMAScript 5.1, they only work on arrays.
Each time it's called, a new array is created and returned. The existing array is not modified. But there's more, they take functions as inputs, often in the form of anonymous functions referred to as callback functions; they iterate over the array and apply the function to each item in the array!

myArray = [1, 2, 3, 4];
newArray = myArray.map(function(x) {
    return x * 2
});
console.log(myArray); // Output: [1,2,3,4]
console.log(newArray); // Output: [2,4,6,8]
Enter fullscreen mode Exit fullscreen mode

One more thing, Because they only work on arrays, they do not work on other iterable data structures, like certain objects. Fret not, libraries such as underscore.js, Lazy.js, stream.js, and many more all implement their own map(), filter(), and reduce() methods that are more versatile.

Callbacks

If you've never worked with callbacks before, you might find the concept a little puzzling.
This is especially true in JavaScript, given the several different ways that JavaScript allows you to declare functions.
A callback() function is used for passing to other functions for them to use. It’s a way to pass logic just as you would pass an object:

var myArray = [1, 2, 3];

function myCallback(x) {
    return x + 1
};
console.log(myArray.map(myCallback));
Enter fullscreen mode Exit fullscreen mode

To make it simpler for easy tasks, anonymous functions can be used:

console.log(myArray.map(function(x){return x+1}));
Enter fullscreen mode Exit fullscreen mode

They are not only used in functional programming, they are used for many things in JavaScript. Purely for example, here’s a callback() function used in an AJAX call made with jQuery:

function myCallback(xhr) {
    console.log(xhr.status);
    return true;
}
$.ajax(myURI).done(myCallback);
Enter fullscreen mode Exit fullscreen mode

Notice that only the name of the function was used. And because we're not calling the callback and are only passing the name of it, it would be wrong to write this:

$.ajax(myURI).fail(myCallback(xhr));
// or
$.ajax(myURI).fail(myCallback());
Enter fullscreen mode Exit fullscreen mode

What would happen if we did call the callback? In that case, the myCallback(xhr) method would try to execute—‘undefined’ would be printed to the console and it would return True. When the ajax() call completes, it will have true as the name of the callback function to use, and that will throw an error.
What this also means is that we cannot specify what arguments are passed to the callback functions. If we need different parameters from what the ajax() call will pass to it, we can wrap the callback function in an anonymous function:

function myCallback(status) {
    console.log(status);
    return true;
}
$.ajax(myURI).done(function(xhr) {
    myCallback(xhr.status)
});
Enter fullscreen mode Exit fullscreen mode

Array.prototype.map()

The map() function is the ringleader of the bunch. It simply applies the callback function on each item in the array.

Note:

Syntax: arr.map(callback [, thisArg]);

Parameters:

callback(): This function produces an element for the new array, receiving these arguments:

  • currentValue: This argument gives the current element being processed in the array
  • index: This argument gives the index of the current element in the array
  • array: This argument gives the array being processed thisArg(): This function is optional. The value is used as this when executing callback. Examples:
var
    integers = [1, -0, 9, -8, 3],
    numbers = [1, 2, 3, 4],
    str = 'hello world how ya doing?';
// map integers to their absolute values
console.log(integers.map(Math.abs));
// multiply an array of numbers by their position in the array
console.log(numbers.map(function(x, i) {
    return x * i
}));
// Capitalize every other word in a string.
console.log(str.split(' ').map(function(s, i) {
    if (i % 2 == 0) {
        return s.toUpperCase();
    } else {
        return s;
    }
}));
Enter fullscreen mode Exit fullscreen mode

Note

While the Array.prototype.map method is a standard method for the Array object in JavaScript, it can be easily extended to your custom objects as well.

MyObject.prototype.map = function(f) {
    return new MyObject(f(this.value));
};
Enter fullscreen mode Exit fullscreen mode

Array.prototype.filter()

The filter() function is used to take elements out of an array. The callback must return True (to include the item in the new array) or False (to drop it).
Something similar could be achieved by using the map() function and returning a null value for items you want dropped, but the filter() function will delete the item from the new array instead of inserting a null value in its place.

Note

Syntax: arr.filter(callback [, thisArg]);

Parameters:

callback(): This function is used to test each element in the array. Return True to keep the element, False otherwise. With these parameters:

  • currentValue: This parameter gives the current element being processed in the array
  • index: This parameter gives the index of the current element in the array

array: This parameter gives the array being processed.

thisArg(): This function is optional. Value is used as this when executing callback.
Examples:

var myarray = [1, 2, 3, 4]
words = 'hello 123 world how 345 ya doing'.split(' ');
re = '[a-zA-Z]';
// remove all negative numbers
console.log([-2, -1, 0, 1, 2].filter(function(x) {
    return x > 0
}));
// remove null values after a map operation
console.log(words.filter(function(s) {
    return s.match(re);
}));
// remove random objects from an array
console.log(myarray.filter(function() {
    return Math.floor(Math.random() * 2)
}));
Enter fullscreen mode Exit fullscreen mode

Array.prototype.reduce()

Sometimes called fold, the reduce() function is used to accumulate all the values of the array into one. The callback needs to return the logic to be performed to combine the objects. In the case of numbers, they're usually added together to get a sum or multiplied together to get a product. In the case of strings, the strings are often appended together.

Note

Syntax: arr.reduce(callback [, initialValue]);

Parameters:

callback(): This function combines two objects into one, which is returned. With these parameters:

  • previousValue: This parameter gives the value previously returned from the last invocation of the callback, or the initialValue, if supplied
  • currentValue: This parameter gives the current element being processed in the array
  • index: This parameter gives the index of the current element in the array
  • array: This parameter gives the array being processed initialValue(): This function is optional. Object to use as the first argument to the first call of the callback. Examples:
var numbers = [1, 2, 3, 4];
// sum up all the values of an array
console.log([1, 2, 3, 4, 5].reduce(function(x, y) {
    return x + y
}, 0));
// sum up all the values of an array
console.log([1, 2, 3, 4, 5].reduce(function(x, y) {
    return x + y
}, 0));
// find the largest number
console.log(numbers.reduce(function(a, b) {
        return Math.max(a, b)
    }) // max takes two arguments
);
Enter fullscreen mode Exit fullscreen mode

Honorable mentions, but not so important!

The map(), filter(), and reduce() functions are not alone in our toolbox of helper functions.
There exist many more functions that can be plugged into nearly any functional application.

Array.prototype.forEach

Essentially the non-pure version of map(), forEach() iterates over an array and applies a callback() function over each item. However, it doesn’t return anything. It’s a cleaner way of performing a for loop.
Syntax: arr.forEach(callback [, thisArg]);

Array.prototype.concat

When working with arrays instead of for and while loops, often you will need to join multiple arrays together. Another built-in JavaScript function, concat(), takes care of this for us.
The concat() function returns a new array and leaves the old arrays untouched. It can join as many arrays as you pass to it.

console.log([1, 2, 3].concat(['a', 'b', 'c']) // concatenate two arrays);
        // Output: [1, 2, 3, 'a','b','c']
Enter fullscreen mode Exit fullscreen mode

The original array is untouched. It returns a new array with both arrays concatenated together.
This also means that the concat() function can be chained together.

Array.prototype.reverse

Another native JavaScript function helps with array transformations. The reverse() function inverts an array, such that the first element is now the last and the last is now the first.
However, it does not return a new array; instead it mutates the array in place. We can do better. Here’s an implementation of a pure method for reversing an array:

var invert = function(arr) {
    return arr.map(function(x, i, a) {
        return a[a.length - (i + 1)];
    });
};
var q = invert([1, 2, 3, 4]);
console.log(q);
Enter fullscreen mode Exit fullscreen mode

Array.prototype.sort

Much like our map(), filter(), and reduce() methods, the sort() method takes a callback() function that defines how the objects within an array should be sorted. But, like the reverse() function, it mutates the array in place. And that's no bueno.

arr = [200, 12, 56, 7, 344];
console.log(arr.sort(function(a, b) {
    return a b
}));
// arr is now: [7, 12, 56, 200, 344];
Enter fullscreen mode Exit fullscreen mode

We could write a pure sort() function that doesn't mutate the array, but sorting algorithms is the source of much grief. Significantly large arrays that need to be sorted really should be organized in data structures that are designed just for that: quickStort, mergeSort, bubbleSort, and so on.

Array.prototype.every and Array.prototype.some

The Array.prototype.every() and Array.prototype.some() functions are both pure and high-order functions that are methods of the Array object and are used to test the elements of an array against a callback() function that must return a Boolean representing the respective input. The every() function returns True if the callback() function returns True for every element in the array, and the some() function returns True if some elements in the array are True.
Example:

function isNumber(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
}
console.log([1, 2, 3, 4].every(isNumber)); // Return: true
console.log([1, 2, 'a'].every(isNumber)); // Return: false
console.log([1, 2, 'a'].some(isNumber)); // Return: true 
Enter fullscreen mode Exit fullscreen mode

Conclusion:

In order to develop an understanding of functional programming, we've covered a fairly broad range of topics. First we analyzed what it means for a programming language to be functional, then we evaluated JavaScript for its functional programming capabilities.
Next, we applied the core concepts of functional programming using JavaScript and showcased some of JavaScript’s built-in functions for functional programming.
Although JavaScript does have a few tools for functional programming, its functional core remains mostly hidden and much is to be desired. In the next parts, we will explore several libraries for JavaScript that expose its functional underbelly.

Discussion (0)