CoffeeScript: A Replacement for JavaScript?
Back in 2009, Jeremy Ashkenas had a huge amount of work on his desk. Full of zeal, he published in quick succession several versions of a new programming language with the goal of its being easier to read than JavaScript yet translate into JavaScript code. Just two years later, the Ruby on Rails project integrated what has since been dubbed the CoffeeScript language.
In September 2012, Dropbox announced that it had ported the complete JavaScript code of its browser client to CoffeeScript. The figures published by the cloud storage service are impressive: The Dropbox client code shrank by about 21 percent.
Cold Coffee
CoffeeScript, however, is not a completely new language. Ashkenas took good old JavaScript, replaced a couple of the more complex constructs with more readable abbreviations, and seasoned it with some Haskell and Ruby. For example, instead of saying
alert("Hello World!");
in CoffeeScript, you can simply say
alert "Hello World!"
This makes CoffeeScript code much more compact and thus easier to maintain. Additionally, you can continue to use existing JavaScript code, including existing frameworks, directly in CoffeeScript.
As in the case of alert , some of the shortcuts are optional, but most are mandatory. For example, the new scripting language no longer supports the typical JavaScript for() loop. In such cases, the user is therefore forced to use the corresponding CoffeeScript constructs, which are presented later in this article.
Because CoffeeScript code, at least for the time being, does not run directly in browsers, the language inventor provides a compiler that translates CoffeeScript code into standard JavaScript code. The idea was for the generated code to be easy to read, pass JavaScript Lint tests, not cause syntax errors or warnings, work in any JavaScript Runtime Environment, and be at least as fast as the equivalent, hand-crafted JavaScript code. As a bonus, the compiler comes with a small interpreter that runs CoffeeScript programs directly at the command line.
If you are curious, you can embark on your first CoffeeScript adventure directly on the CoffeeScript website by clicking on Try CoffeeScript and entering your code into the blue window, the CoffeeScript console.
This console converts your code in the background while you are typing and displays the results on the right-hand side (see Figure 1). To execute your own code, just click Run .
Installation
The compiler is written in CoffeeScript, which means you can even execute it in a browser, as the CoffeeScript website proves with its console. However, the interpreter uses the JavaScript Node.js framework (or more precisely, one Node.js module). To install the CoffeeScript tools, you therefore need Node.js and the node package manager, npm . Some distributions, such as Ubuntu, have both in their repositories. You must have at least version 1.1.6x of npm (try npm --version ). If not, GitHub has an informative page on how to install current packages for various distros. As soon as you have installed this duo, the following command is all it takes to install the CoffeeScript tools:
sudo npm install -g coffee-script
The source code for the tools is also up for grabs on the CoffeeScript website [1]. You will find it at the top of the page by clicking on the version number to the right of Latest Version .
To execute CoffeeScript code at the command line, you need to store it in a file with the .coffee extension and then run the interpreter against the file (Figure 2):
coffee hello.coffee
At this point, you will discover two minor pitfalls. First, the interpreter starts the node program, which some distributions call nodejs . If you see this error message, you either need to set up a symlink manually or look at the package manager once again: Ubuntu offers the nodejs-legacy package especially for this purpose.
Second, not all JavaScript functions are available on the command line. This applies in particular to programs that depend on a window, such as alert . To coax the interpreter into displaying a “Hello World,” you thus have to feed the CoffeeScript code console.log "Hello World" to it.
Finally, to compile a .coffee script, use the -c parameter:
coffee -c hello.coffee
The JavaScript code generated from hello.coffee is stored in the hello.js file.
Dash Mount
As the simple Hello World example shows, CoffeeScript coders can omit the semicolons at the ends of the lines; the return character serves as a replacement. Braces (curly brackets) can also apply for early retirement: As in Python, indentation clarifies which block a row belongs to. Listing 1 shows how clearly you can define an object, and Listing 2 shows the resulting generated JavaScript code.
Listing 1: Definitions with Indents
01 # CoffeeScript 02 autos = 03 bmw: 04 color: "Blue" 05 Model year: 1990 06 vw: 07 color: "Red" 08 Model year: 1988
Listing 2: JavaScript Code for Listing 1
01 // JavaScript 02 var autos; 03 04 autos = { 05 bmw: { 06 color: "Blue", 07 Model year: 1990 08 }, 09 vw: { 10 color: "Red", 11 Model year: 1988 12 } 13 };
Programmers can use variables directly; the appropriate var s are added by CoffeeScript when you compile. As in shell scripts, the hash sign (# ) starts a comment.
Numeric sequences can be written concisely as a range. For example, [1..10] stands for numbers 1 to 10. You can use this elegant notation in CoffeeScript to cut out parts from an array. Listing 3 shows an example: The two dots specify inclusive arrays; that is, [1..3] adds elements 1 through 3 to the new array. Three dots, [1...3] , specify exclusive arrays, in that the final element is excluded (e.g., in Listing 3, BMW ).
Listing 3: Manipulating Arrays
01 autos = ["VW", "Mercedes", "Ferrari", "BMW"] 02 03 racecars = autos[1..4] 04 formula1 = autos[1...4] 05 06 alert racecars 07 alert formula1
Functional
When declaring functions and making function calls, you can leave out the function keyword and brackets. Return values follow an arrow -> , and you can supply default values for arguments. For example,
console.log faculty x
is translated by CoffeeScript to the equivalent JavaScript,
console.log(faculty(x))
and
square = (x = 2) -> x * x
is converted into the JavaScript shown in Listing 4.
Listing 4: Compiled square Function
01 var square; 02 03 square = function(x) { 04 if (x == null) { 05 x = 2; 06 } 07 return x * x; 08 };
In CoffeeScript, you can pass any number of arguments to a function. For example, for the code
a = (x, y, rest...) -> alert rest
you have to call function a with at least two arguments. The first two values are stored in the variables x and y . All other values passed in end up in the rest array. The three dots (splats) show that the caller can pass in an arbitrary number of values. The
a 1,2,3,4,5,6
statement thus would output 3,4,5,6 .
If you want to swap the contents of two variables, a and b , you normally need a third variable to cache them. But CoffeeScript uses a shorter notation,
[a, b] = [b, a]
which assigns the contents of the variables on the right to the variables on the left. You can even use an arbitrary array on the right side. This concept is known as a “destructuring assignment.” The notation also lets you write functions that can return multiple values. In Listing 5, the address function is just an array of values that CoffeeScript dumps into variables name , street , and city . The resulting JavaScript code is shown in Figure 3.
Listing 5: Destructuring Assignment
01 address = () -> ["john", "Doe Street", "Anytown"] 02 [name, street, city] = address() 03 alert name
Packing Station
The do keyword executes a function immediately:
x = 3 square = do (x) -> x * x alert square
Listing 6 shows the JavaScript translation: The square variable does not contain a function that computes x * x , but the result 9 .
Listing 6: JavaScript square Code
01 var square, x; 02 x = 3; 03 04 square = (function(x) { 05 return x * x; 06 })(x); 07 08 alert(square);
For this to work, do first wraps the x * x function in an empty function; then, it forwards all arguments before starting the resulting construct. You can use this approach to create closures. To avoid running into problems when cooperating with existing JavaScript code, CoffeeScript packages the entire code once again in an anonymous function (function(){ ... })(); , which prevents the variables from being globally visible. If you want to provide variables outside of CoffeeScript, you must attach them as properties to a global object (e.g., window ).
Under Control
The if statement doesn’t need parentheses or curly brackets:
if <a> then <b> else <c>
In addition to the logical operators known in JavaScript such as && and || , CoffeeScript also offers the more readable and , or , is , and not operators. Incidentally, CoffeeScript always compiles == to === and != to !== . Also, programmers can use a lean Postfix notation in one-liners; for example, the CoffeeScript line:
ps = 230 if bmw or mercedes
in JavaScript, is:
var ps; if (bmw || mercedes) { ps = 230; }
As in Python, you can string comparison operators together and quickly test for an acceptable range:
if (15 > temperature < 30) then [...]
To determine whether a variable exists, CoffeeScript offers the question mark operator, which returns true if the variable already exists and is also not zero .
if x? then x = 1
You can use this operator to “absorb” null pointers in consecutive calls:
autono = open.garage?().auto?.licenseplate()
If an object (e.g., auto ) or a function (e.g., open.garage() ) does not exist, the whole expression returns undefined ; there is no type error.
Case distinctions can be replaced in JavaScript with the compact switch , but CoffeeScript additionally does away with brackets, break s, and the default case (Listing 7).
Listing 7: CoffeeScript switch
01 language = "US" 02 switch language 03 when "EN" then alert "Hello!" 04 when "DE", "AT" then alert "Tag!" 05 else alert "Unknown language"
Another Round
CoffeeScript replaces the for loops in JavaScript with for [...] in . The line
alert auto for auto in ["bmw", "vw", "vauxhall"]
is compiled in Listing 8. As shown, the program iterates through the array of automobile names and pushes the current name into the car variable, which it in turn passes to the alert function.
Listing 8: Compiled for Loop
01 var auto, _i, _len, _ref; 02 03 _ref = ['bmw', 'vw', 'vauxhall']; 04 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 05 auto = _ref[_i]; 06 alert(auto); 07 }
Additionally, for [...] in lets you create comprehensions — that is, functions that generate a new list from an existing one. The line
squares = (x*x for x in [1..10] by 2)
iterates through numbers 1 to 10 in steps of 2 (by 2 ), squares (x*x ) each of the numbers 1, 3, 5, 7, 9, and stores the results in the squares array:
The results output by alert squares would be 1,9,25,49,81 . Alternatively, you can iterate through the properties of objects. The lines in Listing 9 output BMW is blue, VW is red . Listing 9 also shows another notation borrowed from Ruby: Within the string, CoffeeScript replaces #{color} with the content of the color variable and does the same for brand . With this approach, you can compile more complex strings.
Listing 9: Iteration against Properties
01 autos = BMW: "blue", VW: "red" 02 03 data = for brand, color of cars 04 " #{brand} is #{color}" 05 06 alert data
Although the legacy for loop from JavaScript does not exist in CoffeeScript, at least in the current version, there is a counterpart for the while loop. If it consists only of one line, you can use Postfix notation. In the following example, while would call accelerate() as long as speed is less than maximum :
accelerate() while speed < maximum
In CoffeeScript, you can also use the while loop as an expression that returns an array with the results from each iteration of the loop:
count = 10 countdown = while count -= 1 "#{count} bottles of beer on the wall" alert countdown
Here, while iterates through numbers 9 to 1 and dumps them into a string. All strings generated this way end up in the countdown array. Although this code returns a value, it does not contain a return .
CoffeeScript views statements as expressions, and functions always automatically return their last values — assuming that you do not add an explicit return . Programmers can also use until instead of while not and loop instead of while true .
The CoffeeScript programmer also can make variable assignments within an expression, which leads to funny lines like:
= seven (five = 5) + (two = 2)
Additionally, some statements in JavaScript, such as break and continue , do not have CoffeeScript equivalents. If you use such statements, they are not converted, and the compiler simply adds them to the JavaScript.
Inheritance
Thanks to the class keyword, classes can be implemented in a more compact way — in particular for better readability in CoffeeScript. In the example in Listing 10, the dot class has a constructor that is passed two arguments.
Listing 10: Classes and Inheritance
01 class dot 02 constructor: (@x, @y) -> 03 04 draw: -> 05 alert "Position: " + @x + ", " + @y 06 07 class rectangle extends dot 08 constructor: (a, b, @width) -> 09 super a,b 10 11 draw: -> 12 alert "Width:" " + @width 13 super 14 15 shape = new rectangle 1,2,3 16 shape.draw()
The @ in the constructor ensures that CoffeeScript directly assigns the values passed in to attributes named x and y . The body of the constructor can thus remain empty (a blank line follows the arrow).
As a general rule, the notation @x , on which the draw method relies, is shorthand for this.x . The rectangle class is derived from dot . super calls the corresponding method in the parent class — super in the draw method thus calls draw in the dot class. Finally, the program produces a new rectangle and calls its draw method. Incidentally, constructors use their class names; here, shape.constructor.name is thus rectangle .
In JavaScript code, this always refers to the object to which the currently running function is bound, which leads to problems when you use event handlers and callback functions. When you call a callback function, this refers to precisely the DOM element that generated the event. As a consequence, programmers have to do without this in callback functions — or use a double-arrow (=> ) in CoffeeScript — so that this points to the object in which the callback function is defined (Listing 11).
Listing 11: Double-Arrow Operator
01 shopping = (customer, product) -> 02 @customer= customer 03 @product = product 04 05 $('.shopping_cart').bind 'click', (event) => 06 @customer.buys @product
Cake
Along with the compiler and interpreter, you will find the rudimentary cake build tool on your disk after installing CoffeeScript. Like Make, it evaluates a control file named Cakefile that defines one or multiple tasks. The Cakefile itself is a CoffeeScript script; thus, you can store code in it, like that shown in Listing 12.
Listing 12: Cakefile
01 cp = require 'child_process' 02 03 build = (callback) -> 04 coffee = cp.spawn 'coffee', ['-c', 'src'] 05 06 task 'build', 'Build below src/', -> 07 build()
The Cakefile first binds a helper object and then defines a build function, which tells the coffee compiler to compile all the CoffeeScript files in the src subdirectory. Next, it defines a task named build , which simply calls the build() function. To trigger this task, just call cake build in the directory with the Cakefile. Using cake without parameters only shows the existing tasks and their descriptions; in this case, Build below src/ .
Conclusions
The creators of CoffeeScript do not make promises they cannot keep: CoffeeScript code is more readable in many parts and surprisingly compact compared with JavaScript, but sometimes it’s too compact. Thus, it takes some practice before you can see at a glance the meaning of a statement like:
compute x, (i) -> square i
Fortunately, the ingenious CoffeeScript console, which compiles code to JavaScript as it’s typed, can help you comprehend what calls what.
Dropbox’s move to CoffeeScript proves that the new language is fit for daily business. A list of other CoffeeScript applications is provided by the project’s wiki. The free book Smooth CoffeeScript provides more detailed information about the language and syntax, and you can read The Little Book on CoffeeScript free of charge in an older, but still largely valid edition on the web. The officially available manual on the CoffeeScript homepage is terse, but aimed at JavaScript programmers — and these are precisely the people who should give CoffeeScript a try because of its many advantages.