CoffeeScript is an open source project that provides a new syntax for JavaScript. I have to say that I have a lot of respect for CoffeeScript and it got a lot of things right. The “golden rule” of CoffeeScript is“It’s just JavaScript”. That means there is a straightforward equivalent to every line of CoffeeScript. Consequently, there aren’t as many issues with JavaScript interop as there are withDart (seeWhy I’m Ditching CoffeeScript by Chris Toshok for a discussion of how this isn’t true for accessor properties and a peek into the politics of open source). It also makes it easy to deal with any shortcomings in tooling, because the developer should be more comfortable switching between CoffeeScript and the compiled JavaScript, for example, when debugging in the browser without the aid of a source map. Though CoffeeScript has matured enough that a lot of those shortcomings have been resolved. Nevertheless, the assurance that you can read the compiled output is comforting. CoffeeScript programs can be very compact and require less typing. The language also protects you from many of theJavaScript land mines. For example, in CoffeeScript== compiles to the strict equality operator===, making it impossible to use the dangerous loose equality operator.
The strengths of CoffeeScript were the reason that I argued for its use on the last project I was part of. Indeed, we adopted CoffeeScript and used theMindscape Web Workbench extension for Visual Studio which compiled on save. For me, the compile on save was helpful in learning CoffeeScript because I could immediately see what the resulting JavaScript was without firing up a browser, as required by many of the other options that compile on the fly as part of an asset pipeline. If you’re seeking a similar approach you might also want to check out the popularWeb Essentials plug-in. Though I found merging the compiled JavaScript files to by annoying enough that I am now moving away from that approach. I spent 6+ months on that project learning and working in CoffeeScript. Out ofTypeScript,Dart andCoffeeScript I really can speak to the strengths and weaknesses of the last from experience. That experience has led me away from using CoffeeScript on my current project.
I quickly found that there were situations where I couldn’t predict what a given chunk of CoffeeScript would compile to or couldn’t figure out how to write the CoffeeScript for the JavaScript I wanted. The problem arises because in CoffeeScript parenthesis, curly braces and commas are often optional and white-space and indention replace them. Frequently code I thought should compile didn’t. Consider this valid code.
func5,{event:->45,val:10}Now, if the event function needs to be expanded, one would logically think it could be changed like this.
func5,{event:(e)->ife.something36else45,val:10}However, that doesn’t compile. You have to drop the comma or move it onto the next line, beforeval.
How about something likefunc1 1, func2 2, 3? Does 3 get passed to the first or second function? I still frequently forget one place where parenthesis are not optional, leading to unexpected behaviour. The statementx = f -> f looks like it should assign x to the identity function, but it doesn’t. Instead, x is assigned the result of calling f with a function of no arguments returning f. Or how about this,a + b adds a and b whilea +b calls a with the argument+b. There are lots of other examples of ambiguous and confusing syntax in CoffeeScript. For more examples, check outMy Take on CoffeeScript by Ruoyu Sun and thisGist from Tom Dale.
Ambiguous code is only one part of what makes CoffeeScript difficult to read. Everything is an expression (returns a value) and it has lots of control flow and operator aliases. All of which encourages very English sentence like code. However, that often makes the code less readable, not more. The human mind is good at understanding logic in symbols; English is not good at expression logic. As an example, consider the lineeat food for food in foods when food isnt 'chocolate' from theCoffeeScript tutorial. The declaration of what food is occurs in the middle of the line and doesn’t even look like a variable declaration. Furthermore, until you finish reading the line it isn’t clear which foods will be eaten. That code could easily be worse ifunless eat is undefined was added to the end, making the whole line conditional. One wouldn’t realize it was conditional until reading the end. Imagine if the expression before thefor had been a complex multi-line method call with logic in it. Ryan Florence digs deeper into these issues in his postA Case Against Using CoffeeScript. Suffice it to say, many of the features added to CoffeeScript with the intent of making it “readable” actually have the opposite effect.
In addition to the these confusions, CoffeeScript actually creates new mines that aren’t present in JavaScript at all. As explained by Jesse Donat inCoffeeScript’s Scoping is Madness, in CoffeeScript’s zeal for terseness and “simplicity” it has actually created a major pitfall around variable declarations. In CoffeeScript there is no variable declaration syntax equivalent tovar in JavaScript. Instead, all variables are declared in the scope they are first assigned in. This means it is easy to accidentally change the scope of a variable and not even realize it.
To see how this would happen, imagine you are in a hurry to implement the next feature. Unbeknownst to you, the following code is near the bottom of the file you are about to modify.
innocent=(nums)->lastValue=1fornuminnumsdoSomethinglastValue,numlastValue=numlastValueNow, near the top of a code file you add these lines. So that the file is now.
value=42lastValue=nullchangeValue=(newValue)->lastValue=valuevalue=newValue# many pages of code hereinnocent=(nums)->lastValue=1fornuminnumsdoSomethinglastValue,numlastValue=numlastValueDid you catch the error? Originally,lastValue was local to theinnocent function, but now it is global to the file and theinnocent function actually modifies it. We now have a bug waiting to happen when someone callsinnocent then checks the value oflastValue. Keep in mind there could be multiple screens of code between these two code segments.
Certainly, a language that is “just JavaScript” won’t be able to fix everything. Yet, despite radically altering the syntax of JavaScript and claiming to only expose the good parts of JavaScript, in some ways CoffeeScript doesn’t go far enough in fixing the issues of JavaScript. For example, the+ operator is still both numeric addition and string concatenation. That is frequently listed as one of the bad parts of JavaScript. Why no provide separate operators for the two? Likewise, thetypeof operator “is probably the biggest design flaw of JavaScript, as it is almost completely broken” according to theJavaScript Garden, but CoffeeScript brings its behaviour over unchanged. Instead CoffeeScript could have altered the meaning oftypeof to something that was more useful, for example thetypeOf() functionrecommend by Douglas Crockford.
Many developers wish that JavaScript had classes. This has led to numerous alternative ways to emulate class like functionality in JavaScript and libraries that embody those various approaches. Many of the different approaches don’t interact well with each other. There is even debate on whether to use constructors requiring thenew keyword or to make everything a factory function.CoffeeScript has aclass keyword that makes it easy to create classes. However, since “it’s just JavaScript” they are just one particular emulation of classes on top of JavaScript. Consequently, they may not play well with your library of choice. More troublesome is that they make other language features more confusing. In particular thethis keyword. When creating a class you can’t help but feel thatthis should refer to the class everywhere within the body of the class. That is what it would mean for something to be aclass. But the actual semantics ofthis are unchanged. For example, if you declare a class with methods usingthis.
classCarconstructor:(@model)->drive:()->alert"You are driving a "+this.model;delayed:()->->this.modelBecauseCar is a class, one expectsthis to always refer to the current car object, but instead it behaves like regular JavaScript.
myRide=newCar("BMW")letsDrive=myRide.driveletsDrive()# alerts "You are driving a undefined"getModel=myRide.delayed()alert("Delayed model: "+getModel())# alerts "Delayed model: undefined"Ultimately, JavaScript isn’t a class based language and classes never quite work right in it. I believe it is a mistake to push the language in that direction. For a more in-depth discussion of why this is the case, seeJavaScript Doesn’t Need Class by Ian Elliot.
I have been writing JavaScript for 8 years now, and I have never once found need to use an uber function. Thesuper idea is fairly important in the classical pattern, but it appears to be unnecessary in the prototypal and functional patterns. I now see my early attempts to support the classical model in JavaScript as a mistake.
This article isPart 4 in a6-Part Series.