Scoping in CoffeeScript and JavaScript
raganwald.com
raganwald.com
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.
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. :)
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)
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.
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.
[1] https://en.wikipedia.org/wiki/Principle_of_least_astonishmen...
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.
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).
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]
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.
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.
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.
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.
[1] http://donatstudios.com/CoffeeScript-Madness
[2] http://www.reddit.com/r/programming/comments/1j1cw7/coffeesc...
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.
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 :)
[1] Like representing datastructures. Or that subclass-without-constructor subtlety (https://github.com/jashkenas/coffee-script/issues/2956).
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].
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. :-)
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.
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.
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.
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.
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.
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)
http://blog.bolinfest.com/2013/01/generating-google-closure-...
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.)
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)
Now I wonder what kind of fix the CoffeeScript folks will make, when they admit the problem...
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.
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.
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.
Actually, it does have a meaning. Transcompilers are compilers that compile one language to another language at a similar level of abstraction.
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.
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)
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.
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).
Now pass the sunshine juice and follow me back to the drum circle.
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.