Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Scoping in CoffeeScript and JavaScript (raganwald.com)
61 points by waffle_ss on July 27, 2013 | hide | past | favorite | 61 comments


Wow... despite all of the CoffeeScript programming I've done, I've never come across this, but this kind of "edge" case is totally necessary for variables to "usually" be local, but also still always be able to access closure variables in a higher scope -- which are normally contradictory goals.

In a nutshell... it's totally representative of tons of things in CoffeeScript, in trying to:

a) make things easy for the programmer by removing JavaScript features and syntax

b) to make things easy, make various assumptions in ambiguous syntax situations about a programmer's intent

c) introduce no way to guess or predict which way CoffeeScript will act in "edge" cases like this, when assumptions conflict

d) not bother to document what happens in each edge case, or that there even is any kind of edge case at all -- indeed, to make it sound on CoffeeScript's home page / docs that everything is well-defined, when the reality is just an endless list of mysterious and unpredictable transpilation decisions based on internal undocumented CoffeeScript heuristics

I used CoffeeScript for a full year, and then was very happy never to touch it again. Going back to plain JavaScript might not be quite as concise, but the fact that the code always does exactly what you want it to, no surprises (as long as you know the language), let me program in peace again. With CoffeeScript, there's no way to ever feel like you "know" the language, because these kind of edge-case gotchas will getcha day-in, day-out.


> Going back to plain JavaScript might not be quite as concise, but the fact that the code always does exactly what you want it to, no surprises (as long as you know the language), let me program in peace again.

I'm sympathetic to your point, but this seems entirely wrong in context, because JS exhibits all the flaws you decry and more.

JS is often an ambiguous language, and often behaves poorly in edge cases. Even in the specific example (variable scope) the JS rules are arcane, poorly documented, confuse the hell out of even experienced JS programmers, and are hard to debug. Unless your argument boils down to "I know JS much better than Coffeescript, so its edge cases are less surprising to me", where in the world is the "win" from switching to Javascript from Coffeescript?

For what its worth, I've used Coffeescript more than you, and I find it does have the problems you state, as do pretty much all languages; it's one of degree. Coffeescript is worse than, eg, lua, but better than, eg, Javascript. And of the feasible languages to write code that will run in the browser, Coffeescript is the best choice (if the decision is purely based on "will my code do what I want/are edge cases going to bite me unpleasantly"). But that's just my personal experience. :)


> Even in the specific example (variable scope) the JS rules are arcane, poorly documented, confuse the hell out of even experienced JS programmers, and are hard to debug.

When using strict mode, which you should be, you're about 0/4.

Your second incorrect complain is especially galling as ECMA-262 goes to extensive length in specifying it (section 10 "Executable Code and Execution Contexts" of the ECMA-262 5.1 specification)


I don't think I agree with your point. Javascript is a very small language, and while there are a number of gotchas they are enumerable and well-defined.

Central concepts:

- Objects

- Prototypes

- Scope chains

- Closures

- Function.bind, Function.apply/Function.call

Gotchas:

- Behavior of the "new" operator

- Value of "this"

- Type coercion, falsiness of "", == vs. ===

- var hoisting

- if/for/while do not create scope

- function expression vs. function declaration

Many of the gotchas are self-evident if you learn the central concepts.


Exactly. I can deal with "gotchas" and edge cases as long as they're documented and I can memorize them -- which is 100% the case with JavaScript.

The reason I'll never use CoffeeScript again isn't because of its gotchas -- it's because there's no way to learn them. They're not documented. They're not predictable from principles. They're completely hidden.


It sounds like CoffeeScript violates the principle of least surprise [1] for you (and me too.) On the other hand, when I use a language like C# (whose author is also behind TypeScript) I often find myself thinking "it should work this way" and then finding out is does work that way.

[1] https://en.wikipedia.org/wiki/Principle_of_least_astonishmen...


> these kind of edge-case gotchas will getcha day-in, day-out

Not my experience in 3 years writing CS; can't even remember last time I blamed something on the language. There are very little rough edges if you use a consistent, good coding style.

I'm waiting for ES6 to abandon ship, might take a while.


> because these kind of edge-case gotchas will getcha day-in, day-out.

Examples please?

