CoffeeScript and JS Libraries

AuthorMáximo Mussini
·2 min read

A language helps to shape the libraries that are written on it. This can have unexpected side-effects when using these libraries from a different language.

In the frontend this has become increasingly more common because of all the available languages that compile to JavaScript. CoffeeScript was one of the first ones to gain adoption, but nowadays we have a myriad of available languages like Elm, ClojureScript, Opal, and many more.

Recently, we were trying to debug a strange issue with one of my coworkers, where we had a list of selectable items with a checkbox to select/unselect all the items. Selecting all the items was working properly, but only the first item got unselected.

After debugging it for a while we reached the conclusion that the iteration was being interrupted. Could lodash have such a serious bug?

selectAll = (isSelected) ->
  _.each tasks, (task) -> task.selected = isSelected

Fortunately, we decided to skip that theory, and instead reached for the documentation, which stated:

Iteratee functions may exit iteration early by explicitly returning false.

Which sounds like a reasonable feature that can help to create more efficient algorithms—though we weren't returning false!

But CoffeeScript was. The language draws inspiration from Ruby, and it borrows features like expressions and implicit returns. For functions, this means that the last executed expression becomes the return value of the function. This allows us to write very concise functions:

isEven = (n) -> n % 2 == 0

If we reinterpret our snippet above taking implicit returns into account, it is equivalent to:

function selectAll(isSelected) {
  return _.each(tasks, function(task) { return task.selected = isSelected })
}

When we unselect all the items and selectAll(false) is called, the first iteration returns the value of task.selected which is false, causing lodash to exit the iteration. Mistery solved 🔍

We can fix the bug by explicitly returning null or true on each loop, which will avoid stopping the iteration, and all the items will be unselected correctly.

selectAll = (isSelected) ->
  _.each tasks, (task) ->
    task.selected = isSelected
    true

These are two useful features on their own, yet extremely inconvenient when combined. The reason is that the library was written for a language with a different mindset. The design of the language and the design of the library are not fully compatible.

TL;DR

Watch out for language differences when using libraries written for another language, they will bite sooner or later.