

Scoping in CoffeeScript and JavaScript - waffle_ss
http://raganwald.com/2013/07/27/Ive-always-been-mad.html

======
crazygringo
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.

~~~
Lazare
> 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. :)

~~~
ender7
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.

~~~
crazygringo
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_.

~~~
michaelwww
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...](https://en.wikipedia.org/wiki/Principle_of_least_astonishment)

------
jayferd
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.

~~~
masklinn
> 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
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?

~~~
masklinn
> 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
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.

------
michaelwww
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](http://donatstudios.com/CoffeeScript-Madness)

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

~~~
calibraxis
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](https://github.com/jashkenas/coffee-script/issues/2956)).

~~~
michaelwww
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.

~~~
virtualwhys
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].

~~~
happy_dino
> 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. :-)

------
boromi
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.

~~~
virtualwhys
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.

~~~
michaelwww
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.

~~~
virtualwhys
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.

~~~
michaelwww
> 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](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.

------
cousin_it
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.)

~~~
masklinn
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)

~~~
cousin_it
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...

~~~
masklinn
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.

~~~
cousin_it
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](http://www.paulgraham.com/arclessons.html),
"Implicit local variables conflict with macros."

------
noelwelsh
Relevant is this blog post:

[http://johnbender.us/2012/11/27/math-envy-and-
coffeescripts-...](http://johnbender.us/2012/11/27/math-envy-and-
coffeescripts-foibles/)

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.

~~~
raganwald
[https://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.

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

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

~~~
WalterSear
And 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
but traceur is not compatible with IE8.

------
mark_l_watson
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).

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

------
danmaz74
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.

------
VeejayRampay
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.

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

~~~
ryan-allen
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.

~~~
oakaz
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.