I've done lots of CoffeeScript in the last year and I had no gotchas at all. I'm able to predict the JS output of my Coffee files accurately and it never surprises me (well, once, when I ran into a compiler bug).


Well the original post is one example, obviously.

But, lots of examples below, all from the first few Google results of "coffeescript ambiguity":

http://ruoyusun.com/2013/03/17/my-take-on-coffeescript.html [section "full of surprises"]

http://ceronman.com/2012/09/17/coffeescript-less-typing-bad-...

http://johnbender.us/2012/11/27/math-envy-and-coffeescripts-... [this is a more formal analysis]


Wait, if edge cases turn you away from a language, exactly what languages do you use and for how long?

If Jeremy and the rest of the CS core contributors documented things like this it would cause more confusion. Look how contrived the examples in the blog post are to begin with.


I really like the way scheme does it. In order to mutate a variable (which is actually comparatively rare), you have to use `set!`, and to create a binding (much more common), you just call `define`. `set!` is incapable of creating new bindings, and will error if the variable is in scope, while `define` will always shadow.

The js and coffeescript ways of handling this seem both to come down to the assignment operator being used for both creating bindings and mutating them, which gets confusing, and causes the failure modes you described.


> The js and coffeescript ways of handling this seem both to come down to the assignment operator being used for both creating bindings and mutating them

That is not quite the case in javascript, `var` creates a binding (also `let` in ES6), and assignment assigns to the binding.

There is the issue that assigning to an non-existant binding creates a global one, but strict mode will turn it into an error and linters have little issue recognizing and warning against it either.


Hm, but it seems to be a runtime error. Is there a transpiler or other tool that will give you static errors if you try to set an out-of-scope variable?


> Hm, but it seems to be a runtime error.

Yes, it's still javascript.

> Is there a transpiler or other tool that will give you static errors if you try to set an out-of-scope variable?

Elm and Fay likely do.


Just asked about this on #altjs, and it looks like TypeScript will actually give you a static error if you try to assign to a free variable.


This article "CoffeeScript's Scoping is Madness[1]" and the reddit programming discussion thread[2] convinced me to scratch CoffeeScript off the list of languages to consider. Ain't nobody got time for that.

[1] http://donatstudios.com/CoffeeScript-Madness

[2] http://www.reddit.com/r/programming/comments/1j1cw7/coffeesc...


I'd have to say it's your loss, it's a shame to discount a programming language simply based on a blog post. I remember plenty of blog posts about Go popping up a few years claiming that it was unusable due to the lack of generics, mainly stemming from on a lack of familiarity with the language.

The main reason to use Coffeescript is in place of Javascript, which already has plenty of warts to avoid, many more serious that this issue, and many of which Coffeescript helps to alleviate. If time spent on something is important to you, Coffeescript can help save a great deal through it's expressiveness, especially in areas where vanilla JS is awkward (eg. OOP).

Personally, with relation to the scoping issue, I tend towards using Underscore's higher-order iteration functions (each, map, reduce, etc.) in most cases instead of Coffeescript's `for`, which eliminates this issue entirely due to the scoping of the anonymous functions passed to them.


In my experience, most people who dismiss/hate on CoffeeScript have never tried it. I've personally never met a person who has used the language at length and walked away hating it.


See my root comment. I used it for a whole year, and it would drive me nuts every day. I would never use it again. But then again, you've never personally met me, so maybe I don't count :)


I have know one or two people that dismissed CoffeeScript after fiddling with it for a while; but they were people who didn't have previous experience with JavaScript, and i suspect that might be part of the reason.

The people i've known who most embraced CS were people who have had their deal of JS experience, who knew many of JS quirks and its workarounds, like doing "var self = this", or how to use closure visibility to avoid the need for global variables, or even how to up a prototype chain to implement some basic inheritance. Personally, i know that whenever i have to code in JS for whatever reason and need to do some of those things, i miss CS :)


Personally, even if I conceded Coffeescript is worse in some scenarios [1], I still evaluate Coffeescript as far preferable to Javascript overall. There's multiple dimensions to consider. (Disclaimer: I've only compared the two on the backend, so far.)

