This was written a long time ago when coffeescript was in early beta and I've decided to push it into the public domain. I've updated it a bit but I apologise for any oversights.Also worth mentioning that I am a lot more taken with it than I was at the time of the original blog. I will be revisiting this again soon.
If I've said it once, I've said it a million times - JavaScript is misunderstood. Sure it's got it's bad parts (eval, ==, typed wrappers etc.) and it's got it's VERY bad parts (global variables, scope, typeof etc) but it's also got a lot of beautiful parts (functions as first class objects, object and array literals, dynamic objects and prototypal inheritance etc.). JSLint validates your code against the good subset to ensure you aren't using all that nasty stuff that is going to break your code eventually but can we take it a step further? What if we could take out the good subset and create a subset of the language so we couldn't ever use the bad stuff? Well turns out you can.
Enter CoffeeScript
CoffeeScript is a full featured subset of JavaScript. Essentially it is a meta-language that compiles into JavaScript. Its syntax is a bit different, more like Perl, Python or Ruby and the JavaScript it generates only uses the "good parts" of JavaScript and passes all strict JSLint tests.
Syntax
CoffeeScripts syntax is close to JavaScript and besides a few extra goodies it is nearly a 1:1 mapping. However it strips away a lot of the boilerplate stuff to make the code you write slightly more succinct. Another feature of the syntax is that indentation is important (like Perl or Python, I can never remember which) as there are no line ending tokens. So lets look at a few statements
# Assignment:
number = 42
opposite = true
//-- GENERATED JS
// Assignment:
var number, opposite;
number = 42;
opposite = true;
This simple example of variable creation and assignment demonstrates how CoffeeScripts avoids one of the most common and potentially fatal mistakes made by many JavaScript developers - Inadvertent global variable declaration. In plain old JavaScript leaving out a var statement on a variable declaration results in the variable being declared (or overwritten -eek!) in the global scope. In CoffeeScript var is optional and all variable declarations are made in the current scope (and pushed to the top of the scope as is the JavaScript way). Want global scope declaration for some random reason? Use window.variable name and then ask yourself why you are doing it wrong.
# Conditions:
number = -42 if opposite
//-- GENERATED JS
// Conditions:
var number;
if (opposite) {
number = -42;
}
# Functions:
square = (x) -> x * x
//-- GENERATED JS
// Functions:
var square;
square = function(x) {
return x * x;
};
Function declarations are a good example of how succinct CoffeeScript can be in comparison to JavaScript. In fact anyone keen on C#'s lambdas or Groovys syntax will find this quite familiar. Gone are the bloating function and return keywords ( -> is the empty function - function() {})
# Objects:
math =
root: Math.sqrt
square: square
cube: (x) -> x * square x
//-- GENERATED JS
// Objects:
var math;
math = {
root: Math.sqrt,
square: square,
cube: function(x) {
return x * square(x);
}
};
# Existence:
alert "I knew it!" if elvis?
//-- GENERATED JS
// Existence:
if (typeof elvis != "undefined" && elvis !== null) {
alert("I knew it!");
}
Existence is a nice feature as it keeps non-boolean falsy values nice and strict and saves a lot of guessing. That's the basics but as I mentioned earlier there are some nice bonus features that can make some common tasks very clean.
The Goodie Bag
Array Comprehension
# Array Comprehension:
squares = ((x) -> x * x) n for n in [1,2,3]
//-- GENERATED JS
// Array Comprehension:
var n, squares, _i, _len, _ref;
_ref = [1, 2, 3];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
n = _ref[_i];
squares = (function(x) {
return x * x;
})(n);
}
Array comprehension is quite a common task in many JavaScript solutions. Simply put this is the Map part of any typical MapReduce situations (common in many NoSQL style databases and Ajax situations). MapReduce is essentially - given an array of items,
- Map: Transform that list into what you want (SQLs SELECT)
- Reduce: Based on that mapping remove unwanted items
In the Ajax world you'd probably need to do this when dealing with JSON or XML responses which are quite often generally result sets. The example above shows just how easy it is to apply an arbitrary function over an array of items. Now I guess the equivalent JS could be written slightly better by hand but would it not take longer and be harder to maintain? Probably. This being quite a common task the less you have to write the better!
Splats
Splats are a convenient way to work with the arguments object in JavaScript while clearly outlining required and optional arguments. Lets take this example.
# Splats:
log = console?.log or (o) -> alert o
printResults = (winner, runners...) ->
log "Winner:", winner
if runners?
((r) -> log "Runner up", r) for r in runners
printResults "James", "Paul", "Martin", "John"
//-- GENERATED JS
// Splats:
var log, printResults;
var __slice = Array.prototype.slice;
log = (typeof console != "undefined" && console !== null ? console.log : void 0) || function(o) {
return alert(o);
};
printResults = function() {
var r, runners, winner, _i, _len, _results;
winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
log("Winner:", winner);
if (runners != null) {
_results = [];
for (_i = 0, _len = runners.length; _i < _len; _i++) {
r = runners[_i];
_results.push((function(r) {
return log("Runner up", r);
}));
}
return _results;
}
};
printResults("James", "Paul", "Martin", "John");
Look familiar to you .NET people? It should because for all intents and purposes this is the same as the param keyword. Implementing this sort of thing in JavaScript is no easy task. Again the generated JavaScript could be simplified if written by a human bean but could you get it right first time without seeing this generated code?
Destructing Assignment
Already part of the JavaScript spec but not present in all implementations (see MDC article) destructing assignments allow the assigning of multiple values in one fell swoop e.g.
# Destructing Assignments
a = 1000
b = 0
[a, b] = [1000, 0]
# a now = 0, b now = 1000
# Multiple return values
weatherReport = (loc) -> [loc, 20, "Sunny"]
[city, temp, forecast] = weatherReport "Belfast"
# city = "Belfast"
# temp = 20
# forecast = "Sunny"
//-- GENERATED JS
// Destructing Assignments
var a, b, city, forecast, temp, weatherReport, _ref, _ref2;
a = 1000;
b = 0;
_ref = [1000, 0], a = _ref[0], b = _ref[1];
// a now = 0, b now = 1000
weatherReport = function(loc) {
return [loc, 20, "Sunny"];
};
_ref2 = weatherReport("Belfast"), city = _ref2[0], temp = _ref2[1], forecast = _ref2[2];
// city = "Belfast"
// temp = 20
// forecast = "Sunny"
I've never really come across a massive need for something like this but I guess it's another handy tool to have in the scripting world.
Should We All Be Using It?
If you think I am going to say "Yes" immediatley you are wrong. And if you are now asking yourself why I even bothered typing this.... lets just say train rides are boring but not as boring as most of the TV shows my darling wife makes me sit through EVERY FLIPPING NIGHT OF MY LIFE!!!!
The big reason I am not recommending it yet is that I am not 100% convinced of the benefits of using it above plain old JavaScript. It's a bit like marmite. Many node.js developers write in CoffeeScript, and ONLY CoffeeScript but many still simply reject it. I can understand both standpoints so until I can say without faltering that one side has got it right I am not taking it seriously. That said I prefer to form my own opinions anyway so I need to spend a lot more time with it before I can jump to any conclusions.
Oh and, honestly, who actually really wants to spend what limited time they have trying new things, hacking around, experimenting, taking risks, getting frustrated and making plenty of mistakes? Anyone? Anyone at all? Nah I didn't think so. Computers are just a 9-5 job for all of us ;-P