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.
>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.
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.
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.
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).
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 `...)
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.
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.
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.
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.
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.
Let's extend your example:
So Python does take from the outer scope? Or not? 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.