[1] Like representing datastructures. Or that subclass-without-constructor subtlety (https://github.com/jashkenas/coffee-script/issues/2956).


I don't see that the less verbose syntax of CoffeeScript buys you that much and it's another level of indirection to worry about. My problems with JavaScript are that you're flying blind when you could have an editor pointing mistakes and making helpful suggestions. I tried Dart as a replacement first (too far removed from the JavaScript) and settled on TypeScript, which is a dream because it provides the help I want without obfuscating the resulting JavaScript at all.


Coffee buys me sanity, can easily parse it vs. the giant syntactic mess that is JavaScript.

Coffee is to JavaScript as Scala is to Java.

Compilation blazes with GruntJS as well so overall it's a complete and utter win [for me].


> Coffee is to JavaScript as Scala is to Java.

Considering how incredibly wrong and broken CoffeeScript's scoping is (at least the JavaScript designers realized that they messed up badly), I'd consider that an insult for Scala. :-)


CoffeeScript and Javascript aren't your only options. I quite like CoffeeScript on the whole, but I'm still planning to look into using LiveScript or GorillaScript for the next substantial JS-based project I work on, since they both capture what I like about CS while appearing to fix some of the issues.


I have never understood why anyone would bother with coffeescript. Afterall it just transpiles to javascript. If you know javascript what's the point in a new language that offers nothing additional to javascript other than syntactic sugar. Not to mention the coffeescript's poor compiler design.

To me typescript beats dart and coffeescript by miles. I don't have to use a new syntax or figure out the idiosyncrasies of a new language, it provides the type safety and tools that I need for javascript apps without reinventing the wheel in a very simple and intuitive form.

It solves the problems that javascript doesn't, type-safety. In fact I can't recall another language that is dynamic but also statically typed.


I've been doing full time Coffeescript front end dev for about a year and a half on a moderately complex one page app. I would be happy to never write vanilla javascript again.

The value of coffeescript is that when written well you can get a very high signal to noise ratio in your codebase. When I say "noise", I'm referring to low value syntax and boilerplate, things like:

var someFunc, _this; _this = this; someFunc = function(){ return _this.foo; }

By comparison, the coffeescript is: someFunc = => @.foo

In the coffeescript example, it is immediately obvious what 'someFunc' does. A trained javascript developer will be good at filtering the junk, and extracting the bits that are actually meaningful and important. But any time you are parsing through a bunch of junk to get to the meaningful stuff, you are opening the door to make mistakes, and increasing the time it takes to comprehend/load the code into your brain.

Another important benefit of readable & concise code is that you can hold more of it on a screen at a time, which makes it easier to keep a full class in context and/or work on a small screen (e.g. laptop on the go). I would much rather work with a 100 line coffeescript file than a 300 line javascript file.

This is the same reason I really love the underscore library (not coincidentally also an Ashkenas product). Everything it does is something any competent JS developer can do. But it does it in one line, and in a way that is very easy to read.

Incidentally, using a library like underscore with good iterators also avoids a common class of the "variable capturing" problem, as you don't end up using named variables for iteration (opting instead for map/reduce/find/etc). We otherwise use descriptive names in our project, so the chance of a clash is minimal. In my year and a half on the project, I've yet to run into a "variable capturing" bug. Admittedly that does mean that when it finally happens, I probably won't be looking for it.


TypeScript is awesome. I tried Dart first and found I could bang out code much faster, but when you start to push the Dart team about JavaScript you soon realize they don't care much for JavaScript and really do want to replace it. I'm not ready to abandon JavaScript just yet. TypeScript adds type checking, code completion and easy refactoring to normal JavaScript code. It's all gain and no pain. I can only assume it's not taken off even stronger because it's from Microsoft.


I would say type checking is one of the "pain". Do not get me wrong I use it so that it is clear to other os developers what the expected interfaces are, but absolutely cannot stand the current state of `d.ts` files for external libraries ending up fixing those and searching for different versions instead of focusing on my code.


I've had nothing but good experience with the definitley typed library

https://github.com/borisyankov/DefinitelyTyped


I am glad you did. My first point of contention is the fact that there is only one version of a definition file for a library. I sometimes do not have the luxury of choosing just the version that they have in the repo.


Came across TypeScript last month, looks interesting.

What are the limitations of the type checker? i.e. does the compiler fully have your back, or are we just talking validating whether or not a method param takes a String vs. an Int?

