Python-like List Comprehensions in JavaScript

Python has a neat syntactic feature where you can take a literal list constructor's bracket notation (e.g. [2, 4, 6, 8, 10]) but have code statements generate the list's contents. So instead of writing out the 5 even numbers in the example, one could write [(x+1)*2 for x in range(5)] which turns out to be more characters but you can imagine the savings if the series in the list was longer. Instead of range() we could have a literal list [0, 1, 2, 3, 4] or even a generator function, anything which is iterable.

How could we do this in JavaScript? EcmaScript 5 doesn't have any built-in syntactic sugar for list comprehensions (or array comprehensions since Python lists are JS arrays). There is a proposal for array comprehension syntax which looks similar to Python but it is not implemented by browsers (except some Firefox). So we will ignore any "native" array comprehensions.

Instead, we can use other base functional programming functions to achieve the same thing. Combined with EcmaScript 6's array function notation it becomes simpler.

For convenience, we first should define a JavaScript version of Python's range() function but keep it to a single argument, the range max.

var range = max => Array.from(new Array(max), (_, i) => i)

Now, we want to take the list returned by range(5) and create a new list of numbers from 2 to 10 instead of 0 to 4. This should do the trick:

var that_array = range(5).map(x => (x+1)*2)

So the general pattern is to take the list or iterable and imagine it as a JS array, then take the lambda expression in the prefix of the list comprehension and make it an anonymous function and an argument to map(). Let's try another example. First, Python:

squared_evens = lambda max: [x*x for x in range(max) if x % 2 == 0]

Note we are assigning a lambda expression to a variable, squared_evens, so our range function can be arbitrarily and variably large, and that we are applying a condition to the elements of the list: if x % 2 == 0 or only even numbers. Python's list comprehensions combine the map and the filter into a single expression. How can we do this in ES6 JavaScript?

var squared_evens = (max) => range(max).filter(x => x % 2 == 0).map(x => x*x)

So why does Python have syntactic sugar to simplify the above ES6? Python has all the functional programming stuff like map, reduce, and filter builtin. But here's how it would look using those instead;

squared_evens = lambda max: map(lambda x: x*x, filter(lambda x: x % 2 == 0, range(max))) ...which just doesn't look as nice as: squared_evens = lambda max: [x*x for x in range(max) if x % 2 == 0]

To be fair, in ES5 the equivalent JavaScript is less succint:

function squared_evens(max) { return range(max).filter(function (x) { return x % 2 == 0; }).map(function (x) { return x*x; }) }

If you found this interesting you may enjoy List Comprehensions in PHP.

Add new comment