By Patrick Lee

CoffeeScript Object Comprehensions

This article is based on CoffeeScript in Action, to be published Summer 2012. It is being reproduced here by permission from Manning Publications. Manning early access books and ebooks are sold exclusively through Manning. Visit the book’s page for more information.

To add up all the view counts for every property on the views object, you need to use a comprehension. In this article, based on chapter 4 of CoffeeScript in Action, you will learn how to use comprehensions to transform the properties and values of an object into other values.


Imagine you have a website with two pages. You want to track how many views each page on your website gets and the total number of views for all pages. Your website pages live on specific URLs but are referred to simply as the pages ren and stimpy for the sake of clarity. If the ren page has so far received 30 views and the stimpy page 10 views then that can be represented as:

   views =
     'ren': 30
     'stimpy': 10

 
Storing a property on the object is by assignment, so a count can be incremented using:

   views.ren = views.ren + 1

 
How about adding up all the view counts for every property on the views object though? How is that done? By using a comprehension. In this article, you will learn how to use comprehensions to transform the properties and values of an object into other values.

Object comprehensions

Comprehensions allow you to deal the elements of an array without having to write a bunch of boilerplate and iterate manually through every single thing in the array one by one, again and again and again. Check it out:

   number + 1 for number in [1,2,3,4,5]
   # [2,3,4,5,6]

 
Object comprehensions in CoffeeScript work similarly to how array comprehensions work. They also have a similar basic format to array comprehensions except they use the word of instead of in:

    expression for property of object

 
What does it do? Listing 1 is a side-by-side comparison with the JavaScript’s for…in loop that CoffeeScript replaces with object comprehensions:

 

Listing 1 Comprehension compared to for…in loop

CoffeeScript JavaScript
movie =
  title: 'From Dusk till Dawn'
  released: '1996'
  director: 'Robert Rodriguez'
  writer 'Quentin Tarantino'
for property of movie
  console.log property
var movie = {
  title: 'From Dusk till Dawn',
  released: '1996',
  director: 'Robert Rodriguez',
  writer: 'Quentin Tarantino'
} 
for (var property in movie) {
} console.log(property);
In the basic case, the JavaScript version is fine. However, the JavaScript version uses statements. In CoffeeScript, you should prefer expressions. To see the power of expressions in this specific case compare getting an array of property names from the object in listing 2.

 

Listing 2 Comprehension as expression

CoffeeScript JavaScript
properties = (prop for prop of movie)
var properties = {};
for (var prop in movie) {
   properties.push(p);
}

The JavaScript version uses multiple statements and has to micromanage the state of the variables properties and p. The CoffeeScript version does not.

Comprehending properties

The property names of an object can be returned as an array using a comprehension:

   name for name of {bob: 152, john: 139, tracy: 209}  # ['bob', 'john', 'tracy']

 
Imagine now that your website has four pages named by the path to those pages. The object below shows an example:

   views =

      '/reviews/pool-of-radiance': 121

      '/reviews/summer-games': 90

      '/reviews/wasteland': 139

      '/reviews/impossible-mission': 76

 

A list of pages from this object is obtained using the following comprehension:

   url for url of views

 
Resulting in an array containing the page names:

   [ '/reviews/pool-of-radiance'

     '/reviews/summer-games'

     '/reviews/wasteland'

     '/reviews/impossible-mission' ]

 

Comprehending values

To get the property values from an object instead of the property names, use a slightly different comprehension format:

   value for property, value of object

 
For example,

   score for name, score of {bob: 152, john: 139, tracy: 209} # [152, 139, 209]

 
The number of views for each page is obtained using a comprehension

   count for url, count of views

 
For the four pages described earlier results in an array containing the page views for those pages:

   [ 121, 90, 139, 76 ]

 
Alternative format

In the total function the comprehension looks like

   for property, value of object
      expression

 
This works the same as the format

expression for property, value of object

 
The expression will be evaluated for each property in the object. The comprehension can be used to collect and sum the view count for all of the pages:

Example

CoffeeScript JavaScript
properties = (prop for prop of movie)
var properties = [];
for (var prop in movie) {
   properties.push(prop);
}
			

To keep track of how many views each page gets, you will need a function to increment the value stored against an individual page. To get the total number of views for all pages, you will need a function that sums all of the values of the object. Listing 3 is a first implementation of this program. A detailed discussion follows the listing:

Listing 3 Page views

views = {} #A

views_increment = (key) ->                    #B
  views[key] ?= 0                             #B
  views[key] = views[key] + 1                 #B
total = ->
  sum = 0                                     #C
  for own url, count of views                 #C
     sum = sum + count                        #C
  sum                                         #C

#A The views object, created ex-nihilo
#B An increment helper function
#C A total function to add up the page views

Reduce!

If you were expecting the sum to be done inside the total function with a reduce on the array, then rest assured that you can and should use Array.reduce in CoffeScript.

 Getting started

To begin, create a views object ex nihilo:

   views = {}

 
Dealing with undefined properties

The views object does not yet have any properties. If there is a page called donatello that receives a view, how do you increment a donatello property that does not exist? If the views object does not have a donatello property, then this must be the first view to the page donatello and so the value should be set to 1. The existential operator

can be used to determine if a property is defined:

   if !views['donatello']?
     views['donatello'] = 1

 
This is a common pattern, so CoffeeScript has a shorter version of it called existential assignment. Put an existential operator in front of the assignment operator:

   views['donatello'] ?= 0

 
You will notice that the views_increment function takes the name of the property as the argument key, which it

uses as the property name. The specific case of donatello is then generalized:

views[key] =? 0

 

Updating values

Once the views object has a property, the value can be incremented:

   views[key] = views[key] + 1

 
Getting the total

To compute the total number of views for all pages, you can now use a comprehension on the views object:

sum = 0
for own url, count of views
   sum = sum + count

 
Own properties

The total function adds an own to the comprehension, immediately after the for keyword: for own url, count of views

Objects can get properties that were not defined directly on them. In this case, it was important not to include any properties not defined directly on the object. Properties of an object that are not defined on the object come from prototypes.

TIP

If you are using an object as a key-value store, always use own inside comprehensions on that object.

Summary

Considering that objects as key-value stores provide code-as-data for CoffeeScript, you would expect an equally convenient way to do things with that data. Comprehensions provide dedicated syntax to do that. In this article, you learned how to write comprehensions for objects.

Tagged with:
 
  • http://www.aaron-powell.com Aaron Powell

    Is it my imagination or does the output JavaScript in listing 1 have a bug?

  • http://www.userinexperience.com Brandon Satrom

    not your imagination, my fat fingers. nice catch, thx.

  • http://www.bglen-fan.net/ ビーグレン

Switch to our mobile site