Coffee is a dream [for me], JavaScript, not so much.


I'm not sure what you mean by the compiler fully having your back, but it is fairly helpful. It does type inference, which means you don't have to explicitly type everything to get type checking help. You can do as little or as much type checking as you would like. Regular unadorned JavaScript code is TypeScript code and will compile. The compiler will tell you when not all code paths return a value, when the wrong type is passed to a function, when a return value is the wrong type, etc... It keeps track of the symbols in the source making refactoring work as you would expect. I really can't think of any downside since it's just JavaScript.


By type inference I mean the compiler has your back ;-)

If IDE support is getting to the point where you can hover-view/click-through to type, then that is really quite impressive.

Will be awhile before that gets to MonoDevelop, however (Linux workstation here).

For me the ideal would be a terse syntax a la coffeescript, but with static type checking.

I wonder what compile times are like for large-ish TypeScript projects? Imagine it's fairly quick, but even simple static type checking will bring some overhead.


> If IDE support is getting to the point where you can hover-view/click-through to type, then that is really quite impressive.

Like this? http://i.imgur.com/RRFq0dl.png

Compile times are easily control by breaking the files up. Similar to C# partial class, they can still go in the same module. I'm using the free Visual Studio Express for Web, so I'm not sure about Mono/Linux support, but I hear there is WebStorm and Sublime Text plugin support and one I also like which is Adobe Brackets.


For all the cases I have checked the type checker is extremely impressive. I have even checked whether types flow through promises! and with underscore.js and it has not failed me yet, it is extremely robust.

But aside from that, the most useful feature of typescript to me has been the productivity gains, just checkout: http://www.typescriptlang.org/Playground/ You can now have an IDE that provides useful information about clases/objects/etc. (imagine if javascript actually had proper codecompletion and intellisense)


Any thoughts on Closure Compiler vs. typescript? One thing I like about Closure Compiler is that it lets you explicitly list the symbols you wish to export from your JS, which is a huge boon for code minification.


Ideally TypeScript will honor the Closure source annotations like this

http://blog.bolinfest.com/2013/01/generating-google-closure-...


Both JavaScript and CoffeeScript got it wrong. Here's the right solution:

1) Keep the distinction between declaration and assignment. 'var x = 1' introduces a new variable, 'x = 1' changes an existing variable.

2) Remove globals from the language. 'x = 1' becomes an error unless there's a 'var x' in scope.

3) Change 'var' declarations so they apply to the containing {} block, not the whole containing function.

There's wouldn't be much to discuss here, if not for historical accidents. Brendan should've known better because he knew Scheme, which had it exactly right, and Jeremy should've known better because he saw the mistakes of JavaScript, but they both screwed up.

If you're an aspiring language designer, please learn from this. There are valid disagreements in language design, but this isn't one of them. (Some other solved problems: modules > header files, option types > nulls, covariant arrays don't work, etc.)


1. and 2. are solved in javascript by using strict mode (now in a browser near you)

3. is solved in ES6 with `let` (currently working in Firefox, behind a flag in Chrome, no support in Opera, Safari, MSIE or Node). Note that let also 1. isn't hoisted and 2. forbids double declaration (`let x; let x;` is an error)


Thanks! That's very nice, among possible backward compatible solutions I cannot imagine a better one.

Now I wonder what kind of fix the CoffeeScript folks will make, when they admit the problem...


I don't think they can fix that one in CS short of changing the semantics of the language: they decided to infer scope (an error as far as I'm concerned) and because they wanted to avoid shadowing they went with generalizing scope inference (as opposed to Python, which always infers function-local scope unless specifically told otherwise, scope inference is still bad but slightly less so as it won't suddenly turn a local variable into a global one without warning due to a non-local change).

I believe CS tried to copy Ruby as block scope inference works the same way, but Ruby uses its method/block duality to mitigate the issue: scope inference is generalizing across the (lexical) blocks of a method, but the inference stops at the method, if you assign to a name at the top-level (global) and assign to a name in a method you will create two different bindings.

Coffescript has no such duality, and as a result can't use it for a "watertight seal" around scoping.


Great examples, I agree that conflating declaration and assignment seems to be a mistake that different languages try to mitigate in different ways. Another example is in http://www.paulgraham.com/arclessons.html, "Implicit local variables conflict with macros."


New JavaScript features do just that:

2) Globals with 'x = 1' throws an error when run in strict mode.

