CoffeeScript's Magic of Blanks

November 14, 2014

The Problem

CoffeeScript greatly simplified Javascript’s syntax, omitting braces, parentheses, semi-colons, and a great deal of commas. This often generates terser code. But, there are totally opposite perspectives regarding readability: some people belive CoffeeScript cdoe is more readable, while others argue against it. In my opinion, actually, both sides of the coin are true. Consider an example in favor of the positive argument:

foo
    a: 1
    b: 2

while compiles into the following Javascript:

foo({
    a: 1,
    b: 2
});

Clearly the Coffee code seems more readable.

Yet another example supporting the negative side:

foo () -> "foo"
foo() -> "foo"

these two nearly identical lines generates completely different Javascript:

foo(function() {
    return "foo";
});

foo()(function() {
    return "foo";
});

The first line means passing an anonymous function () -> "foo" to the named function foo, while the second line is passing the same anonymous function -> "foo" (recall that the empty parentheses can be omitted) to the object returned by the same named function foo, which could well be a funtion too.

This huge difference between the subtle change in code sure is a firm argument against the readability of CoffeeScript. Because the only difference in the two lines is the single blank, this example clearly emphasises the importance of blanks in CoffeeScript.

Finding the Answer

To understand the role of blanks in CoffeeScript, let’s start with something that is supposed to be rather simple:

a b c

But the question of ambiguity rises immediately: what does this exactly mean? Experiences with other programming languages gives us the following possible answers:

a(b(c))  # function: a, argument: b(c)
a(b, c)  # function: a, argument: b and c
a(b)(c)  # function: a(b), argument: c

This involves also the role of commas in CoffeeScript: they are only used to seperate function arguments and fields of objects. By knowing this, and the fact that blanks are never translated to commas, the second candidate drops out. Now if we use our heads harder, the difference between candidates number 1 and 3 is really a question of associativity: are blanks in CoffeeScript left or right associative? If they are right associative, candidate #1 wins; if they are left associative, candidate #3 wins.

Now what does the judge – the compiler – say? He chooses candidate #1. That is to say, blanks in CoffeeScript are right-associative.

Why is that? Maybe it helps to understand the behaviour of the CoffeeScript compiler in this way:

It always translates blanks (of course after removing successive and unnecessary blanks) into left parathensis (, and adds needed right paranthesis ) at the end.

Managing the Answer

Frankly speaking, this is a brilliant choice, since it’s the most natural way people write code. Consider now a reall-world example. Say we want to test an array of tags agains an array of regular expressions. For each regular expression, if any of the tags match it, consider it successful, and we want to know if every regular expressions are successful. Thanks to Javascripts’s some and every functionality, we can manage the task by writing the following single line of CoffeeScript code:

flag = regs.every (r) -> tags.some (t) -> r.test t

According to the rule we just discovered, replace the blanks after every, some and test by left paratheses, and add three right parantheses at the end. Notice that the blanks before and after -> should be removed since they are part of the function definiations and are unnecessary. This line is equivalent to

flag = regs.every((r) -> tags.some((t) -> r.test(t)))

meaning passing t to r.test, using its return value as the return value of a new function (t) -> ..., and pass that function as the argument for tags.some, whose return value should be returned by another new function (r) -> ..., then pass that function as the argument for regs.every, and finaly use the return value of regs.every.

You may have observed, we actually have read the line from right to left, and it sounds quite natural. That’s right. It’s recommended to read CoffeeScript code from right to left. It’s not that straitforward to always replace blanks with left paratheses in head, but the latter piece of advice comes in handy.

Summary

I think it’s pretty clear now, that after we understand the role of blanks (and commas too), there’re no magic at all. Our world of CoffeeScript just got better.

If you have commens about this, either positive or negative, just leave them below. I’d be happy to see them.