Hacker News new | past | comments | ask | show | jobs | submit login
The worst thing about CoffeeScript (walpurgisriot.github.io)
43 points by luu on Jan 25, 2014 | hide | past | favorite | 54 comments



The worst thing about CoffeeScript is that you are used to having scoping work a different way?

Let's extend your example:

    >>> a = 1
    >>> def foo():
          print a
    >>> foo()
    1
So Python does take from the outer scope?

    >>> a = 1
    >>> def foo():
          a = 2
          print a
    >>> foo()
    2
Or not?

    >>> a = 1
    >>> def foo():
          print a
          a = 2
    >>> foo()
    UnboundLocalError: local variable 'a' referenced before assignment
Or it just doesn't work at all?

Next time, pick a better example. The answer to "What does Python do with a variable from outside a function's scope?" is "It depends."

At least CoffeeScript is consistent.


Coffeescript isn't consistent.

    setter = (arg) -> store = arg
    store = 'initial'
is quite different from

    store = 'initial'
    setter = (arg) -> store = arg
This is a practical problem since it means that the most commonly used JS encapsulation feature - closures - become harder to write. Suddenly assignments that have no direct influence on each other cannot be blindly reordered (which is a pain), and in particular closures that have a bunch of helper functions defined initially that need to ensure that local variables are set before the helpers, even if those local variables are themselves functions.

It's just a mess. Frankly, I think javascript does this part a lot better than coffeescript, especially now that "use strict" exists and you can get some feedback when your scoping assumptions turn out wrong, rather than a silent failure and unexpected behavior.


It is a bigger issue than lack of familiarity.

It can lead to some really unexpected bugs. There is a reason most languages don't do things this way.

http://lucumr.pocoo.org/2011/12/22/implicit-scoping-in-coffe...


This is also a problem with javascript. Javascript should do automatic variable shadowing but also throw a warning.


>The worst thing about CoffeeScript is that you are used to having scoping work a different way?

No, the issue is that he's used to having scoping work properly.

Coffeescript behavior corrects a wrong (shadowing) with an even worse wrong. Even worse, as a default it can affect variables outside of the function, so it breaks locality.


I suspect, but don't know, this is different in Python 3, since `print a` changes from a statement to an expression.


Certainly, by far the "most controversial thing about CoffeeScript".

Fortunately, CoffeeScript is open-source, and can be both easily changed, and easily forked to suit your fancy. There are very nice versions (LiveScript and Coco, for example), that change this behavior by introducing two different types of assignment — if that sort of thing is your cup of tea.

... and if anyone has a specific proposal about how they'd like to see this feature changed in order to improve it, feel free to open a ticket for discussion — it could very well happen. For example, making this:

    topLevelFunction = ->
      path = "a/b/c"
 
    ... many lines of code go here ...

    path = require('path')
... into a compile-time error, with some sort of "Subsequent variable shadows `path`" warning, that would be something worth talking about.


The fact that it's open source doesn't help: using and maintaining a similar but subtly different version of coffeescript is worse than living with this bug since it means your code is now incompatible with the rest of the world's. Not to mention you're likely to train yourself to misread "real" coffeescript.

As to the ticket; A quick search would have revealed there are many tickets on the topic of shadowing: https://github.com/jashkenas/coffee-script/search?q=shadowin...

And this ticket covers this specifically https://github.com/jashkenas/coffee-script/issues/2697 and was closed.


I think Jeremy is being a bit disingenuous here. There are many good reasons for not wanting to fork a language, and getting a change made to CoffeeScript is frustrating and glacial. To be clear, I understand why this is: bad decisions are hard to reverse. Unfortunately, the specific issue raised at the top is a poster-child for bad decisions that are hard to reverse.


I don't know if this can be solved without a syntax change. As long as there is variable binding and variable mutation are conflated in the syntax, there's really no good way for the compiler to distinguish between the two. It's an unfortunate design decision that we may be stuck with if we want back-compat.

