CoffeeScript: "A little language that compiles into JavaScript"
By Steve Molitor, OCI Software Engineer, and Mark Volkmann, OCI Partner and Software Engineer
November 2011
The Future of Computing
Computing Eras
At the Strange Loop conference in St. Louis last month, Allen Wirfs-Brock gave an insightful presentation on the future of computing called "Post-PC Computing" is not a Vision. To Wirfs-Brock, the history of computing can be divided into different eras. Each era has a primary platform, social focus, and programming language. In the mainframe era, the social focus was on corporations and other large organizations like universities, and the primary programming languages were Fortran and Cobol. In the PC era the focus was on empowering individual users, and the primary programming language was C.
Transitional Technologies
Transitional technologies stradle two eras. Some transitional technologies die out as the new era picks up steam. Other transitional technologies adapt and live on in the new era. For example, at the end of the mainframe era, Dennis Ritchie created the C programming language at the end of mainframe era on a minicomputer. Minicomputers eventually died out in the PC era. But because it was portable and not tied to any particular minicoputer architecture, the C programming language moved beyond its microcomputer roots and became the dominant language of the PC era. Eventually other C based languages such as Java and C# also arose. But C set the tone for programming language syntax and semantics in the PC era.
The Next Computing Era - Ambient Computing
According to Wirfs-Brock, we are entering the third computing era, the era of ambient computing. In this era, computers fade into the background and just become part of the general ambience. Computing devices will be everywhere - in our phones, cars, TVs, etc. They will provide ubiquitous access to information and the 'network', but in unobtrusive ways that augment ordinary tasks people perform and connect them to the web of information.
JavaScript - the Next Major Language (or Platform?)
To Wirfs-Brock, web browsers are a transitional technology between the PC and ambient computing eras. On newer devices such as smart phones and tablets, users prefer to access information on dedicated 'apps' that do away with the browser chrome - no address bar, back button, etc. However, increasingly many developers are writing apps using the underlying browser platform technology, using HTML, CSS, and of course JavaScript. In fact the only practical way to write cross platform apps for various 'ambient' devices like iPhones, iPads, Android devices, etc, is to write them in JavaScript. Wirfs-Brock believes the web platform in general, minus its browser 'chrome', and JavaScript in particular, will come to define development in the ambient computing era. JavaScript is not the best language ever written, but it may be good enough. Furthermore it is emerging as the fastest dynamic programming language due to the performance arms race among browser makers. We are even seeing the rise of highly scalable server side JavaScript platforms such as Node.js that let developers write entire systems, client and server, in JavaScript. JavaScript is everywhere.
Languages that Compile to JavaScript
If JavaScript becomes the dominant platform of the ambient computing era, does that mean we will all be coding in JavaScript? That wouldn't be such a bad thing. JavaScript is a good language and you can write elegant programs in it. Still, it does have its warts. We are starting to see languages that compile to JavaScript, such as ClojureScript, Google's new language Dart, and many more: List of Languages that Compile to JavaScript. CoffeeScript is one such language.
Why CoffeeScript
Unlike, say ClojureScript, CoffeeScript is not a radical departure from JavaScript. If you are already familar with and like JavaScript, you will pick up CoffeeScript quickly. In terms of core underlying semantics, CoffeeScript is essentially JavaScript. Syntactically however, CoffeeScript looks different. CoffeeScript is JavaScript with a prettier skin. But when it comes to programming, looks matter. Sure, you can say 'lipstick on a pig' and all that, but we spend most of our time reading and maintaining code, not writing it from scratch. That means readability is very important. CoffeeScript aims to provide a more readable, and thus more maintainable, alternative to JavaScript. The CoffeeScript compiler produces readable JavaScript that passes JavaScript Lint.
CoffeeScript History
CoffeeScript was created by Jeremy Ashkenas and first released on December 25, 2009. The original compiler was written in Ruby. The compiler was rewritten in CoffeeScript and released in March 2010. For its syntax CoffeeScript borrows heavily from Ruby, Python, and of course JavaScript. If you are not familar with JavaScript but have used an object-oriented scripting language like Ruby, Python or Groovy you will also pick up CoffeeScript quickly.
Significant Whitespace
One of the first things that will jump out at you in the CoffeeScript code examples below is the lack of curly braces, semicolons or similar noise. Like Python, CoffeeScript uses significant whitespace to delimit blocks of code. While surpising at first, this really makes the code more readable.
Debugging
A challenge when developing in CoffeeScript is debugging. You can use JavaScript debuggers such as Firebug on the generated JavaScript code that the CoffeeScript compiler generates. The generated JavaScript is fairly readable, but still it's not the code you wrote. Line numbers in runtime stack traces refer to the JavaScript line numbers, not the original CoffeeScript source. You can always resort to inserting 'console.log' statements to print debugging information in your code, but this is not pretty. Fortunately this issue is expected to improve in the future when the browser makers add 'source map' support to allow debugging of languages that compile to JavaScript: see Mozilla, WebKit To Support Debugging Minified JS As Well As CoffeeScript and other JS Languages.
Installation and Getting Started
*Nix Systems and OS X
The CoffeeScript compiler is a Node.js program. On Unix based systems including OS X, you need to install Node.js, npm (the Node Package Manager), and then use npm to install CoffeeScript:
npm install -g coffee-script
Windows
To install the current version of Node.js on Windows you will need to use Cygwin, and install as above. (The upcoming version of Node.js will run natively on Windows.) But if you just want to play around with CoffeeScript you can use the CoffeeScript-Compiler-for-Windows.
Try in Your Browser
The easiest way to get started with CoffeeScript is to Try CoffeeScript in your browser. This web page lets you type in CoffeeScript code on the left side of the screen, and displays the generated JavaScript on the right side. Clicking the 'Run' button will run the code. You can try the examples in this article by pasting them into the Try CoffeeScript page. You will need to change console.log
statements into alert
statements to see results.
CoffeeScript Functions and Variables
Here is a simple CoffeeScript anonymous function definition:
-> 'Hello caffinated world!'
The '->' operator ('arrow operator') marks the beginning of a function definition. Similar to Ruby, the last line of a function is its return value - there is no need for an explict 'return' statement (although you can use one if you want to return early). But how do we refer to this function so that we can call it? One way is to assign it to a variable:
hello = -> 'Hello caffinated world!'
console.log hello()
The first line defines the function and assigns it to the variable 'hello'. The next line calls two functions, passing the result of calling the hello function to the console.log function. Notice the lack of parenthesis around the call to console
. Parenthesis are optional in CoffeScript - except when leaving them out would cause CoffeeScript to do the wrong thing. We could write the second line more verbosely as:
hello = -> 'Hello caffinated world!'
console.log(hello())
However, we do need the parenthesis for the call to the hello function. Functions are first class objects in CoffeeScript (just like JavaScript). Without the parenthesis console.log
would print the toString()
value of the hello
function object without actually calling the function - not what we want. To call a function with no arguments you must use parenthesis. (This is different from Ruby where a no argument method can be invoked with or without parenthesis. Functions are not first class objects in Ruby so there is no ambiguity. In general it's more powerful and flexible to treat functions as first class objects, although in the case of calling no argument functions it is a bit more verbose.)
Also note the lack of semicolons and or other statement terminators.
Function Arguments, String Interpolation, Comments
Let's take a look at a function that takes arguments:
order = (amount, drink='coffee') ->
"I want #{amount} cups of #{drink}"
console.log order(2) # prints "I want 2 cups of coffee"
console.log order(3, 'tea') # prints "I want 3 cups of tea"
Function that parameters use the form '-> (arg1, arg2)'. Default values for function parameters can be specified as in the "drink=coffee" example above. Stealing again from Ruby, double quoted string literals support interpolation. We could write the second line of the above function definition more verbosely like this:
order = (amount, drink='coffee') ->
"I want " + amount " cups of " + drink
console.log order(2) # prints "I want 2 cups of coffee"
console.log order(3, 'tea') # prints "I want 3 cups of tea"
Comments begin the '#' character (again, similar to Ruby). Multi-line comments are delimited by '###':
###
This is
a multiline
comment
###
Splats
Splats ...
can be used to define functions that accept variable number of arguments. When used in a function definition splats convert discrete arguments passed to the function into an array:
order = (drink, extras...) ->
"#{drink} with #{extras.join(' and ')}"
console.log order 'coffee', 'cream', 'sugar', 'nutmeg'
# prints 'coffee with cream and sugar and nutmeg'
Only one parameter can use splats, but it doesn't have to be the last one:
order = (drink, extras..., adverb) ->
"#{drink} with #{extras.join(' and ')}, #{adverb}!"
console.log order 'coffee', 'cream', 'sugar', 'nutmeg', 'please'
# prints 'coffee with cream and sugar and nutmeg, please!'
Conditionals
CoffeeScript supports if/else statements similar to JavaScript, but without the parenthesis and curly braces:
if x is 0
console.log "oh noes it's zero"
else
console.log "not zero"
CoffeeScript does not have a special ternary operator. Instead you use a normal if...else
statement on one line:
answer = if x is 0 then 0 else 42
This works because if statements, like most things in CoffeeScript, are expressions that return a value. We could also write the above example in long form like this:
answer =
if x is 0
0
else
42
Statement Modifiers
You can also use postfix conditionals where the test goes at the end of the line, similar to Ruby:
console.log "oh noes it's zero" if x is 0
CoffeeScript supports postfix versions of if
, unless
, while
and until
:
n=3
console.log n if n > 0 # 3
console.log n unless n > 5 # 3
console.log(n--) until n is 0 # 3 2 1
console.log(n++) while n < 3 # 0 1 2
Operators
Because the JavaScript ==
and !=
operators perform undesirable conversions, are not transitive, have different meanings than in other languages, and are in general not recommended, CoffeeScript does not use them. The ==
and !=
operators in CoffeeScript compile to the JavaScript===
and !==
operators, which are safer and saner. CoffeeScript also supports is
and isnt
as more English like equivalents (no pun intended). We saw the is
operator above. Similarly, you can use and
, or
and not
as equivalents for <<
, ||
and !
.
The and=
and or=
operators can be used to conditionally assign to a variable:
answer or= 42 # answer is 42 unless we already have an answer
answer and= 42 # answer is 42 only if we already have an answer
The or=
operator is probably more useful than and=
.
Here's a list of all the CoffeeScript operators along with their JavaScript equivalents:
CoffeeScript Operators List
CoffeeScript | JavaScript |
---|---|
is | === |
isnt | !== |
not | ! |
and | && |
or | || |
true, yes, on | true |
false, no, off | false |
@, this | this |
of | in |
in | no JS equivalent |
? | no JS equivalent |
The in
operator tests whether a given value is in an array. The of
operator tests whether a given property is in an object. The @
is used to refer to instance variables (see section on classes below).
Avoiding Null Checks with the Existential Operator - '?'
The '?' operator, also known as the 'existential operator', can be used to write expressions that succeed even when the value of a variable is null or undefined, a function returns null, or an object doesn’t have a given method. In the example below the person object has a null address, so calling person.address.city
would throw a runtime error. However, putting the '?'
operator after 'address' makes it null safe:
person = { name: 'Albert Pujols', address: null }
console.log person.address?.city # undefined
Ranges
CoffeeScript borrows Ruby's range syntax to create arrays of consecutive numbers:
# inclusive:
[1..5] # [ 1, 2, 3, 4, 5 ]
# exclusive:
[1...5] # [ 1, 2, 3, 4 ]
# backwards
[5..1] # [ 5, 4, 3, 2, 1 ]
Ranges can be used to 'slice' values from arrays and strings:
s = 'abcdef'
s[2..s.length] # 'cdef'
Loops and Comprehensions
CoffeeScript comprehensions are used to iterate over arrays, objects and ranges. Unlike the JavaScript for loop, comprehensions are expressions that can be returned and assigned. Here's a comprehension that loops over an array:
batters = ['Rafael', 'Jon', 'Albert', 'Matt', 'Lance']
for batter in batters
console.log "Get a hit #{batter}!"
Or:
console.log batter for batter in batters
When iteration over numbers you can use a range, optionally using by
to specify the increment:
odds = (n for n in [1..10] by 2)
Comprehensions can be used in place of a map
function to convert collection values:
doubled = (n * 2 for n in [1, 2, 3]) # [ 2, 4, 6 ]
Use the when
keyword with comprehensions to perform a filter or reject operation:
evens = (n for n in [1, 2, 3, 4] when n % 2 is 0) # [ 2, 4 ]
Pattern Matching
Pattern matching, also known as 'destructuring', Provides an easy way to extract values from an array or object:
values = ['St. Louis', 'Cardinals', 'baseball']
[city, team, sport] = values
# Prints 'The Cardinals play baseball in St. Louis.'
console.log "The #{team} play #{sport} in #{city}."
You can use destructuring to swap two values:
x = 1
y = 2
[x, y] = [y, x]
console.log "x=#{x}, y=#{y}"
Objects
Object literals can be defined using an indented, JSON-like syntax:
person =
firstName: 'Albert',
lastName: 'Pujols',
fullName: ->
@firstName + ' ' + @lastName
address:
street: '25 Main Street'
city: 'Cooperstown'
state: 'NY'
console.log person.fullName() # 'Albert Pujols'
console.log person.address.city # 'Cooperstown'
Object literal values can be simple values (firstName: 'Albert'
), or function definitions likefullName
above. The @
operator refers to instance variable or methods, similar to this
in JavaScript. In the example above @firstName
is the same as this.firstName
in JavaScript.
Simulating Named Parameters with Object Literals
CoffeeScript does not have a specific syntax for passing named parameters to functions, but we can simulate them using object literals:
f = (params) ->
console.log params.name if params.name
f color: 'yellow', name: 'Mark', number: 19
f
color: 'yellow'
name: 'Mark'
number: 19
Classes
Classes definitions in CoffeeScript Script are similar to object literal definitions:
class BaseballGame
# You can only have one constructor.
# When a constructor parameter name starts with '@', it is automatically
# assigned to an instance variable with the same name:
constructor: (@homeTeam, @visitingTeam) ->
@homeTeamScore = @visitingTeamScore = 0
homeTeamScored: (runs=1) ->
console.log "Yeah!"
@homeTeamScore += runs
visitingTeamScored: (runs=1) ->
console.log "Boo!"
@visitingTeamScore += runs
reportScore: ->
console.log "#{@homeTeam}: #{@homeTeamScore}, #{@visitingTeam}: #{@visitingTeamScore}"
game = new BaseballGame('Cardinals', 'Rangers')
game.visitingTeamScored() # 'Boo!'
game.homeTeamScored(4) # 'Yeah!'
game.reportScore() # 'Cardinals: 4, Rangers: 1'
Subclasses can be defined using the extends
keyword:
class CubsGame extends BaseballGame
constructor: (@homeTeam, @visitingTeam) ->
super
# assign handicap:
if @homeTeam is 'Cubs'
@homeTeamScore += 4
else
@visitingTeamScore += 4
makeError: ->
console.log "Doh!"
The super
keyword calls the super class construtor. When called with no arguments or parenthesis as above, it passes all arguments to the superclass construtor.
Conclusion
There is still more to CoffeeScript than what we've shown in this article - but not too much more. We've shown you the most commonly used 80% of CoffeeScript that you can use to start writing good programs. CoffeeScript is a small, focused and fun language that you can use to write readable and maintainable programs for any Javascript based platform.
References
- [1] CoffeeScript
http://jashkenas.github.com/coffee-script - [2] CoffeeScript Book - CoffeeScript: Accelerated JavaScript Development
http://pragprog.com/book/tbcoffee/coffeescript - [3] "Post-PC Computing" is not a Vision
https://thestrangeloop.com/sessions/post-pc-computing-is-not-a-vision