Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> Obviously, plenty of folks managed to write bug-free code in Python before "nonlocal" was invented.

Python has the inverse behavior of CoffeeScript. So that was never an issue.



Well, fine, but Python2 had the inverse problem--you couldn't mutate top-level variables without awkward "global" statements.


That's a whole different matter, which can't make you shoot yourself in the foot without knowing it for any non-trivial code.

CoffeeScript's scoping forces you to always keep track of whatever is enclosing the current scope ALL THE WAY TO THE TOP. This is way too much when your function doesn't need to access outer variables (which should be the minority of the cases).

So, problem is, you either make all your functions have non-free variables (but Jashkenas seems to dislike functions shadowing outer variables too, which is just... overtly weird), or you keep track of all variables above the current scope.

The former is not too unreasonable, until you remember it makes no sense with closures :3


I actually agree with his stance against shadowing variables, just on philosophical grounds. It encourages good, descriptive naming. On the other hand, I acknowledge that sometimes it's desirable to shadow outer variables. I think it should be discouraged, but not prevented.

I think the big problem with Coffeescript's behavior is that it can introduce some damn subtle bugs that can be really hard to track down if you don't know what you're looking for, because you're not able to explicitly specify scope semantics. It's even worse if you're polluting higher-scope variables of the same type, because it becomes even less obvious where the error comes from.

Coffeescript more or less shares Ruby's scoping rules, but there's a cultural difference between the Ruby and Javascript communities that makes it a little less workable in Javascript. Specifically, Ruby's "everything is an object", aggressive use of namespacing, and the general idiom that only constants go into the global namespace tends to limit scope issues that could arise from mix-ins.

Coffeescript does attempt to mimic this by providing class semantics and wrapping everything in anonymous functions to limit scope leak, but there's still a lot of temptation to just create a bunch of top-level functions, and that leads to situations like the one described in the blog post.


The problem is actually worse. You also have to keep track of all the variables BELOW the current scope as well so you don't accidentally (as in the case of the article) turn a declaration into a reassignment.


yeah that's a sketch thing to have to worry about :s


You are correct that variable scoping goes all the way to the top, but nobody is forcing you to create top-level variables with overloaded names like "log". Seriously, if you have a file that uses log files and logarithms, just take some care to distinguish the concepts. Use "log_file" for log files; use "logarithm" or "Math.log" for inverse exponentation.


> Use "log_file" for log files; use "logarithm" or "Math.log" for inverse exponentation.

Just clarifying; are you actually suggesting this, or was that sarcasm?


It's a serious suggestion. Instead of introducing four top-level variables (log, sin, cos, and tan), just introduce one (Math) that wouldn't possibly conflict with a local.


The CoffeeScript sourcecode itself does not advocate it. It uses functions named like 'last', 'starts', 'ends', 'extend' etc.


You are correct about "last", "start's, etc., but there are also good examples in the CS sourcecode of easily avoiding naming conflicts with a sensible naming convention:

  browser.coffee:CoffeeScript = require './coffee-script'
  coffee-script.coffee:{Lexer,RESERVED} = require './lexer'
  coffee-script.coffee:    {Module} = require 'module'
  command.coffee:{EventEmitter} = require 'events'
  grammar.coffee:{Parser} = require 'jison'
  lexer.coffee:{Rewriter, INVERSES} = require './rewriter'
  nodes.coffee:{Scope} = require './scope'
  nodes.coffee:{RESERVED} = require './lexer'
  repl.coffee:{Script}     = require 'vm'


Yes, and it's very uncommon that you need to do that. I learned Scheme years ago before I learned Python (in 1993, I think). At first I was dismayed by Python's lack of non-local assignment. However, in years of Python programming I can count the times I needed the ability on one hand. I haven't yet found a need for 'nonlocal'.

Obviously it depends on programming style. In Coffeescript you don't have something akin to 'self' and so assigning to a non-local happens a lot.


> However, in years of Python programming I can count the times I needed the ability on one hand. I haven't yet found a need for 'nonlocal'.

Might have to do with Python itself as well: because it's function-scoped and it tends to avoid higher-order function (in part due to the limitations of its anonymous functions), there are far less occasions write to lexical closures than in Scheme, Smalltalk or Ruby.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: