To me the most convenient thing about it is the auto-declaring variables and proper loops. Whether the sugar is worth it or not comes down to taste. I used Python as my primary language from 2001-2007 before switching to Javascript/Coffeescript full time so I'll provide a bit of commentary.
> Arrays could use a beard or the varied language syntax is going to cause ingrown pains
The whitespace magic only causes me a problem in a few situations.
Watch indent on wrapped if statement:
if foo is true or bar is false or
baz is null
doSomething() #Error
if foo is true or bar is false or
baz is null
doSomething() #ok
Explicitly returning objects is awkward:
foo: ->
if not ok then return
#lots of code here
return
x: 1 #ha ha
y: 2
foo: ->
if not ok then return
#lots of code here
return {
x: 1 #ok but ugly
y: 2}
I always want to use implicit call parens with property chaining but that doesn't work:
$('.doAllTheThings').filter 'span'
.data 'foo', true
.wrap '<div/>'
#produces filter('span'.data('foo' ...
$('.doAllTheThings').filter('span')
.data('foo', true)
.wrap('<div/>')
The result of assigning a comprehension threw me off for a while because I was thinking Python list comprehension instead of postfix loop:
foo = x + 1 for x in array
foo = (x + 1 for x in array) #what I expected
(foo = x + 1) for x in array #what happens
for x in array #same thing written the other way
foo = x + 1
Finally, the whitespace magic working well tends to depend on object/functional arguments coming last in the call list with the functional arg coming after the object.
These annoyances are relatively rare and are the sort of thing you just get used to if you use the language heavily. The upside is that for an appropriately designed API (the options object argument last convention in JS makes this fairly common), the object literal magic gives a feel like python kwargs:
animatedShow node, duration: 200, onComplete: -> fire 'doThing'
You can also do DSLish things fairly easily. I'm a fairly heavy YUI user. Compare:
YUI.add('myCoolModule', function(Y, name) {
Y.CoolModel = Y.Base.create('coolModel', Y.Model, [], {
aMethod: function(){return this.get('foo') === 1}
},{
ATTRS: {
foo: {value: 1},
bar: {value: 2, readOnly: true}
}
})
}, '1.0.0', { requires: ['app', 'model'] });
to:
YUI.module 'myCoolModule',
version: '1.0.0'
requires: ['app', 'model']
(Y) ->
Y.CoolModel = CoolModel
class CoolModel extends Y.Model
@ATTRS:
foo:
value: 1
bar:
value: 2
readOnly: true
aMethod: -> @get('foo') is 1
It requires a one word patch on Coffeescript (superclass name in generated output) and the addition of a tiny YUI.module to YUI.add shim but I think the result is a lot cleaner.
> It's also bizarre from a Python background
Python's auto-concatenation of runs of string literals is convenient but I don't know of another language that does the same thing.
> if foo.bar? then console.log foo.bar
The ? is an explicit existence check, while doing `if(foo.bar)` will fail if bar is an empty string or zero. I tend to write existence checks using the postfix form:
console.log foo.bar if foo.bar
This is a personal style that indicates that the normal control flow is to log but there's a simple guard on the operation. I'd use the if...then form if I expected the test to fail a significant fraction of the time (i.e. it's a branch instead of a guard). My other quirk is that I use &&/|| only for short circuiting but and/or only for boolean logic.