.

Tags:

Many modern programming languages support list comprehension, a concise way to create a list based another list where each entry is the result of some operations. If comprehension is used properly, it eliminates the need for the traditional and error-prone manual iteration. Next-generation JavaScript will have the similar feature via array comprehension.

First of all, let’s do a quick refresh on Array’s map and filter functions.

Array.prototype.map

Section 15.4.4.19 of the official ECMAScript 5.1 specification defines the official behavior of Array.prototype.map. This function returns a new array resulting from applying the given callback function to each entry.

Two quick examples:

[1, 2, 3].map(function (i) { return i * i }); // [1, 4, 9]
[650,123,4567].map(String).join('-'); // "650-123-4567"

This facilitates a one-liner to build a sequence of numbers:

Array.apply(0, Array(3)).map(function(x, y) { return y }); // [0, 1, 2]

or even English alphabets ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ':

Array.apply(0, Array(26)).map(function(x,y) {
  return String.fromCharCode(y + 65);
}).join('');

For other variants, see also Brandon Benvie’s usage of apply-map and Ben Alman’s Object.keys technique.

Update: For details, see also my other blog post Sequences with JavaScript Array.

Array.prototype.filter

Section 15.4.4.20 of the official ECMAScript 5.1 defines the official behavior of Array.prototype.filter. As the name says, this function lets you include or exclude some entries of the array based on some certain criteria. Take a look at the following example:

[1,4,2,3,-8].filter(function(i) { return i < 3 }); // [1, 2, -8]

Let’s extend the previous sequence number generation, say to have only odd number:

Array.apply(0, Array(6)).map(function(x,y) { return y }).
filter(function(x,y) { return y & 1 }); // [1, 3, 5]

We can also do a complicated dance to print all consonants by excluding the vowels:

Array.apply(0, Array(26)).map(function(x,y) { return String.fromCharCode(y + 65) }).
filter(function(s) { return 'AEUIO'.indexOf(s) < 0 }).join('');

Real-world applications are likely more practical than the above snippets. It could be something like:

Give me the list of house prices in a certain ZIP code
What is the total expense of our Engineering department?
Find the best paid professions of Gen X

Array Comprehension

Array comprehension is a syntax feature which has been available in Firefox for a while. It is however not part of the 5th edition of ECMAScript and hence no other browser supports it. The good news is that array comprehension is being incorporated into the next ECMAScript 6. The latest 2012/12/21 draft includes the grammar of array comprehension in section 11.1.4.2. Update: The following code examples have been adjusted to follow the latest comprehension syntax (left-to-right).

An easy way to understand how array comprehension works is by comparing it with map and filter. See the following two lines, they give the same exact result. The second line is something you have seen in the previous map example.

[for (i of [1, 2, 3]) i * i]; // [1, 4, 9]
[1, 2, 3].map(function (i) { return i * i }); // [1, 4, 9]

The fun part is when you use two for clauses or more. The following line creates a list which contains the references to all 64 possible squares in a chess board, from ‘a1′ to ‘h8′.

[for (x of 'abcdefgh'.split('')) for (y of '12345678'.split('')) (x+y)];

If this still looks confusing, I highly recommend understanding the syntax tree, for example by using Esprima’s syntax visualization.

es6comprehension

Note: Firefox’s array comprehension also support for-in form (I am not sure whether this will make it into ES6). It can simplify some construct, generating a sequence of numbers can be rewritten as [j for (j in Array.apply(0, Array(3)))].

Filtering using array comprehension is straightforward. Again, compare the two different forms here:

[for (i of [1,4,2,3,-8]) if (i < 3) i];
[1,4,2,3,-8].filter(function(i) { return i < 3 }); // [1, 2, -8]

And the simplification of printing the sequence of all alphabets:

[for (i of  Array.apply(0, Array(26)).map(function(x, y) { return y; }))
String.fromCharCode(65 + i)].join('');

and just the consonants:

[for (j of
  [for (i of Array.apply(0, Array(26)).map(function(x, y) { return y; }))
   String.fromCharCode(65 + i)]
) if ('AEUIO'.indexOf(j) < 0) j];

As an exercise, analyze the following expression. What it does is producing the list of all prime numbers less than 100. You can see that there is no need for a manual loop at all. Note that since we are talking about next-generation JavaScript, we also use the arrow function (Section 13.2) to shorten the incantation.

[
  for (x of Array.apply(0, Array(99)).map((x, y) => y + 2))
    if ([
      for (i of Array.apply(0, Array(1 + Math.round(Math.sqrt(x)))).map((x,y) => y))
      if ((i > 1) && ((x % i) === 0))
      (x % i)
    ].length === 0)
  x
];

With the support for array comprehension, JavaScript is getting more and more functional. If you come from Python, Haskell, Scala, or another modern language, you won’t feel so powerless anymore!

  • http://twitter.com/jefftschwartz Jeff Schwartz

    While I welcome these additions in the pending ecmascript6, as well as others such as let for true variable scoping beyond the current function scope, ecmascript6 has really disappointed me in that is still doesn’t respond to our need for real modules. While AMD and CommonJS are currently available, it will be a great asset to have a singular definition of modules that is supported by all browsers, libraries and platforms. This would make modular JavaScript a reality. Until then, we can only hope and wait. This feature would be akin to Java’s class loader which if you think about it, makes Java classes compatible across VMs. We need this in JavaScript too and sooner rather than later. Why isn’t anyone listening?

    • Sean McMillan

      Where are you getting your information? Ecmascript 6 is going to include a module system, with a flexible module loader. http://wiki.ecmascript.org/doku.php?id=harmony:modules

      • http://ariya.ofilabs.com/ Ariya Hidayat

        Harmony != ES 6. It may trickle into ES 6 but so far Section 14.2 of the latest ES 6 draft is still empty.

        • Sean McMillan

          Fair enough. I get confused sometimes. I thought that harmony proposals were greenlighted for inclusion in ES6, and that not-fully-baled ones were “strawman”.

          They certainly get a good amount of traffic on es-discuss

  • http://twitter.com/rwaldron rick waldron

    Just a heads up: At the TC39 meeting last week, we changed the order of comprehension syntax from right-to-left to left-to-right and added parens for clarification.

    • http://ariya.ofilabs.com/ Ariya Hidayat

      Thank you for the notice, Rick! Once this is reflected in another spec draft update, I’ll adjust the blog post accordingly.

  • http://twitter.com/bobmyers Bob Myers

    >> [650,123,4567].map(String).join(‘-‘); // “650-123-4567″

    But [650,123,456].join(“-“) works even without explicit conversion to String…

  • http://tomleo.com/ Tom Leo

    I couldn’t get the list comprehension to work in Chrome 24 or Firefox 18, just curious what browsers support this feature.

    p.s. i’m sting very new to javascript

    • http://ariya.ofilabs.com/ Ariya Hidayat

      You can use Firefox 18, although the syntax is slightly different (until ES 6 draft catches up with the TC 39 update), e.g. `[i*i for (i of [1,2,3])]`.

  • Peter van der Zee

    Bracket mismatch in this example:

    [j for j of [String.fromCharCode(65 + i)
    for i of Array.apply(0, Array(26)).map(function(x, y) { return y; })]
    if (‘AEUIO’.indexOf(j) < 0)].join('');

    • Peter van der Zee

      Or not. Meh this is confusing. I know they can come in handy, but for the sake of legibility and learning curve I wish they wouldn’t add it to the language.

      • http://ariya.ofilabs.com/ Ariya Hidayat

        Why not? Other languages have the similar feature. I think it’s more because many people are not familiar with functional programming, anything new always triggers confusion (or fear).

        • ernest

          yes but you gotta admit syntax is difficult to read and a language is about expressivity.