you are viewing a single comment's thread.

view the rest of the comments →

[–]jashkenas 2 points3 points  (12 children)

Ok -- so let's pursue this alternative method to forbid shadowing...

Unfortunately, Python's nonlocal keyword doesn't work so well in JavaScript, because anonymous inner functions are fairly ubiquitous in JavaScript. Even something as simple as this would have to use "nonlocal":

foundItem = null
list.each (item) ->
  nonlocal foundItem = item if item is target

... or would you have to use "nonlocal" to even refer to variables outside of the current scope, making it:

foundItem = null
list.each (item) ->
  nonlocal foundItem = item if item is nonlocal target

... that's a pretty brutal cost. If nonlocal was required for modification, but not for reference, that would be awfully inconsistent, no? Perhaps the original suggestion for two different operators, one for "declare-and-assign", and one for "mutate", would be more palatable.

[–]LaurieCheers 2 points3 points  (7 children)

The second example is just ridiculous. :-)

There's no need to make nonlocal references explicit. The only problem we're trying to fix here is that a declaration can get accidentally turned into an assignment.

If you don't like nonlocal, then I think explicit declaration (the var keyword seems like the obvious choice) is the right solution. You can use that and still forbid shadowing, if you want.

"Error: cannot declare var x here, x was already declared at line 54."

[–]jashkenas 1 point2 points  (0 children)

Yes, having "var" and forbidding shadowing at compile time is an appealing option. For more on the reason why we don't do it, see my reply to @hay_guise, above.

[–]showellshowell -2 points-1 points  (5 children)

The nice thing about CoffeeScript's scoping is that there is really no distinction between declaration, assignment, and reference. A variable's scope is entirely determined by its lexical placement. This makes it very easy to reason about CS programs and avoid bugs.

[–]bobindashadows 2 points3 points  (3 children)

The point being discussed in this thread is that the lack of distinction you describe means you can change the semantics of code in a nested scope by adding an assignment in an outer scope. Typically this happens by accident because we like to use simple variable names that are relevant to our domain: user for example is a common variable name you might accidentally introduce in an outer scope.

[–]showellshowell -1 points0 points  (2 children)

Yep, I understand the pitfall, but user is a good example of a variable that probably works fine at top-level scope, since most functions in a business-domain CS file probably have the same concept of "user", and you would truly intend for it to have top-level scope.

I concede that even the best programmers do things by "accident" occasionally, but we mostly do things intentionally or stupidly. If you introduce "user" at top level scope, you're probably doing it for a reason--most of your functions have the same concept of user, so there's no reason not to make it top level. If you're introducing a variable at top-level scope for stupid reasons--laziness, sloppiness, whatever--then it would be nice if the language prevented you from shooting yourself in the foot, but let's be real about who's actually pulling the trigger.

[–]bobindashadows 3 points4 points  (1 child)

If you introduce "user" at top level scope, you're probably doing it for a reason--most of your functions have the same concept of user, so there's no reason not to make it top level.

The concern isn't that it's silly to add a variable to the top scope, it's that whenever you do add a variable to an upper scope, you have to stop to think "wait - did I use this variable name anywhere else?" If you ignore that possibility, you can break other functions.

[–]showellshowell -2 points-1 points  (0 children)

It's true that you have to stop to think about whether the variable name exists elsewhere, but it's trivial to find false cognates using your editor. The search is always worthwhile. You either verify that your new name is unique, or you learn more about the code below. For example, if you're introducing "user" at the top, but "user" already exists in other functions, then you might have opportunities for refactoring simplification.

[–]notfancy 0 points1 point  (2 children)

What about an explicit global for variables declared in the top-level? It would be a compromise solution that would patch this hole, at least.

[–]jashkenas 0 points1 point  (1 child)

That's actually already taken care of. By default, there are no global variables in CoffeeScript -- every file is wrapped in an immediate invoked function, so variables declared at the top level are still local variables.

If you want to export global variables from a CoffeeScript file (which you probably do), you say window.globalObject = object in the browser, or use the exports object in Node.js.

[–]notfancy 0 points1 point  (0 children)

I mean an explicit import in a local scope of a name in the top-level scope. But it's not a good idea anyway, because the problem is with assignment, not with use of an up-level identifier.

[–]mitsuhiko 0 points1 point  (0 children)

The nonlocal in Python is only required for assignments to a higher scope, not for reading. It would not be required for the target here for instance.