Functional-like data manipulation in JavaScript
Learn how to exploit the native JavaScript library to your benefit by manipulating the data conveniently and transparently.
Learn how to exploit the native JavaScript library to your benefit by manipulating the data conveniently and transparently.
Functional programming (or, more accurately, functional-oriented programming) is a-booming in the software industry nowadays. Much like object-oriented programming back in its heyday somewhere in the late 1990s, it became little more than a buzzword to a commonplace programmer or a sophomore IT student.
FP (as it will be referred in this article henceforth) enabled developers to process data more intuitively, using transformations on a collection of objects or primitives to process them as a whole instead of in piecewise fashion. However, unluckily for the beginners who wish to elaborate on the topic, most explanations of FP are fraught with mathematic elucidations hardly relevant for common programmers. I won’t delve here into mostly academic applications of functional programming as it is meant by computer scientist, so no talk of monads here – for those more interested in the topic from the theoretic side, I would definitely recommend a free book, which comes with an explanation of the aforementioned topics and much more and free Haskell knowledge. Definitely worth a grab!
As much as a subject of hype, I consider FP a must-have in both front-end and back-end programmer’s toolbox – especially if we are talking JavaScript on both ends. Today, many languages considered before only an academic exercise are flourishing, the closest example being Haskell-based Elm language, which compiles to JavaScript (as if front-end design wasn’t complicated enough!)
Those so-called pure functional solutions, however, are not a subject of this article. Instead, I will describe how to use some ES6 magic and native JavaScript library to your own benefit.
Seriously.
Most of the time, where there’s data, it’s almost always stored in arrays, objects or maps (ie. structures where each piece of the data (value) has its corresponding, unique identifier (key). And in today’s Javascript, we have a simple way of going through (that is, iterating) all of these without resorting to for loops. Of course, more advanced transformations of data might involve using nested loops and recursion, which iterating cannot account for or is more complicated than using the loop in the first place. These are rare in everyday coding, however – and FP is here to solve all the rest and help your code clean and understandable.
To appeal to both beginner and intermediate programmers alike, I’m going to be using some fruitful analogies – our subject (ie. collection) being an assortment of fruit and our transformations will be acting directly on the single pieces of fruit (items of the collection). Let’s get started!
Suppose you are an IT technician employed in health food factory, tasked with maintaining the fruit evident owing system. In the system, every fruit is represented by a single JavaScript object. We are usually going to operate on arrays of these, which are perhaps served from the backend of some sort. For example:
[ { name: 'apple', weight: 0.18 }, { name: 'banana', weight: 0.25 }, { name: 'avocado', weight: 0.31 }, { name: 'watermelon', weight: 1.85 } ]
In each of the examples below, these objects are going to have fields which are relevant to the given example, so the collections may differ – this is to keep them simple. Also in most of the code, we will be using ES6 additions where possible (const and let keywords, rest/spread operator etc.), as they are elegant and quite powerful.
Your first task will be making the fruits unique by assigning to each of them an identifier (ID) of the sort. Since our data structure is flat, we will simply use the index of each element in the array as the identifier.
for(let i = 0; i < collection.length; i++) { const item = collection[i]; item.id = i; }
Good, old-fashioned for loop does the job and does it well. We run through every item of the collection by using indexes of the elements to access the items themselves and then assign a field called id to each, containing the index itself. The very clutter needed to get them for loop going, however, easily gets tedious to write and is naturally error-prone – it is easy for a newbie, say, to misplace a < sign and use <= instead, actually going beyond the bounds of the array.
Also, since loops are a very generic mechanism, they require more analysis from the programmer reading somebody else’s code to reveal the purpose of the loop and possible side effects (of which we will talk briefly later on).
The solution for the clutter would be, in principle, using a for…in loop:
for(let i in collection) { const item = collection[i]; item.id = i; }
After execution of the code, our collection would look like the following:
[ { name: 'apple', weight: 0.18, id: 0 }, { name: 'banana', weight: 0.25, id: 1 }, { name: 'avocado', weight: 0.31, id: 2 }, { name: 'watermelon', weight: 1.85, id: 3 } ]
for loop still requires us to use the brackets operator to access an item from the collection. Since usually, it’s the item we are looking for and not just its index, we can use forEach – a native library method, pertaining to every array object. Please note we are operating directly on the collection:
collection.forEach((item, index) => { item.id = index; });
Now, while it does not take much less space, the purpose of the code is now more clear. And it only becomes more advantageous as we add more steps to the ladder! Some explanation is in order:
The => (fat arrow notation) is an ES6 shorthand for anonymous functions. They must not have a name (they can be assigned to a variable, though) and take their arguments in round braces. The arrow follows, and after the arrow, the body of the function. They need not use curly braces if they are one-liners and are somewhat different when used in object context. More on fat arrow functions.
The term for a function that takes a function as an argument is a higher-order function, and that’s what our function forEach exactly is. Let’s proceed with another example.
Laid before yourself is another task – you have to list the names of all the fruit in order to have a quick preview of the contents of our fruit store. We want to have a new array containing all the names. We can do it like so, using our new friend forEach:
let fruitNames = []; collection.forEach((item, index) => { fruitNames.push(item.name); });
The fruitNames array will now look like so:
['apple', 'banana', 'avocado', 'watermelon']
Now, forEach is still quite generic and while useful, it doesn’t offer many obvious advantages over for…in loop. Perhaps there exists a more specialized utility for our task?
Turns out, it does – map comes to our rescue. Our code will now look like this:
const fruitNames = collection.map((item, index) => { return item.name; });
For an even more compact version (since we don’t need index here anyway):
const fruitNames = collection.map(item => item.name);
As usual, some explaination:
To put it curtly, a map takes elements from our collection and creates an entirely new collection with the elements being transformed somewhere along the way. Here we pick just the name of each item and supply it as a resulting item, that is, we return it from the inner function. This mechanism is worth noting, since many other functional utilites make use of it.
Suppose our collection now looks like this:
[ { name: 'apple', bestBefore: '2017-02-10' }, { name: 'banana', bestBefore: '2017-01-05' }, { name: 'pear', bestBefore: '2016-12-10' }, { name: 'cantaloupe', bestBefore: '2017-10-05' }, { name: 'khaki', bestBefore: '2016-12-24' } ]
Every fruit in our system is now accompanied by its best before date. How do we get rid of the spoiled fruit? The answer is, we filter our collection:
const freshFruit = collection.filter(fruit => { const bestBefore = new Date(fruit.bestBefore); const now = new Date(); return bestBefore > now; });
The freshFruit array will consist of the whole objects of the fruits that are still palatable (which depends, of course, on today’s date). The Date object constructor invocations are here for the purpose of directly comparing the two dates – the bestBefore date and today.
We can see the inner function, called a filter itself, should return a Boolean-like value, so we are using the comparison operator to return either true (for fresh fruit) or false (otherwise).
Meet reduce, the last of prototypical functional methods. Though a little more complicated to understand, it is remarkable in its ability to act as either of the above mentioned functions: forEach, map and filter – indeed, each of those can be written in terms of reduce function. It can recreate a lot more of operations as well.
reduce takes a collection (array) and reduces it to a single value. Note that single does not denote a primitive value, like a number or a string – it can very well be an object or another array.
Instead of jumping straight into the code, let’s use an example.
Suppose now your factory needs a front-end smoothie machine, so that clients can choose several kinds of fruit to create a delicious health beverage from. Luckily, your part of the task boils down to outputting a name for the smoothie (ie. a string), something like banana-lingonberry-pear juice).
Let’s suppose our collection stays the same as in the filter example. Basically, we need to take only the names of the fruit and figure out an elegant way to put them together, along with some suffix (juice).
const juiceName = collection.reduce((result, fruit, index) => { const delimiter = index > 0 ? '-' : ''; return fruit.name + delimiter + result; }, ' juice');
This can be (just for illustrative purposes, as it is somewhat harder to read) put into a one-liner:
const juiceName = collection.reduce((result, fruit, index) => fruit.name + (index > 0 ? '-' : '') + result, ' juice');
Our result, which will be contained in juiceName, will equal the string watermelon-avocado-banana-apple juice. Note that the order in which the fruit appear is reversed – this is because we are actually prepending each name to our string.
What’s happening here? Let’s start by noticing that, unlike our previous functions, reduce takes two arguments: the inner function (often named the reductor) and initial value, which will act as a seed for the resultant value.
const result = collection.reduce(reductor, initialValue);
The reduction function itself takes at most four arguments, in order: partialResult (also called the accumulator), item (as in forEach, map and filter), index and collection (likewise). Most of the time you will make use of just the first two.
reduce, just as map does, iterates over every element of our collection. The important part is that in each iteration, as we go through elements one by one, the accumulator part takes on a value that was returned by the inner function in the previous iteration. In case of the first iteration this value is, of course, absent – the initialValue is used instead.
To illustrate, let’s unwind the inner loop that our example uses and take a look at the state of some of the variables in our scope, step by step:
result = ' juice', fruit.name = 'apple', index = 0, delimiter = '' result = 'apple juice', fruit.name = 'banana', index = 0, delimiter = '-' result = 'apple-banana juice', fruit.name = 'pear', index = 0, delimiter = '-' result = 'apple-banana-pear juice', fruit.name = 'cantaloupe', index = 0, delimiter = '-' result = 'apple-banana-pear-cantaloupe juice', fruit.name = 'khaki', index = 0, delimiter = '-'
The last value returned from our inner function will be assigned to our juiceName variable.
In another example, let’s take weights of our fruit and sum them up.
const totalWeight = collection.reduce((result, item) => { return result + item.weight; }, 0);
That’s quite powerful, isn’t it? To illustrate the concept further, let’s recreate forEach, map and filter in terms of reduce function.
const forEach = (collection, fn) => collection.reduce((result, item, index, collection) => { fn(item, index, collection); return undefined; }, undefined); const map = (collection, fn) => collection.reduce((result, item, index, collection) => { result.push(fn(item, index, collection)); return result; }, []); const filter = (collection, fn) => collection.reduce((result, item, index) => { if(fn(item, index, collection)) { result.push(item); } return result; }, []);
Note that signatures of our functions will change a little and our new functions will have to be invoked as follows: name(collection, innerFunction), e.g.
forEach(collection, item => console.log(item)).
An observant reader would notice that most functional operators described above return values that are collections (arrays) themselves – we can exploit that to chain the transformations without resorting to creating intermediate variables. Consider this example:
collection .filter(fruit => fruit.weight > 0.5) .sort((fruitA, fruitB) => fruitA.name > fruitB.name) .forEach((fruit, index) => console.log(`Fruit ${index+1}: ${fruit.name}`));
This code will pick all the heavy fruit, sort them by their name alphabetically and then will output their names into the console, perhaps resulting in a printout somewhat like below:
Fruit 1: coconut Fruit 2: melon Fruit 3: watermelon
The sort function and many more that are not the subject of this article can be found in the great MDN Array reference.
The journey does not end here – there are libraries that offer convenience functions for more specialized purposes, i.e. grouping the objects by some predicate, filtering along the keys or values of a collection or merging two or more collections in a well-defined fashion.
The choice of tooling is quite vast, but Lodash is a functional library with one of the best documentations there are. Lodash expands the functions we learned about today with some convenience notations and provides far more related utilities. You can grab it here.
Today we learned how to exploit the native JavaScript library to our benefit by manipulating the data in a convenient and transparent way. Also, thanks to the self-explanatory fashion of functional transforms, the code we write looks cleaner and is more obvious to a fellow programmer… as long as they are versed in functional programming, of course!
There are cases where using our FP-oriented toolset is overkill or perhaps a simple loop gets it meaning across well enough, but such is the nature of abstractions, to which functional-oriented programming belongs. Nevertheless, the benefits are there, and personally I can’t imagine myself going back. Hopefully, after getting the hang of mapping and filtering you will be poised to say the same. 🙂
Are you looking for a tech partner? Searching for a new job? Or do you simply have any feedback that you'd like to share with our team? Whatever brings you to us, we'll do our best to help you. Don't hesitate and drop us a message!
Drop a message