Link

Do more in CoffeeScript

I’ve been dabbling a bit in CoffeeScript, and so far it’s been fairly good once I started to trust its abstractions and not worry so much about leakiness.

Some handy CoffeeScript features I wish were better documented (it is somewhat in a Github issue thread, but nowhere else as far as I can see at the time of this writing) are the variations on the do (...) -> ... syntax.

First, you can use default argument syntax, which let’s you do things like

func = ->
  x = {}
  for i in [0...5]
    x.i = i
    do (temp = x.i) ->
      -> temp

# Gives 0,1,2,4
f() for f in func()

That’s syntactic sugar for

func = ->
  x = {}
  for i in [0...5]
    x.i = i
    ((temp) ->
      -> temp
    )(x.i)

# Gives 0,1,2,4
f() for f in func()

(It seems according to the issue thread above that this was the original syntax for do, and the better known do (x) -> ... is actually shorthand for do (x = x) -> ..., but that’s not stated in the official documentation on the website. Two more variations:

# Gives [1, 1, 2, 6, 24, 120]
for i in [0...6]
  do factorial = (i) ->
    if i == 0 then 1 else i * factorial(i - 1)

which is sugar for

for i in [0...6]
  (factorial = (i) ->
    if i == 0 then 1 else temp * factorial(i - 1)
  )(i)

and

f = -> 1
do f # returns 1

which of course is just sugar for

f = -> 1
f()

What is do?

Ok, some more explanation for those who are unfamiliar with do syntax, or who want to know what is different about the above from the usual documented syntax for do.

do addresses the problem of combining JavaScript closures with mutable environments.1 Here is a rather contrived example:

f1 = ->
  for i in [0...5]
    -> i

# Gives 5,5,5,5,5
f() for f in f1()

Since variables in JavaScript can be rebound to other values, i points to something different after the closures are defined. Because each closure has a reference to the scope of f1 and not to the value of the variable i, the end result is that each function returns the same value of '5' (the value of i after the for loop is finished).

The common solution (described in JavaScript: The Good Parts and elsewhere) is basically to change the stack scope to which the inner anonymous functions point:

f2 = ->
  for i in [0...5]
    ((temp) ->
      (-> temp)
    )(i)

# Gives 0,1,2,3,4
f() for f in f2()

f2 sets up another scope inside the for loop by using a function which is executed immediately. This allows the closures to point to an intermediate scope that won’t change when the for loop alters the i variable, because i is passed by value to the intermediate scope; I’ve emphasized this by using a new variable temp, though you could just as well write f2 as

f2 = ->
  for i in [0...5]
    ((i) ->
      (-> i)
    )(i)

(It’s a little more confusing this way, though.) In any case, this is the solution that CoffeeScript has embraced with the do syntax. You can write f2 as

f2 = ->
  for i in [0...5]
    do (i) ->
      (-> i)

This particular syntax, however, only works when i is a primative immutable type, because JavaScript is a pass-by-reference language. If you need to refer to values in a mutable object in the outer scope, you come back to essentially same problem, and standard do notation doesn’t work.

You can either go back to using your little intermediate-scope closure (again, please forgive the extremely contrived example):

f3 = ->
  x = {}
  for i in [0...5]
    x.i = i
    ((temp) ->
      (-> temp.toString())
    )(x.i)

# Gives 0,1,2,3,4
f() for f in f3()

Or, use the undocumented syntax, which is moderately cleaner:

f3 = ->
  x = {}
  for i in [0...5]
    x.i = i
    do (temp = x.i) ->
      -> temp

You can also use this capability of do to implement the JavaScript module pattern with local aliases for global objects:

myModule = do ($ = jQuery, bb = Backbone, _ = _) ->
    # do stuff, return module object

  1. One of the nice things about immutability in languages like Haskell is that you don’t have to worry about variables in a scope changing from underneath you. The value of a variable at the time you define a closure is guaranteed to be the same value when you call the closure. None of this do nonsense! ↩︎