3) They couldn't change the meaning of 'var', but they introduced 'let' which does just that - applies to a block rather than the whole function.


Thanks. I think this is the best comment in this discussion. If you hadn't written it, I would have sat down and done it.


Relevant is this blog post:

http://johnbender.us/2012/11/27/math-envy-and-coffeescripts-...

The gist of it is that Coffeescript's syntax is fragile, and a more careful design could avoid these issues.

Also, can we erase the word "transpile" from our vocabulary, please? It has no meaning beyond that of "compile" and so, like Coffescript's syntax, adds complexity and confusion where none is needed.


https://en.wikipedia.org/wiki/Source-to-source_compiler

Actually, it does have a meaning. Transcompilers are compilers that compile one language to another language at a similar level of abstraction.


The addition of the Let statement to ES6 makes this discussion moot, while bypassing coffeescript's sketchy globality.


> The addition of the Let statement to ES6 makes this discussion moot

I don't think it does. The JS broken behaviour described in the article is that if you happen to forget the "var" keyword when assigning a variable then you will create a global variable. "let" is great, but it doesn't fix that problem.


> The JS broken behaviour described in the article is that if you happen to forget the "var" keyword when assigning a variable then you will create a global variable.

Use strict mode, problem gone.

(alternatively, jshint/jslint or a good IDE will warn about implicit global declarations, it's not a very difficult pattern to check for)


CoffeeScript's "do" syntax replicates the let semantics now :-)


You should only need 'do' in the same place where you'd need it in javascript. (You're generating a closure that needs to close over variables that are likely to change, such as within a for loop..)

Otherwise, just name your variables sensibly! The smaller the scope, the terser the name is, the greater the scope the more verbose the name should be.

It's strange to me that anyone would miss block scoping or ever get 'bit' by this -feature-. I found it perfectly understandable after reading the docs.

I really can't imagine why someone would feel compelled to rely on shadowing.. why would you want to name two variables the same thing within a nested context? And why would you ever nest your contexts so deeply that you have literally no recollection of what goes on above?

I really can't imagine how people are structuring their source files if this is a problem. When would a closure every shadow a variable that ISN'T a parameter in the new context?

All the examples I've seen of this being a problem are contrived and brittle-looking.

I appreciate your writing on this stuff raganwald, but.. honestly, I think this a problem for sloppy programmers who don't jive with coffeescript being an opinionated language, and they'll disregard any advice you give them because they want the freedom to "safely" write hard to reason about code.


And google's traceur compiler allows you to use all of ES6 now without coffeescript too.

https://code.google.com/p/traceur-compiler/


but traceur is not compatible with IE8.


Nice article!

I have recently given CoffeeScript a good try and generally like it. I think I am going to stick with JavaScript, at least for now. I edit in IntelliJ with JSLint always running in the background. I believe that IntelliJ + JSLint give clear warnings for the issues raised in the article (the warnings for CoffeeScript coding errors were also very useful).


Very nice article. This is easily the biggest problem I have with CoffeeScript.


TL;DR: If you nest functions in CS, you should be aware that you are creating a closure. If you create a "global" variable in your file/module, you should know that it is global to your module.


Coffeescript is good in itself, I like the sugar it slaps on top of Javascript, but the real productivity improvement in my opinion is Underscore.js/LoDash, that's what really makes programming in Javascript/Coffeescript SO much nicer in my experience.


CoffeeScript has good use cases and we should talk about its benefits rather than unnecessary rants and negativity.


You definitely should never question anything, _especially_ if you're going to be negative about it!

Now pass the sunshine juice and follow me back to the drum circle.


I think you got me wrong here. I'm happy with any kind of discussions but getting stuck makes me unhappy. For example, 7 years ago, I thought PHP is really shitty and just switched to another language immediately. A few years later, switched to another.

I just think it's really pointless to discuss PHP or JavaScript or CoffeeScript in 2013. All of them are what they are and people should use them if they work for them.

Getting stuck with same discussion is just boring.




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

Search: