Why I'm (Finally) Switching to CoffeeScript
codelikebozo.com
codelikebozo.com
(x, y) ->
for game_piece in @game_pieces
if game_piece.is_at x, y
@held_item = game_piece
@held_item.start_dragging x, y
break
compiles to function(x, y) {
var game_piece, _i, _len, _ref, _results;
_ref = this.game_pieces;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
game_piece = _ref[_i];
if (game_piece.is_at(x, y)) {
this.held_item = game_piece;
this.held_item.start_dragging(x, y);
break;
}
}
return _results;
}
Notice that _results is initialized as an empty array... and is never modified, making it an utterly pointless return value. What's happening is that the CoffeeScript compiler has both implicit returns and list comprehensions, which means that it tries to return an array of all the values in the loop. All well and good, but the compiler doesn't try to do a list comprehension when there's a break... and yet it creates the frivolous _results variable anyway.Obviously I'm a fan of CoffeeScript; this is a minor bug and I expect it to be fixed. But let's be fair in making the case for it. Well-written JavaScript will always look better than CoffeeScript output; it just takes a lot more work.
(x, y) ->
for game_piece in @game_pieces
if game_piece.is_at x, y
@held_item = game_piece
@held_item.start_dragging x, y
break
returnHaving a concise syntax doesn't relieve you of the duty to simplify your code. Both the CoffeeScript and JavaScript code can be simplified considerably, as several others have pointed out.
In fact, if you clean up the CoffeeScript code like this:
keyMoves =
37: 'pan_left'
38: 'pan_up'
39: 'pan_right'
40: 'pan_down'
187: 'zoom_in'
189: 'zoom_out'
document.onkeydown = ( event ) ->
move = keyMoves[event.keyCode]
piece[move]() for piece in game_pieces
the_screen.refresh( game_pieces )
It compiles to this JavaScript code: var keyMoves;
keyMoves = {
37: 'pan_left',
38: 'pan_up',
39: 'pan_right',
40: 'pan_down',
187: 'zoom_in',
189: 'zoom_out'
};
document.onkeydown = function(event) {
var move, piece, _i, _len;
move = keyMoves[event.keyCode];
for (_i = 0, _len = game_pieces.length; _i < _len; _i++) {
piece = game_pieces[_i];
piece[move]();
}
return the_screen.refresh(game_pieces);
};
And that's a quite reasonable piece of code - nothing like the JavaScript example in the article.Personally I would write the JavaScript version more like this (assuming I wanted a raw for loop instead of an iterator function, perhaps for speed):
keyMoves = {
37: 'pan_left',
38: 'pan_up',
39: 'pan_right',
40: 'pan_down',
187: 'zoom_in',
189: 'zoom_out'
};
document.onkeydown = function( event ) {
var move = keyMoves[event.keyCode];
for( piece, i = -1; piece = game_pieces[++i]; )
piece[move]();
the_screen.refresh( game_pieces );
};
But I certainly don't see anything very wrong with the CoffeeScript-generated JavaScript, once the original CoffeeScript code is improved.But once you are used to coffee, your code will start to look very different from pure JS code. Coffe encourages you to write even more, even smaller functions due to its lean syntax.
This new coding style that emerges (at least it began to emerge for me and a coworker) is infinitely more readable than JS, even more so when you look at a direct translation.
My takeaway from this is two things:
1) coffee is cool. Just give yourself one time to adapt. At first everyone's code in $NEWLANGUAGE looks like code in $OLDLANGUAGE until you get used to the common idioms in your new language.
2) The general syntax of a language can have a huge effect on your code style which in turn can have a huge effect on overall readability.
It's interesting how so many people profess love for CF's syntax, where I have such a dislike for it (to me, it looks like a magpie language: rather than a whole design it's cobbled from shiny pieces taken from all over the place; the way some features are "integrated" are close enough to yet far enough from the source language that it lands straight in my uncanny valley; it's drawing back from the OO/Func movement of the JS community back into procedural and statements-based something fierce; the target language leaks heavily through such things as the thin v fat arrows; and finally it draws back from things I think are genuinely better features of javascript, such as explicit scopes)
This statement is completely incorrect, the truth is actually the exact opposite.
First I would note there is no such thing as a statement in Coffeescript. Everything is an expression ... this may seem like a minor point to some, but it's actually quite a powerful difference. As with a lot of the things CS adds, It's hard to appreciate the boost in expressiveness until you start writing a lot of code with it.
If you want to talk about functional programming, expressions rather than statments count as 1 whole point out of 9 for PG's "list of things that are awesome about lisp".
Another important aspect of functional programming is lambdas, JavaScript does not have "real lambdas" because of the lack of implicit returns and the aforementioned quality.
By adding them, CS get's you much closer to real lambdas. This combined with the light syntax for functions encourages one to program in a much more functional style.
These qualities enable say, quick and easy decorators:
logged = ( f , named_f ) ->
if named_f? then [ f, name ] = [ named_f, f ] else name = "function"
# decorated function, to be returned
( args... ) ->
console.log "#{name} (#{f.length}) with #{args.length} args."
f.apply @, args
# random function, note our decorator preserces "this"
foo = logged ( x ) ->
console.log( @[x] + "" )
# random function that calls other function, our logging lets us trace the calls
# this one provides a name
bazz = logged "bazz", ( x ) ->
foo.call @, x
# test
bar = fizz: "buzz"
bazz.call bar, "fizz"
Note also how splats simplifies this operation. Destructuring and the existential operator makes the logic in the decorator function a little cleaner. String interpolation makes the logging easier.Additionally the use of implicitness really makes it shine and fit together well, rather than being mere eye-candy. There's no wrapping in parens to decorate, making it harder to mess up, easier to modify, and easier to parse visually and perhaps programatically.
I think a lot of people are deceived when it comes to CoffeeScript, they see a lot of little things which seem nice...but individually they seem pointless or minor. They are deceived on two parts: a lot of these "little things" are actually quite powerful on their own, and additionally they mesh in a way that is greater than the sum of the parts.
All these little nice things can add a powerful boost to expressiveness and once you spend some time with them it really changes how you think and write code. That's another thing I would note is that CoffeeScript is for people who want more expressiveness/find these things more expressive. If you are looking for something else, or think in a way that has an impedance mismatch with the CoffeeScript philosophy, obviously you should look elsewhere.
(If anyone's curious, this decorator makes it so every time a decorated function is called, it logs the function's name if provided, the number of defined arguments it takes, and how many it was actually passed.)
I would also note that scopes function the same in CS as they do in JS, the only difference is that in CS you cannot shadow variables from an enclosing scope, and there are no implicit globals.
In terms of OO, CS adds syntactic sugar for perhaps the most common inheritance pattern in JS.
I would argue both of those are good things, though I respect other people might think otherwise.
I dont see why he didn't use do "var game_piece , _i , len;" He doesn't need multiple i's nor lengths. The JS example is just a very poor example of coding. (in fact he could of just simplified it further down by having one loop and a variable to the method, to be executed on those pieces.)
CoffeeScript is very nice, but I want to see examples of why to use it that will actually save me time. Compared to the examples, there are better ways to code and tons of easy ways to do OO with certain frameworks/toolkits.
var keydown_action_map = {
37: 'pan_left',
38: 'pan_up',
39: 'pan_right',
40: 'pan_down',
189: 'zoom_out',
187: 'zoom_in'
}
document.addEvent('keydown', function(event) {
var action = keydown_action_map[event.keyCode];
game_pieces.each(game_piece[action], game_piece);
}); keydown_action_map =
37: 'pan_left'
38: 'pan_up'
39: 'pan_right'
40: 'pan_down'
189: 'zoom_out'
187: 'zoom_in'
document.addEvent 'keydown', (event) ->
action = keydown_action_map[event.keyCode]
game_pieces.each game_piece[action], game_piece
If you can write terse code in JS you can write it even more terse way in CS.Not exactly impressive (and coffeescript's symbolless maps read horribly badly, imo).
au BufWrite *.js mark` | silent!%s/,\(\_s*[}\]]\)/\1/g | norm``Yes.
> CS will put it back at compile time, don't worry.
May I ask why you're assuming I'm a moron, exactly?
> And it will do it in more consistent manner than many coders will manually.
I don't know who your coders are, but you should fire them.
> CS will save your ass from being bitten by IE because of stray comma.
Yep, confirmed, and you should probably fire yourself as well.
That's because it's the output of the coffeescript compiler, it's not "actual" javascript code.
Honestly, if that type of output is why people think Javascript is "hard" or "convoluted," then maybe CS is right for them. As with any tool, a proper knowledge of its use is required and anyone who would write code like that clearly doesn't really know their tool.
In the first example I would have written the original handler like this, for example. Written more concisely it doesn't present an immediate need for shortening or clarification by CS:
document.onkeydown = function(event) {
var key_to_func = {
37: "pan_left", 38: "pan_up",
39: "pan_right", 40: "pan_down",
189: "zoom_out", 187: "zoom_in"
};
for (var i in game_pieces)
game_pieces[i][key_to_func[event.keyCode]]();
return the_screen.refresh(game_pieces);
};
The class example is better but I haven't found much use for the JavaScript prototypes to build class-like constructs. Instead of using prototyping I generally just create objects and shove stuff into them. YMMV.Last example is merely a transformation of the first version. The constructs used are quite similar: it would have been nice to see CoffeeScript transform the actual paradigms used in the code. The CoffeeScript version still reads like JavaScript, just with slight syntactic sugar.
Mere syntactic sugar might actually be the core idea of CoffeeScript (instead of landing a somewhat higher-level language on top of JS) in which case the example illustrates nicely why the gains from switching from JS to CS are mostly cosmetic. You can compress your code conceptually much better if you just write JS more functionally, i.e. write a few quick functions to provide map/filter/reduce and some helpers.
Sam Stephenson, the creator of Prototype.js, has been doing all kinds of awesome things with CoffeeScript and Node.js. And there's a chapter of my book on it...
http = require 'http'
server = http.createServer (req, res) ->
res.writeHeader 200, 'Content-Type': 'text/plain'
res.write 'Hello, World!'
res.end()
server.listen 3000
console.log "Server running at http://localhost:3000/" from twisted.internet import reactor
from twisted.web import resource, server
class HelloWorldResource(resource.Resource):
isLeaf = True
def render_GET(self, request):
return "Hello, World!"
reactor.listenTCP(3000, server.Site(HelloWorldResource()))
reactor.run()Um. Node.js gives you access to the filesystem... http://nodejs.org/docs/latest/api/fs.html
And anything you can do in Node, you can do from CoffeeScript code running under Node.
As many pointed out (five proper versions of the first snippet so far), the examples are not really valid.
I do not necessarily agree with the trend of criticizing javascript of being a language that needs to be fixed. Quite frankly, it is a proper language. Coffescript introduces some practical sugar, class construct being IMHO the best plus as javascript class definitions are rather verbose. CS object notation is another neat advantage. Many other things on coffeescript appear to be concepts that for some reason are fashionable these days such as the arrow or the conditions after the statement.
I write javascript everyday and I absolutely don't feel like there are anoyances which need to be taken care of urgently. It's a language as enjoyable as most. Cofffescript does look cool and I do plan to use, at least for fun if not for any other reason. But saying that is the missing piece, or that is the long expected replacement for javascript, sounds like fanboyism.
Same reason every shitty go article out there reaches HN's front page (or reached until pretty recently), same reason /r/prog's frontpage is cyclically full of a single technology or language: it's what most denizens of the place currently have a hard-on for.