LiveScript is interesting, but seems to have Too Many Features for my tastes.


I use both javascript and coffeescript extensively and regularly, and I just don't understand how this ever causes real problems to anyone - unless you're writing 5000 line long spaghetti code coffeescript files with ambiguous variable names, in which case lexical scoping is the least of your worries.


> I use both javascript and coffeescript extensively and regularly, and I just don't understand how this ever causes real problems to anyone

If you have a team that is big enough and a project that lives long enough that someone has to do changes in some old code, this thing is bound to happen sooner or later.


I have to agree, I will probably write 10k lines of coffeescript in the next year, much of it in reusable and open source libraries, and OPs problem does not seem to cause issues all the much in practice.

People have to distinguish between the simple issues that trip up beginners, and the fundamental issues that plague you for life.


>I have to agree, I will probably write 10k lines of coffeescript in the next year, much of it in reusable and open source libraries, and OPs problem does not seem to cause issues all the much in practice.

Whether it causes any issues in practice for some particular programmer or not (and 10K lines is not much even for ONE modern webapp, and you write them broken down in multiple libraries), the thing is the behavior is logically flawed in itself.

Like Javascript needing "var" to NOT make a global variable (instead of defaulting on local vars) is.

Coffescript's behavior on this scoping issue is in the list of bad items that includes goto, global variables and nullity.


Same here.


Raganwald shows an interesting point of view on Coffeescript scoping in relation to javascript https://github.com/raganwald/homoiconic/blob/master/2012/09/...


Never had this problem with coffeescript until... today. Some hours after I read this post. Thanks

angular.module("blabla", []).directive "bla", ->

template: """ <div>Whatever<div ng-transclude></div></div></div> """ scope: true transclude: true restrict: "E" compile: (element, attributes) -> $input = element.find "input" for attr, value of attributes.$attr $input.attr value, attributes[attr] element[0].removeAttribute value

		(scope, element, attributes) ->
			$input = element.find "input"
			# do something with $input
			# now $input is global to all directive and that sucks


Drop a "space space" in front of every line in your code and it'll format monospace.


This is why I don't use it. I know JS well, I can think in JS. Changing the syntax is fine but altering semantics in subtle ways would probably drive me crazy.


It doesn't really alter the semantics, if you recognize that CoffeeScript always adds `var` before variable assignments, you can just get around that by attaching a variable you want to share to a higher scope by using `this.whatever = 'foo'`. It's really not as hard as people make it out to be, but there are still plenty of killer reasons not to use CS (for example, if you don't feel like learning it...I mean JS is just as good, it does all the same stuff).


CoffeeScript doesn't always add var before assignments though, sometimes it does, sometimes it doesn't, depending on the variables in outer scopes.

This has two assignments, but only one var in the compiled JS:

    a = 1
    -> a = 2
This has two vars:

    -> a = 1
    -> a = 2


Much worse is this gotcha:

One variable:

    a = 1
    -> a = 2
Two variables:

    -> a = 2
    a = 1


I love vanilla JS, but I also love CoffeeScript. I think the thing that I love the most about CS is it's looping constructs (just the ability to iterate over an array without typing a for loop then another variable to get the item out) that have a custom step and when filter is pretty nice.

That saves me quite a bit of time in the long run.


In modern JS (easily polyfilled), there are map, reduce, filter, and forEach for this. These have the further advantage of being more easily chained; however they're a lot slower too.

One downside of coffeescript's loop comprehensions is that they generate fairly complex code whenever you use them in non-trivial ways, thus undermining coffeescript's key feature: easily readable (and debuggable) javascript.


If I really do need an explicit scope I find myself writing `foo = no` higher up which actually seems pretty natural, if a bit verbose (as many characters as `var `...)


It is funny to see Python in an example of 'good' variable scoping. How exactly would you refer to the outer 'a' in that python example?


The "global" keyword in Python binds a name to the scope of the top level of the enclosing module. The "nonlocal" keyword is a Python 3.x feature which binds a name to the next enclosing scope level.

So you would do:

    a = 1

    def contrived():
        global a    # I added this
        a = 2
        return a

    contrived() # 2
    a           # 2
The "nonlocal" keyword would give the same result if the outer scope is the top level of the module. If the entire above code is itself enclosed in a function like so:

    def enclose():
        a = 1

        def contrived():
            nonlocal a
            a = 2
            return a

        contrived() # 2
        a           # 2
You need "nonlocal" to refer to the "a" that's a local variable in enclose(). I.e. "global" gets you the top level, "nonlocal" gets you the next enclosing level.

The keyword is only necessary when you're assigning to an out-of-scope variable. If you remove the assignment statement "a = 2" from the function, the program will correctly read and return the value of a from the enclosing scope.

In other words, by default the set of names considered to be local variables in a function is the set of names that are the target of an assignment statement. Usually the default behavior is what you want, but if you wish to override it, use the "global" or "nonlocal" keyword.

From a design standpoint, I've learned over the years that global / nonlocal keywords are a "smell" that frequently indicates poor architecture, usually of the sort that the oft-cited "global variables are evil" doctrine is intended to protect against. Code that extensively uses these keywords should usually be refactored into a class, with the formerly global names instead being attributes (member variables).


Not completely seriously.. This is why some of us prefer Perl ;)

    my $a = 1;
    
    my $contrived = sub {
        my $a = 2; # new lexical variable $a
        $a = 3; # if the my above wasn't there, outer $a would be 3
        ...
    }

    say $a; # 1
