Understanding CoffeeScript Comprehensions

15 Jan 2012

If there's anything better than CoffeeScript, it's the amazing quality of documentation available. The main homepage, coffeescript.org is a stellar example many projects should be copying. And then there's the The Little Book on CoffeeScript which I have nothing but praise for (as a Little Book author myself!).

Nevertheless, I find that most resources don't properly explain CoffeeScript comprehensions. So, what's a comprehension? .NET readers would find it similar to LINQ...it's essentially a way to loop over and act on values in arrays or hashes.

Even though that sounds simple, I had a hard time grasping the syntax. I don't know what it was. Most of the resources show JavaScript code along with the corresponding CoffeeScript version. What helped me really understand comprehensions though was was breaking them down and first looking at the plain way to write them

In CoffeeScript, if you want to loop through an array, you use for in. To loop through hashes/objects you use for of. For example:

  heroes = ['leto', 'duncan', 'goku']

  for hero in heroes

  # Or, including the index
  for hero, index in heroes
    console.log('The hero at index %d is %s', index, hero)

  likes =
    leto: 'spice'
    paul: 'chani'
    duncan: 'murbella'

  for key of likes

  # Or, including the value
  for key, value of likes
    console.log('%s likes %s', key, value)

Now, that's pretty basic and understandable. I understood the syntax around comprehensions when I recognized the similarities to statement modifiers. Statement modifiers are a neat and useful way in CoffeeScript and in Ruby to execute an if (or unless) statement.

  # instead of
  if x == 0
    x = 1

  # we ca do
  x = 1 if x == 0

If you aren't used to these, it might seem like pretty useless syntactical sugar, but I believe that they contribute enough to code readability to make them worth using. Anyways, even though most people are used to thinking of code executing from left to right, I think the above statement modifiers (which first check the condition on the right) are easy enough to understand.

Being familiar with this type of syntactical trick, we can go back to our loops fancy them up:

  heroes = ['leto', 'duncan', 'goku']

  console.log(hero) for hero in heroes

  likes =
    leto: 'spice'
    paul: 'chani'
    duncan: 'murbella'

  console.log('%s likes %s', key, value) for key, value of likes

Comprehensions allows for conditions via when, so we do:

  heroes = ['leto', 'duncan', 'goku']

  # boring
  for hero, index in heroes
    if index % 2 == 0

  # cool
  console.log hero for hero, index in heroes when index % 2 == 0

Notice also that I dropped the parenthesis around the console.log. In CoffeeScript parenthesis are often optional and people tend to not use them while writing comprehensions.

There are other things you can do with compressions. For example, you won't always want to execute some code (like console.log) on each item. Instead you might want to select or map the results into another variable:

  evenHeroes = (hero for hero, index in heroes when index % 2 == 0)

The parenthesis in the above code are critical. Without them, only the last value of our comprehension ('goku') will be assigned. With them, the filtered array is assigned. The very first hero in the above code is what's being selected from our compression.

blog comments powered by Disqus