Negative Index in Coffee-Script

Coffee Script borrowed quite a lot syntax patterns from both Ruby and Python, especially from Ruby.
So people, like me, usually tends to write coffee-script in ruby way.

In ruby, we can retrieve the element in an array in reversed order by using a negative index, which means array[-1] returns the last element in the array. This grammar sugar is really convenient and powerful, so we can omit the code like this array[array.length - 1].

But for some reason, coffee-script doesn’t inherit this syntax. To me, it is weird. But after analyze this problem in detail, I found the reason.
Coffee script announce it has a golden rule: “coffee-script is just javascript”. So all the coffee script must be able to compiled into javascript.

Let’s try to analyze how the coffee script is compiled into javascript:

Coffee Script
1
2
3
array = [1..10]
second = array[1]
last = array[-1] // Psudocode

Obviously, the previous code should be compiled as following:

JavaScript
1
2
3
4
var array, last, second;
array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
second = array[1];
last = array[array.length - 1];

The negative index should be processed specially, so we should check the index is negative or not while compiling. This translation seems easy but actually not, since we can and usually use variable as the index.

Variable as index
1
2
3
4
5
array = [1..10]
index = 1
second = array[index]
index = -index
last = array[index]

In the previous code, because we use the variable as index, which cannot be verified in compile-time, which means we need to compile the array reference code as following:

Compile Result
1
2
3
4
5
6
var array, index, last, second;
array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
index = 1;
second = index >=0 ? array[index] : array[array.length + index];
index = -index;
last = index >=0 ? array[index] : array[array.length + index];

So every time we reference the array, we need to check whether the index is negative or not. This approach absolutely hurts the performance a lot, which in basically unacceptable.
So that’s why coffee-script doesn’t support the negative index.

Space Pitfall in coffee-script

Coffee Script had fixed quite a lot of pitfalls in Javascript. But on another hand it also introduced some other pitfalls, the most common one is the space.

Space in function declaration

Read the following code:

Show Message:Coffee
1
2
3
4
show = message ->
console.log message
show "space pitfall"

This is a quite simple script, but it failed to run. And if you might also feel confused about the error message: “message is not defined”

What happened to the code? We indeed had declared the message as argument of function show. To reveal the answer, we should analyze the compiled javascript.
Here is the compiled code:

Show Message:JS
1
2
3
4
5
6
7
8
// Generated by CoffeeScript 1.3.1
var show;
show = message(function() {
return console.log(message);
});
showe("space pitfall");

Look the fun declaration, you will see it is not a function declaration as we want but a function call.
The reason is that we omitted the parentheses around the argument and we add a new space between message and ->. So the coffee-script compiler interpret message as a function call with a function as parameter.

Soltuion
To fix this problem, we can remove the space between message and -> to enforce coffee-script compiler interpret them as a whole.

Show Message:Fix
1
2
3
4
show = message->
console.log message
show "space pitfall"

Best Practise
To avoid this pitfall, my suggestion is never omit the parentheses around the arguments, even there is only one argument.
And also including the function call, even coffee-script allow to omit the parentheses. Since you won’t able to chain the method call if you omit the parentheses.
So never omit parentheses, unless you are very certain that there is no any ambiguity and you won’t use method chain.

The space in array index

Since coffee script doesn’t support the negative index. So we should use following code as negative index:

Last Hero:Coffee
1
2
3
heros = ["Egeal Eye", "XMen", "American Captain", "IronMan"]
lastHero = heros[heros -1]
console.log lastHero

This piece of code is also failed to run, and the error message is “property of object is not a function”.
Quite wield right?
Let’s see what is behind the scene, here is the compiled code:

Last Hero:JS
1
2
3
4
5
6
7
8
// Generated by CoffeeScript 1.3.1
var heros, lastHero;
heros = ["Egeal Eyr", "XMen", "American Captain", "IronMan"];
lastHero = heros[heros.length(-1)];
console.log(lastHero);

Same problem, heros.length -1 is interpreted as heros.length(-1) instead of heros.length -1.
To fix this problem, we should write the code in following way:

Last Hero:Fix1
1
2
3
heros = ["Egeal Eye", "XMen", "American Captain", "IronMan"]
lastHero = heros[heros - 1]
console.log lastHero

Or

Last Hero:Fix2
1
2
3
heros = ["Egeal Eye", "XMen", "American Captain", "IronMan"]
lastHero = heros[heros-1]
console.log lastHero

Both solution is try to enforce the compiler divid the component in correct way.

And unfortunately, there is no way to avoid this problem, the only thing you can do is always be aware the spaces in expression.