Do more in CoffeeScript
• Category: ProgrammingI’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
-
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! ↩︎