Skip to content

Instantly share code, notes, and snippets.

@liaody
Created January 12, 2012 01:56
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save liaody/1598046 to your computer and use it in GitHub Desktop.
Save liaody/1598046 to your computer and use it in GitHub Desktop.
Coffeescript gotchas when migrating from javascript

Here are some of the "gotchas" I've encountered when I converted all the javascript code into coffeescript. (BTW, this is gathered while coding for YourMechanic.com, a very cool mobile auto repair service.)

Disclaimer: Coffeescript is a great productivity booster for me personally. Even with all the things outlined below, it is still way better than javascript. None of these should discourage you from jumping into it right away.

Major gotchas

(Things that generate wrong code silently)

The ?: setup is translated differently without warning.

For example, these two lines:

# Really need to convert lines like this
candy = good() ? 1 : 0
# to this before you rename the .js to .js.coffee
candy = if good() then 1 else 0

Generates code like these:

// Unexpected!
candy = (_ref = good()) != null ? _ref : {
  1: 0
};
// Expected
candy = good() ? 1 : 0;

The implicit return values

Coffeescript always returns the last expression (like in ruby). This can be fairly unexpected for javascript programmers, and easily overlooked (even with people fluent in ruby). I end up almost always terminate a void function with empty return statement. For example:

with_return = ->
  doStuff()
  return

without_return = ->
  doStuff()

get translated to this:

with_return = function() {
  doStuff();
};

without_return = function() {
  return doStuff();
};

Returning the last value may not seem like a big deal until it hits you in event handlers (last expression evaluated to false).

Need to be careful with white spaces within expressions

(completely understandable, but still):

# No space: Good
'Time:'+hour+' hours'   
# Irregular spaces: Bad
'Time:' +hour +' hours'  
# All spaced: Good again
'Time:' + hour + ' hours' 

here is what these statements get translated to:

'Time:' + hour + ' hours';

'Time:' + hour(+' hours');

'Time:' + hour + ' hours';

Potentially incorrect code with the range (../...) logic in for loops

Coffeescript range ../... works in both directioins. In case the compiler does not know which direction to take, it generates dynamic code to do both. This is usually not intened, and there is no good way to specify direction of range. For example:

Original javascript code:

var sum = 0;
for(var i=5;i<arr.length;i++)
{
  sum += arr[i];
}

Naively, I would translate it to these coffee script code:

sum = 0
for i in [5..arr.length] 
  sum += arr[i]

and be surprised to see that actual javascript generated to be this (wrong logic for arr.length less than 5)

var i, sum, _ref;

sum = 0;

for (i = 5, _ref = arr.length; 5 <= _ref ? i <= _ref : i >= _ref; 5 <= _ref ? i++ : i--) {
  sum += arr[i];
}

Even in the cases where logic is not wrong, it is still inefficient to do two compares for each iteration.

Minor Gotchas

(Things that are understandable and relatively easily fixable)

Beware when converting var my = this; to => pattern

Sometimes this is used together with my (e.g. in event handlers this used to refer to element)

In many cases I need to convert code like this:

var my = this;
$('.tab').click(function() 
{
  my.switchTab($(this).data('tab'));
})

to the code below with alternate way to get to the event target.

$('.tab').click (ev)=>
  @switchTab $(ev.target).data('tab')
  return

(Lately I would use _.bindAll and switch from => to ->, to avoid => as much as I can)

Typo of -> to => is hard to notice

(Made worse by this effect: http://www.hiddentao.com/archives/2011/11/09/coffeescript-function-binding-gotcha-when-using-cloned-spine-models/)

Typo of -> to = is hard to notice

Function b() disappears in below code. Would be a good candidate for a "coffeescript lint" tool.

class Test
  a: ->
    doStuffA()
    return
  b= ->
    doStuffB()
    return

Block comment style

Block comment style ### sometimes mysteriously interfere with "pound lines" like "#####################" in the original javascript code. Syntax highlighting saves the day here. (Why mysterious? It only treat code block as comment if number of pounds in your line satisfies (nPounds/3)%2==1)

For example

#############
doStuff1()
###############
doStuff2()
#########

Foward declarations are not straightforward in some situations.

var count;
function Show() { count += 1; ActualShow(); }

$(function()
{
  count = 0;
  Show();
  setInterval(1000, Show)
});

Naive translation wouldn't work. A dummy assignment is required to get the proper forward declaration

count = null
Show = -> 
  count += 1
  ActualShow()

$ ->
  count = 0
  Show()
  setInterval(1000, Show)

Not immediately apparent how to do one-liner fluent jquery-style bindings (to put parenthesis right before the arrow)

$('#el').focus(function(){ DoFocus(); }).blur(function(){ DoBlur(); })

Took me a while to realize that I can write like this

$('#el').focus( -> DoFocus()).blur( -> DoBlur())

Then again, this is not quite correct due to implicty return problem mentioned above, so I end-up have to write something like this:

$('#el').focus -> 
  DoFocus()
  return
.blur -> 
  DoBlur()
  return

Much less fluent than the original javascript.

Lack of an easy way to traverse an array in reverse order

I have to write:

    idx = words.length
    while i > 0 
      i -= 1
      words.splice(i,1) if (words[i].length < 2)

instead of

    reverse_for word, i in words
      words.splice(i,1) if (word.length < 2)

NOTE: I used to do something shown below, which is actually incorrect. Can you discover the bug? Again it is related to the special behavior of coffeescript for-loop construct.

    for word, i in words
      if (word.length < 2)
        words.splice(i,1) 
        i-- # works, but scary

Stray space causes big trouble

# Good coffeescript ...
if (cc)
  a()
  b()
# turns bad if your coffee cup hits the space bar
if (cc)
   a()
  b()

translates to these:

if (cc) {
  a();
  b();
}

if (cc) a();

b();

Typo between of and in can be hard to detect

Could've warned me of this because I have the own keyword there.

# I wanted to do this:
for own id of list 
  doStuff(id)
# But end up doing this
for own id in list
  doStuff(id)

Coffeescript just blissfully generate code for both lines:

var id, _i, _len,
  __hasProp = Object.prototype.hasOwnProperty;

for (id in list) {
  if (!__hasProp.call(list, id)) continue;
  doStuff(id);
}

for (_i = 0, _len = list.length; _i < _len; _i++) {
  id = list[_i];
  doStuff(id);
}

Passing both a function and a hash can be tricky

This does not work, generates PARSE ERROR ON LINE 2: UNEXPECTED 'INDENT'

  FB.login
    (response) -> doStuff(); return
    scope: 'email'

This works:

  FB.login(
    (response) -> doStuff(); return
    scope: 'email')

Missing a good for loop construct, like this:

for(n=head; n!=null; n=n.next)
  dostuff()

And there is no do-while loops - See: http://stackoverflow.com/questions/6052946/were-do-while-loops-left-out-of-coffeescript

@CTimmerman
Copy link

for i in [5..arr.length] includes the last value. As you used < instead of <=, you need ... instead of ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment