Breaking out of the For Loop: Pragmatic Functional Techniques in JavaScript
While JavaScript’s simplicity and small size is often an advantage, JS programmers sometimes must restort to techniques that are too low-level and convulted for the problem at hand. With no standard library to speak of, convinient abstraction mechanisms available in many other languages are often lacking from JavaScript.
A common scenario for many JS developers is extracting information from a list of objects. Consider the following datastructure.
var sales = [{ customer: 'Kurt', item: 'cup', price: 14.99 },
{ customer: 'Siri', item: 'drone', price: 1095.00 },
{ customer: 'Kurt', item: 'book', price: 22.50 },
{ customer: 'Lisa', item: 't-shirt', price: 25.00 }];
Let’s say we want to know if a particular item has been sold. JavaScript has no any-function, so we have to iterate over the list ourselves, either using recursion or a loop. In this case we will use a loop.
We could use a forEach-loop, but since we cannot return from inside a forEach-loop we will have to traverse the whole list of sales, even if the item we search for occures as the first in the list. For efficiency, we choose a for-loop instead.
function didSellItem(item, sales) {
for (var i = 0; i < sales.length; i++) {
if (sales[i].item === item) {
return true;
}
}
return false;
}
didSellItem('cup', sales) // => true
While this solution works, the for-loop is an irrelevant implementation detail that does not describe what we are doing particularly well. Readers of our code will have to spend more time figuring out what is going on.
We also “cheat” and return from within the for-loop so as not to traverse the whole list if we don’t have to. We could have used a while-loop, but that would bring its own set of cruft that is besides the point of what we are trying to acheive —checking if a particular item has been sold.
Library to the rescue #
Let’s solve the same problem using a functional library. There are several to choose from —Underscore.js, lodash, Ramda, etc. We’ll use Ramda, a relatively new library with good support for functional idioms. You can install Ramda via node $ npm install ramda
and then include it in your JavaScript file with var R = require('ramda');
.
Now, let’s turn back to the problem of checking if we’ve sold a particular item, this time using Ramda.
function didSellItem(item, sales) {
function saleContainsItem(sale) { return sale.item === item; }
return R.any(saleContainsItem)(sales);
}
didSellItem('cup', sales) // => true
The loop is gone. Instead we first define a predicate function (saleContains) that we use in Ramda’s any function. The code is shorter and the error-prone for-loop is gone.
Let’s take another example. This time we want to count the number of sales with a price above 1000. In this case JavaScript’s forEach-loop is an appropriate choice, since we need to traverse the whole list in every case.
function countPricySales(sales) {
var count = 0;
sales.forEach(function(sale) {
if (sale.price > 1000) {
count++;
}
});
return count;
}
countPricySales(sales) // => 1
JavaScript Arrays does have a reduce method which we also could use.
function countPricySales(sales) {
function countIfPricy(countSoFar, sale) {
return countSoFar + (sale.price > 1000 ? 1 : 0);
}
return sales.reduce(countIfPricy, 0);
}
countPricySales(sales) // => 1
Alternatively we could use Ramda’s reduce function.
function countPricySales(sales) {
function countIfPricy(countSoFar, sale) {
return countSoFar + (sale.price > 1000 ? 1 : 0);
}
return R.reduce(countIfPricy, 0, sales);
}
countPricySales(sales) // => 1
Note the difference between JavaScript’s and Ramda’s reduce functions. In JavaScript reduce is a method on the array itself, while in Ramda the function takes in the array to be reduced — a more object oriented vesus a more functional apprach, respectively.
*
The reduce solution to this last problem might not seem easier or more readable than the forEach solution, at least not to programmers that are not familiar with functional techniques. Some might even describe it as alien and off-putting.
Wheter or not functional techniques shold be used depends on your (and your teammates’) familiarity with the techniques and willingness to learn. Also, including external libraries — while offering many convenient functions — will also add complexity. Ultimatley, the value of using functional techniques and higher order functions is a tradeoff between the clarity and brevity it can offer, and the cost of learning what’s unfamiliar.
Scott Sauyet — one of the creators of Ramda — has a thorough introduction to functional programming in JavaScript.