Hashrocket.com / blog

Two Coffeescript Oddities

posted on and written by in

Here are a couple of interesting situations I encountered a while back while writing some Coffeescript. I was writing the QUnit test suite for jQuery.minical as an exploratory exercise to become more familiar with the language, and I ran across a couple of unexpected behaviors – not bugs, just ways Coffeescript’s more minimal syntax can trip you up in ways that aren’t immediately apparent if you’re coming from the world of vanilla Javascript.

$.map() and Coffeescript Aren’t Friends

One particular test in this plugin required that I verify the contents of the days of the week (it’s a calendar plugin), which are contained in the <th> elements of the calendar (a table). This is essentially what we want to verify:

$(".calendar th").text() == "SunMonTueWedThuFriSat"

Well, that’s fine, but kind of ugly. Just because.text() mashes everything together doesn’t mean I should just compare against a mashed-up string. So, I thought a nice touch would be to compare the elements in an array, like this:

$(".calendar th").getTextArray() == ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]

Now, $.getTextArray() is the function I wrote for this, and it’s kind of awkward in Coffeescript. The Javascript implementation is easy: use $.map() to create a jQuery object, and use $.get() to extract the native array. As the jQuery docs say: “As the return value is a jQuery object, which contains an array, it’s very common to call .get() on the result to work with a basic array.”

$.fn.getTextArray = function() {
  return $(this).map(function() {
    return $(this).text();
  }).get();
}

However, as far as I can tell, that output is undeniably awkward to execute in Coffeescript. This should work, right?

$.fn.getTextArray = ->
  $(@).map -> $(@).text().get()

Nope, that will call .get() within .map():

$(this).map(function() {
  return $(this).text().get();
});

Your options are to add parentheses, which is the most verbose javascript-like option …

$("li").map( ->
    $(@).text()
).get()

… outdent, which feels super-awkward (especially if you don’t indent the third line at all, which evaluates accurately but looks confusing) …

$("li").map ->
    $(@).text()
  .get()

… or use an extra set of parentheses (which actually outputs an extra set into the Javascript as well, but works fine):

($("li").map -> $(@).text()).get()

This isn’t a deal breaker by any means, but if you’re used to vanilla Javascript, you’ll run into these issues where jQuery expects to operate in ways that require more parentheses than Coffeescript usually needs, and your code will fail in unpredictable ways.

Update: I stand humbled by Tomas Carnecky’s comment below with another (superior) option: $("li").map(-> $(@).text()).get() does the job. Just goes to show how many suboptimal ways there are to refactor Javascript into Coffeescript.

Don’t Under-Parenthesize, if “Parenthesize” Is Even A Word

Here’s a more complex example. I wanted to put together an array of every day I expected to be in a particular calendar month view. This would require handwriting an array or running a bunch of loops in Javascript, but Coffeescript’s comprehensions and array operations allows us to slam together the whole array in one line.

days = ((day + "") for day in [].concat([25..30],[1..31],[1..5]))

Woohoo! I love that to death. Coffeescript is occasionally great for reducing distracting code around something minor like constructing an arbitrary array. However, note the enclosing parentheses around the whole thing. If you remove those:

days = (day + "") for day in [].concat([25..30],[1..31],[1..5])

CRAZY THINGS happen, the results of which I will leave as an exercise for the reader.

The bottom line here is that Coffeescript’s minimal syntax will sometimes sneak up on you when you’re not carefully keeping track of how it’s evaluating your code. It’s pretty easy to drop one too many sets of parentheses in your zeal to write concisely.

This sort of extreme code breakage in the CS to JS handoff is the riskiest thing about using Coffeescript, as it can be difficult to bug fix, but if you’ve used Coffeescript at all, you’re well aware of that, and hopefully this article has better prepared you for those situations. Happy Coffeescripting!

Posted in Development and tagged with Javascript