I've wanted to switch to Coffeescript for a while now and yesterday I thought I'm finally sold but then I stumbled across Armin Ronachers article on shadowing in Coffeescript.
Coffeescript indeed now abandoned shadowing, an example of that problem would be if you use the same iterator for nested loops.
var arr, hab, i;
arr = [[1, 2], [1, 2, 3], [1, 2, 3]];
for(var i = 0; i < arr.length; i++){
var subArr = arr[i];
(function(){
for(var i = 0; i < subArr.length; i++){
console.log(subArr[i]);
}
})();
}
Because cs only declares variables once I wouldn't be able to do this within coffeescript
Shadowing has been intentionally removed and I'd like to understand why the cs-authors would want to get rid of such a feature?
Update: Here is a better example of why Shadowing is important, derived from an issue regarding this problem on github
PS: I'm not looking for an answer that tells me that I can just insert plain Javascript with backticks.
Variable shadowing is not an error syntactically. It is valid and well defined. However, if your intention was to use the variable from the outer scope, then you could consider it a logical error.
CoffeeScript is a programming language that compiles to JavaScript. It adds syntactic sugar inspired by Ruby, Python, and Haskell in an effort to enhance JavaScript's brevity and readability. Specific additional features include list comprehension and destructuring assignment.
In computer programming, variable shadowing occurs when a variable declared within a certain scope (decision block, method, or inner class) has the same name as a variable declared in an outer scope. At the level of identifiers (names, rather than variables), this is known as name masking.
Basically, CoffeeScript was made by people who hate JavaScript syntax, intended to be used by other people who hate JavaScript syntax. It's compatible with all flavors of JavaScript, and adds in “syntactic sugar” from other programming languages in the same way you sweeten up your morning cup of Joe – hence the name.
If you read the discussion on this ticket, you can see Jeremy Ashkenas, the creator of CoffeeScript, explaining some of the reasoning between forbidding explicit shadowing:
We all know that dynamic scope is bad, compared to lexical scope, because it makes it difficult to reason about the value of your variables. With dynamic scope, you can't determine the value of a variable by reading the surrounding source code, because the value depends entirely on the environment at the time the function is called. If variable shadowing is allowed and encouraged, you can't determine the value of a variable without tracking backwards in the source to the closest var variable, because the exact same identifier for a local variable can have completely different values in adjacent scopes. In all cases, when you want to shadow a variable, you can accomplish the same thing by simply choosing a more appropriate name. It's much easier to reason about your code if a local variable name has a single value within the entire lexical scope, and shadowing is forbidden.
So it's a very deliberate choice for CoffeeScript to kill two birds with one stone -- simplifying the language by removing the "var" concept, and forbidding shadowed variables as the natural consequence.
If you search "scope" or "shadowing" in the CoffeeScript issues, you can see that this comes up all the time. I will not opine here, but the gist is that the CoffeeScript Creators believe it leads to simpler code that is less error-prone.
Okay, I will opine for a little bit: shadowing doesn't matter. You can come up with contrived examples that show why either approach is better. The fact is that, with shadowing or not, you need to search "up" the scope chain to understand the life of a variable. If you explicitly declare your variables ala JavaScript, you might be able to short-circuit sooner. But it doesn't matter. If you're ever unsure of what variables are in scope in a given function, you're doing it wrong.
Shadowing is possible in CoffeeScript, without including JavaScript. If you ever actually need a variable that you know is locally scoped, you can get it:
x = 15
do (x = 10) ->
console.log x
console.log x
So on the off-chance that this comes up in practice, there's a fairly simple workaround.
Personally, I prefer the explicitly-declare-every-variable approach, and will offer the following as my "argument":
doSomething = ->
...
someCallback = ->
...
whatever = ->
...
x = 10
...
This works great. Then all of a sudden an intern comes along and adds this line:
x = 20
doSomething = ->
...
someCallback = ->
...
whatever = ->
...
x = 10
...
And bam, the code is broken, but the breakage doesn't show up until way later. Whoops! With var
, that wouldn't have happened. But with "usually implicit scoping unless you specify otherwise", it would have. So. Anyway.
I work at a company that uses CoffeeScript on the client and server, and I have never heard of this happening in practice. I think the amount of time saved in not having to type the word var
everywhere is greater than the amount of time lost to scoping bugs (that never come up).
Since writing this answer, I have seen this bug happen two times in actual code. Each time it's happened, it's been extremely annoying and difficult to debug. My feelings have changed to think that CoffeeScript's choice is bad times all around.
Some CoffeeScript-like JS alternatives, such as LiveScript and coco, use two different assignment operators for this: =
to declare variables and :=
to modify variables in outer scopes. This seems like a more-complicated solution than just preserving the var
keyword, and something that also won't hold up well once let
is widely usable.
The main issue here isn't shadowing, its CoffeeScript conflating variable initialization and variable reassignment and not allowing the programmer to specify their intent exactly
When the coffee-script compiler sees x = 1
, it has no idea whether you meant
I want a new variable, but I forgot I'm already using that name in an upper scope
or
I want to reassign a value to a variable I originally created at the top of my file
This is not how you forbid shadowing in a language. This is how you make a language that punishes users who accidentally reuse a variable name with subtle and hard to detect bugs.
CoffeeScript could've been designed to forbid shadowing but keep declaration and assignment separate by keeping var
. The compiler would simply complain about this code:
var x = blah()
var test = ->
var x = 0
with "Variable x already exists (line 4)"
but it would also complain about this code:
x = blah()
test = ->
x = 0;
with "Variable x doesn't exist (line 1)"
However, since var
was removed, the compiler has no idea whether you meant meant "declare" or "reassign" and can't help.
Using the same syntax for two different things is not "simpler", even though it may look like it is. I recommend Rich Hickey's talk, Simple made easy where he goes in depth why this is so.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With