9 ms·
Scoping in CoffeeScript and JavaScript
- michaelwww 13y agoThis 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 http://donatstudios.com/CoffeeScript-Madness [2] http://www.reddit.com/r/programming/comments/1j1cw7/coffeescripts_scoping_is_madness/ http://www.reddit.com/r/programming/comments/1j1cw7/coffeesc...
- calibraxis 13y agoPersonally, 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 https://github.com/jashkenas/coffee-script/issues/2956).
- michaelwww 13y agoI 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.
- virtualwhys 13y agoCoffee 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].
- happy_dino 13y ago> 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. :-)
- plorkyeran 13y agoCoffeeScript 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.
- mambodog 13y agoI'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.
- mattdeboard 13y agoIn 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.
- crazygringo 13y agoSee 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 :)
- epidemian 13y agoI 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 :)
- WalterSear 13y agoThe addition of the Let statement to ES6 makes this discussion moot, while bypassing coffeescript's sketchy globality.
- raganwald 13y agoCoffeeScript's "do" syntax replicates the let semantics now :-)
- WalterSear 13y agoAnd google's traceur compiler allows you to use all of ES6 now without coffeescript too. https://code.google.com/p/traceur-compiler/ https://code.google.com/p/traceur-compiler/
- camus 13y agobut traceur is not compatible with IE8.
- likeclockwork 13y agoYou 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.
- jayferd 13y agoI 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.
- masklinn 13y ago> 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.
- jayferd 13y agoHm, 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?
- masklinn 13y ago> 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.
- jayferd 13y agoJust 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.
- mark_l_watson 13y agoNice 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).
- boromi 13y agoI 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.
- virtualwhys 13y agoCame 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.
- michaelwww 13y agoI'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.
- virtualwhys 13y agoBy 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.
- sluukkonen 13y agoVery nice article. This is easily the biggest problem I have with CoffeeScript.
- crazygringo 13y agoWow... 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.
- ricardobeat 13y ago> 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.
- rubiquity 13y agoWait, 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.
- Lazare 13y ago> 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. :)
- oakaz 13y agoCoffeeScript has good use cases and we should talk about its benefits rather than unnecessary rants and negativity.
- ryan-allen 13y agoYou 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.
- oakaz 13y agoI 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.
- noelwelsh 13y agoRelevant is this blog post: http://johnbender.us/2012/11/27/math-envy-and-coffeescripts-foibles/ 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.
- deleted 13y ago[deleted]
- deleted 13y ago[deleted]
- raganwald 13y agohttps://en.wikipedia.org/wiki/Source-to-source_compiler 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.
- danmaz74 13y agoTL;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.
- VeejayRampay 13y agoCoffeescript 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.
- cousin_it 13y agoBoth 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.)
- masklinn 13y ago1. 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)
- cousin_it 13y agoThanks! 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...
- masklinn 13y agoI 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.