Explicit declaration of the scope of a variable at initialisation is awesome. 'global' and 'nonlocal' feel weird/less consistent/like a workaround. People are used to creating variables 'on the fly' in Python / CoffeeScript - both break (for a certain value of 'break') certain assumptions in certain circumstances.

I've never heard anyone ever complain about perl's lexical 'my'.. It even catches - with a warning - multiple my declarations that will clobber each other. You don't see that in other languages that often.


If you've ever programmed in JavaScript -- and most people have, it's the language of the Web -- you will know that it is very easy to accidentally omit the "var" keyword and unintentionally pollute the global namespace. Such a bug likely will not manifest until someone else writes completely unrelated code that uses the same variable name by coincidence.

Explicit declaration of the scope of a variable at initialization leads to unintended global namespace pollution when omitting the declaration is legal and causes the variable to be placed in the global scope, which can be difficult to diagnose because it is usually completely silent until unrelated code happens to interact by using the same name.

From a language design standpoint, this means that you should either require a declaration which determines scope (C or Java), or have local be the default for variables whose scope is undeclared (Python).

> I've never heard anyone ever complain about perl's lexical 'my'

That's because programmers who care about good syntax in their languages don't use Perl.


> Explicit declaration of the scope of a variable at initialization leads to unintended global namespace pollution when omitting the declaration is legal and causes the variable to be placed in the global scope

Ah, but it's not legal with 'use strict;' (which everyone uses)

> That's because programmers who care about good syntax in their languages don't use Perl.

And Lisp has too many parentheses. I clearly just like my code to be illegible.


Yeah - back when coffeescript was new this was an argument, but with "use strict", the likelihood of accidental mis-assignments is actually considerably slower in javascript than in coffeescript. In javascript you'll get an error assigning to a non-existant variable you meant to be global; in coffeescript you'll get a local varaible and that means subtly buggy code.


I think cursork was talking about 'use strict' in Perl; he said that everyone uses it. 'use strict' in JavaScript is much rarer than it is in Perl, at least in my experience.


Well, I always use it when I have the chance: it's easy to turn on, and it catches nasty bugs. Why wouldn't you turn it on? Even if you have a large legacy code base, it's still a a win, since you can turn it on function-by-function.


Additionally, unwanted global assignments are really easy to detect with a linter.


By using Python3's nonlocal statement.

  def outer():
      a, b = 1, 2

      def inner():
          nonlocal a
          a, b = 3, 4
          print('inner a: ', a)
          print('inner b: ', b)

      inner()
      print('outer a: ', a)
      print('outer b: ', b)

  outer()
  # inner a:  3
  # inner b:  4
  # outer a:  3
  # outer b:  2
I find Python's scoping rules to be quite sane.


``print a``? The inner `a` is the same reference as the outer `a`.

  a = 1;

  def foo(): b = a; return b

  foo() is a

  >> True
This gets hairy when referring to mutable types:

  a = []

  def foo(): b = a; return b

  foo().append(1)

  a

  >> [3]
Compare to immutable types:

  c = ()

  def bar(): d = c; return d

  bar() + (1,)

  c

  >> ()


I understand the context, but isn't it unfair to compare `append` and `+` ? Python is not clojure or sml, I think people assume mutability, and using foo() + [1] will consistently build fresh objects in both cases.


No, I don't think it's unfair because "fairness" has nothing to do with anything.

I was simply illustrating how using a mutable data structure defined in an outer scope can bite you.


Fair enough. May I mention the default parameter bite too. It's hard to notice at first.


you're doing something wrong if this is an issue for you.

I personally would like to apologize to everyone for dissing CoffeeScript in the past, here (thus my low karma) and elsewhere.

I'm working with a new customer now, three weeks already. and they're using CoffeeScript. and I had to get over myself and start using it. and it's amazing. I've been so stubbornly blind to be against it without actually trying it out yet cursing and flaming against it. it's so much faster to work with it, it reduces the boilerplate code, it simplifies everything.

and, yes, I still think you need to be able to know and use JavaScript very good before taking on CoffeeScript -- otherwise topics like the one we're discussing here arise.


>you're doing something wrong if this is an issue for you.

Of course he's doing something wrong. That's the whole point of TFA. That the language, due to a bad design decision, permits this case of doing something wrong, when it shouldn't.

Like Javascript making variables global by default (unless you use var). If this is an issue to you, you're doing something wrong (namely, you're forgeting to add "var"). But the language does an even bigger mistake by permitting this to happen.


all I can say is "use strict";


I do! But that's not available in CS, right?


For me there is no worst thing in CS. The thing that bothers me now is that there is no practical standard on documenting the code similar to JsDoc. Yes there are a couple of ways to do that, but in fact meta-programming helps not only for cleaner code, but also Closure Compiler, any IDEs ( WebStorm autocomplete ) and also it helps make the code more readable. Anyway in every project that I work I usually try to convince my team to switch to CoffeeScript.

Big thanks to jashkenas, because ... CS is awesome!


First of all, that's hardly a bad thing about CoffeeScript. It's very consistent and is a tradeoff of not having to use var everywhere to define your variables.

If you want to complain about CoffeeScript, there are some much more fundamental issues. For example:

- I can write `foo.bar baz, bat` but if I call it with no arguments I have to use parens. `for.bar` will just return the function. I understand why it's the case, but it sucks.

- Syntax is so flexible that sometimes it still compiles when you make a mistake.


"- I can write `foo.bar baz, bat` but if I call it with no arguments I have to use parens. `for.bar` will just return the function. I understand why it's the case, but it sucks."

Interestingly, if you write "do foo.bar" you don't need to use parens and it will work as expected. I think this is a bit more idiomatic.


Worst thing? I was expecting to see discussion on the use of white-space instead of the obviously superior curly braces! :-)


I believe there is a coffeelint plugin that will warn you if you reassign a variable from an outer scope


MoonScript introduces a special construct for handling this exact situation: http://moonscript.org/reference/#the_using_clause_controllin...


iirc LiveScript don't have this issue with scoping


In LiveScript there's a := operator. From the docs -- "However, unlike CoffeeScript, you must use := to modify variables in upper scopes" -- I find LiveScript overall a better alternative to CoffeeScript. If you're into CS for the sugar, then you'll love LS.


Much love to LS! Been using it almost exclusively for over a year.


Is this really news?


Don't use semicolons in CoffeeScript!




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: