CoffeeScript is something that I’ve watched for a very long time and my first attempt at using it in a project was early in its development and while I wanted to commit patches, reading some of the conversation around the purpose and overall goals made me realize that I simply see JavaScript differently than the author (Jeremy Ashkenas, whom I do not know but would LOVE to have a drink with (alcoholic drink or otherwise) and pick his brain, if anyone knows him and would kindly introduce us at a conference or something :). I’m buying, btw!). Different is wonderful and should be celebrated; encourage! Which is why I am loathe to write an article like this but after being mis-represented on twitter for the ump-tenth time, I decided it would be in my best interest to attempt to articulate my perspective first hand.
CoffeeScript makes a wonderful assertion of referential transparency in it’s scoping rules. By eliminating the “var” keyword and wrapping the entire context in a closure, it’s impossible to unwillingly scope variables globally nor can you augment outer scopes in an inner context through assignment which is fantastic. So far I’m loving this! Throw in all the syntactical sugar, iterators, and list comprehensions and hot damn if I didn’t think our brilliant friend was trying to “haskell-ize” JavaScript! Until I saw the class keyword. In this, my heart sunk. Then I saw a differentiation between “->” and “=>” in which the very notion of the scoping rules being encapsulated by the language seemed to be leaking back into the developer’s lap. Finally, the back tick (`) to interpret something as JavaScript inline. These 3 items are significant concessions on the overall vision that give me pause and I begin to ponder “In what context, would I need a back tick?” or “If classes ape their statically typed brethren of Java, then, idiomatically, is the idea that I should augment them by reference? and if so, is it even possible to do so given the implementation of var without the overhead of accessor methods? And if so, doesn’t that just kill all the cool stuff that came before it?”
All of these questions came up in the first project I used Coffee in. It’s not the that Object modeling metaphor used in CoffeeScript is bad, it’s just overly object-oriented and builds on OO style inheritance. JavaScript has a built in mechanism for attaching functions to any instance, like composition aka “mix-ins”, which can lead to high degrees of function reuse outside of any domain logic that may be encapsulated by a class to begin with. JavaScript’s Prototypical inheritance model is so simply in it’s implementation that it actively discourages large object graphs from being constructed through traditional OO methods. Yet CoffeeScript is engineered to dismiss this compositional simplicity in exchange for a more complex albeit more familiar OO style inheritance model. This, in my opinion, makes CoffeeScript less transparent that JavaScript, increases the complexity and introduces an amazing amount of generated JavaScript code into your runtime interpreter that contains little value over JavaScript and no compile time optimizations (closures don’t count, scoping doesn’t count). This means that CoffeeScript is not a language with it’s own identity and purpose but rather a bridge to another one. CoffeeScript classes have attributes and properties that are encouraged to be mutated by reference so a language contrivance was devised (@) to allow OO style encapsulation while still trying to provide Functional referential transparency in anonymous contexts… wrap your head around that. I can’t. {} is an object, any object, in JavaScript. What good does it to force it into an “Animal” type that extends another and another and so on? So that I can know that {name:”Leon”} can be safely mutated in it’s scope but not outside of it? CoffeeScript’s Class system and inheritance model adds un-needed complexity in a way that I’ve yet to comprehend the inspirations for. I’m going to scope the rest of my article to deconstructing it and leave some of the other issues for another article.
Ironically, if the idea was to mix-in existing functionality via functions, Jeremy wrote a much more simple and elegant version of this in 6 lines of code for underscore.js (yes, he’s the author of both!) in the extend method; extend is simple, idiomatic, and encourages the construction of tiny and discrete units of code that can become polymorphic as the need arises. Here’s the extend function in all it’s glory…
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (source[prop] !== void 0) obj[prop] = source[prop];
}
});
return obj;
};
Right?! RIGHT! It’s awesome! that means I can compose my objects (as late as runtime!) from existing objects, objects that don’t exist and objects I don’t even know exist yet. Each composed to be as simple or complex as the domain allows, without the increased overhead of the OO object model of CoffeeScript’s “class” implementation. I can give functions to objects, properties, anything my heart desires, whenever I want.
I would love to juxtapose the implementation of the class based inheritance in Coffee to the simple extend in _.js but the comparison is orthogonal and unfair. Instead, I’ll compose methods onto a tiny object from the class example in the CoffeeScript docs. First, let’s examine the output of the Class results from this CoffeeScript:
class Animal
constructor: (@name) ->
move: (meters) ->
alert @name + " moved #{meters}m."
class Snake extends Animal
move: ->
alert "Slithering..."
super 5
class Horse extends Animal
move: ->
alert "Galloping..."
super 45
sam = new Snake "Sammy the Python"
tom = new Horse "Tommy the Palomino"
sam.move()
tom.move()
The generated JS:
var Animal, Horse, Snake, sam, tom;
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor;
child.__super__ = parent.prototype;
return child;
};
Animal = (function() {
function Animal(name) {
this.name = name;
}
Animal.prototype.move = function(meters) {
return alert(this.name + (" moved " + meters + "m."));
};
return Animal;
})();
Snake = (function() {
__extends(Snake, Animal);
function Snake() {
Snake.__super__.constructor.apply(this, arguments);
}
Snake.prototype.move = function() {
alert("Slithering...");
return Snake.__super__.move.call(this, 5);
};
return Snake;
})();
Horse = (function() {
__extends(Horse, Animal);
function Horse() {
Horse.__super__.constructor.apply(this, arguments);
}
Horse.prototype.move = function() {
alert("Galloping...");
return Horse.__super__.move.call(this, 45);
};
return Horse;
})();
sam = new Snake("Sammy the Python");
tom = new Horse("Tommy the Palomino");
sam.move();
tom.move();
Now here’s the same re-written in pure JavaScript.
function move(aDistanceOf){
var it = this.name || "It";
if(arguments[1]){ alert(arguments[1]); }
return it + " moved " + aDistanceOf + " meters.";
}
var snake = { name: "Sammy" };
var horse = { name: "Tommy" };
move.call(snake, 5, "Slitering");
move.call(horse, 45, "Galloping");
That’s it. Now, I know what you’re thinking, “Leon, please, the Coffee example is contrived and doesn’t represent real world domains.” You’re right and so is mine as a result. “But you also didn’t create real domain models.” Also right, because I didn’t have do, nor would I ever want to. Therein lies my point. Sometimes a struct is a struct and not an class. The difference that I’m desperately trying to convey in this overly simple context is that by leveraging FP over OO one can provide not only a more dependable abstraction but when combined with JavaScript’s inherent flexibility as a language, we can achieve true referential transparency and polymorphism. As my domain continues to increase in complexity I’m not concerned about constantly redefining my object graph, instead, I pass them through the move function as I would any valid javascript object to achieve my end goal. When I encounter a task that move does not make sense for, I can create a different function, add additional arguments (for say, callbacks) or refactor this one (be sure to run those tests!)
In terms of pragmatic interface design, I’m not impressed by the fact that I have to think of the “move” function as only pertaining to that model’s object graph. It’s an abstraction that is bound (and encouraged) to leak. This is a fundamental difference between OO and FP and the crux of my frustration with CoffeeScript. FP encourages building small functions that do not mutate state and can be bound together as needed to perform a task whereas the OO philosophy is to encapsulate and achieve polymorphism through interface or contract. The more dynamic your context the more OO’s encapsulation paradigm, as seen in the inheritance model, leaks and defies it’s 3rd and most important tenant: Polymorphism. Even these 2 contrived examples above, if I look at the majority of CoffeeScript I’ve seen written at EdgeCase (including my own… guilty.) there has become this Java-esque DTO on the client side that takes a pure, evaluated, ready to go json object that is sent back from the server, xhr style, and translates it into a “Snake or Horse” instance. for what? This is where Jeremy’s claims to “just as fast or faster as the JavaScript you’d write” fall down. The overhead encourages more complexity, more computation and ultimately more indirection.
CoffeeScript sees the world through OO’s eyes. When I see JavaScript, I find beauty in it’s ability to be a dynamically typed Functional programming language. (that’s your TL;DR;, friends.)
This doesn’t mean CoffeeScript is BAD, poorly written, without merit. In fact, I encourage you all to check it out and decide for yourselves. I think you’ll find a clean, well crafted lexer and a sweet tokenizer written in JavaScript. That’s a pretty cool thing in itself; something worth celebrating. I simply find that outside of an academic context I don’t prefer it over JavaScript. JavaScript’s function keyword and curly brackets aren’t “ugly” to me, they are useful indicators of code smells. Do you have too many of them in a row? Time to break down your functions. This is not a problem with JavaScript, this is a problem with developers. CoffeeScript’s encouragement of OO patterns over Functional ones seems, to me, misguided in it’s current form which is not a compile-time machine or VM optimized language alternative to JavaScript but a bridge that seeks to help you write better JavaScript at the expense of gaining the wisdom of what that even means.
If you’re a fan of OO you’re going to love CoffeeScript from day one, guaranteed. If you’ve never tried to see the world through Functional glasses then I assert that you’re going to be missing out on a few beautiful vistas. Even if you go back to OO, give your solution a try in a functional style before turing JavaScript into Ruby or Python just because it feels more comfortable. If you can’t do it on this project, try it at home, or better yet, learn Erlang or Haskel or F# or Clojure and try to see the world that JavaScript sees before you turn it into something it’s not.
I hope you enjoyed this article. It was painful to write because I want to see more wonderful things from authors like Jeremy (seriously, _.js is genius.). While CoffeeScript is not for me, it’s obvious that many others find value in it and that’s really all that matters.
Jakou, že to má výhodu? Stačí se podívat na skutečné funkcionální jazyky, tedy např. Common Lisp nebo Clojure. CLisp má...