12 ms·
The Problem with Implicit Scoping in CoffeeScript
- rayiner 15y agoScheme got this right in 1970. There is no excuse to design a new language this way.
- tikhonj 15y agoI agree with this wholeheartedly--Scheme use an extremely simple scoping model that is nonetheless more expressive than Python (before 3, I guess) and CoffeeScript's. In fact, I only really completely understood JavaScript's model--and realized that, even if a little awkward, it was fundamentally elegant--after writing a Scheme interpreter.
- wisty 15y agoI think Python's is deliberately unexpressive, forcing you to use local variables pretty much everywhere. This tends to decouple stuff, which is usually good. The model is "everything you do is local, unless you know better, and want to jump through a lot of hoops". Coffeescript works a similar way. Javascript seems to have the model "everything you do is global, unless you know better". I'm not a big Javascript hater, but this is one very sore point.
- swannodette 15y ago"everything you do is global, unless you know better" That is not the JavaScript model at all.
- lnanek 15y agoHe is talking about if you don't do anything extra (in this case, use the var keyword). Much like the nonlocal keyword in Python requires knowing about it and wanting to use it in order to actually use it and get the non-default behavior.
- swannodette 15y agovar is not extra in JavaScript the way nonlocal is in Python. var is a language construct that any good JavaScript program must use.
- Lazare 15y agoThe point of the linked article is that this is not the CoffeeScript way at all (although the author wishes it was).
- whateverer 15y agoNo, the Javascript model is "you name things with `var`, you mutate things with `=`". Python went for a more complicated scheme of having every variable in a function be a new binding when first assigned, and then it's just mutation (i.e. 'local'), unless explicitly declared to be nonlocal, but it still makes sense with their... penchant for mutable state. And Coffescript instead went over to PHP to have some of the glue it was eating.
- Someone 15y agoNo, the Javascript model is "you name things with `var`, you mutate things with `=`". If only it were that simple. It also has "you create thing with `var` and sometimes with `=`", plus it does not have block scope: if( true) { var x = 1; } // x == 1 here.
- ryanflorence 15y agoFunctions define scope. The end. Not hard. One rule.
- rayiner 15y ago'let' is a way to make things more local! When I say: let x = 0, I'm saying "hey, I don't care what 'x' is in other scopes, in this local scope it's 10, dammit!"
- draegtun 15y agoYes explicit is best and in Perl its very clear what you want... my $x = 10;
- abecedarius 15y agoScheme dates to about 1975. But there were older languages with this kind of scoping, like John Reynolds' Gedanken and Landin's ISWIM. (I guess I wouldn't count Algol-60 since it was call-by-name.)
- showell30 15y agoCoffeeScript's approach toward top-level variables is quite elegant and simple. When you declare a variable at top-level scope, it is equally available to all code within that file for both reading and writing, with no strange "nonlocal" or ":=" syntax to complicate manners. Once you understand the reach of CoffeeScript's top-level variables, it is easy to write bug-free code. Since you know that top-level variables have wide scope, you simply need to be judicious about putting variables at top-level scope. If a variable is not needed at top level scope, don't put it there.
- the_mitsuhiko 15y agoWay to miss the issue. I was not suggesting ":=" for global variables at all. I was suggesting ":=" as a replacement for the nonlocal keyword that Python 3 has to solve the issue I was demonstrating.
- showell30 15y agoNope, I get it, you were suggesting ":=" as a replacement for the "nonlocal" keyword in Python. I'm not sure how my comments demonstrate any lack of reading comprehension. All I said about ":=" and "nonlocal" is that they are overly complicated once you accept that top-level variables can have wide scope. Obviously, plenty of folks managed to write bug-free code in Python before "nonlocal" was invented. I'm not saying it's a bad idea, but you can avoid bugs without it.
- the_mitsuhiko 15y ago> 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.
- showell30 15y agoWell, fine, but Python2 had the inverse problem--you couldn't mutate top-level variables without awkward "global" statements.
- oinksoft 15y ago@mitsuhiko Not gonna happen ;) Forbidding shadowing altogether is a huge win, and a huge conceptual simplification. How arrogant! You'd think he'd step back for a second and consider the suggestion, but it sounds like he's on autopilot.
- latchkey 15y agoIt sounds like he had 140 characters to succinctly say what he wanted to say.
- statictype 15y agoEveryday there seems to be a new post on HN, complete with inflammatory headline, criticizing Coffeescript in someway because it doesn't work in the exact way the author expected. There are lots of people throwing in their $0.02 on how the language should work without having joined the mailing list or seen any discussions on the thought process behind its features. I'm not saying the suggestion made isn't reasonable, but I can understand glib replies like this from the author that don't make too much effort to explain his stance more than 140 characters.
- showell30 15y agoIn fairness, Armin (the author the blog post) did engage Jeremy (the author of coffeescript) on this issue over twitter. I wish that he would allow comments on his blog. I also wish that he could engage the broader CS community in a proper forum before dissing the language and/or creating a fork of the language. He's overreacting. This whole blog post apparently started because he had a naming collision with "log" in one of his programs. CS does have a mailing list, but most of the action happens via github issues.
- the_mitsuhiko 15y ago> I wish that he would allow comments on his blog. Why? Hackernews and reddit exist and everybody is free to send me a mail or contact me on twitter. This way I do not have to moderate any comments or deal with spam. > I also wish that he could engage the broader CS community in a proper forum before dissing the language and/or creating a fork of the language. And do what? Duplicating an issue that is already there? Commenting on a dead issue? The author has expressed his unwillingness to deal with this issue so why should I reopen the issue there? > He's overreacting. How am I? I wrote a very short blog post about why I think the scoping is bad and how it caused me problems. Many people asked me on Twitter why I think the scoping does not work as good as it should and since I only have 140 characters to explain stuff there I wrote it to my blog. How else should I communicate that? > CS does have a mailing list, but most of the action happens via github issues. There is an issue about this topic from a year ago which was closed and the author does not want this to be changed. I am okay with that, a language needs leadership. That does not mean however that other people should not know about this issue when they design the next programming language.
- latchkey 15y agoKind of a side note to the posting, but I just have to say: Please make your usage of parens consistent. If you aren't going to use them, don't use them everywhere. Here is an example of what I'm talking about: if isAir cx, cy, cz + 1 then addPlane('near', block) Should be: if isAir cx, cy, cz + 1 then addPlane 'near', block Personally, I use them everywhere because I like having the stronger visual clue that this is a method I'm calling. I think making them optional in CS was a bad idea. if isAir(cx, cy, cz + 1) then addPlane('near', block) imho, so much more readable.
- MatthewPhillips 15y agoNot allowing parens would mess up the usage of anonymous functions.
- latchkey 15y agoNot sure I understand you or maybe you don't understand me. I was talking about using parens for method/functional calls, not getting rid of them from CS.
- MatthewPhillips 15y agoI understand. If you don't have parens on method calls, how could you use an anonymous function that is not the last parameter? For example: doSomething(-> # stuff happens ), onerror
- latchkey 15y agoI totally agree with you.
- jacobolus 15y agoFirst, your code snippet is wrong: you need a space before your first paren. In coffeescript you can do any of the following, or a few other variants: doSomething -> stuff , onerror doSomething(-> stuff , onerror ) doSomething (-> stuff ), onerror doSomething( -> stuff onerror ) doSomething (-> stuff), onerror
- leafo 15y agoLook at how MoonScript handles this: http://moonscript.org/reference/#the_using_clause_controlling_destructive_assignment http://moonscript.org/reference/#the_using_clause_controllin... I didn't want to change the default semantics, but I wanted to have a way for the programmer to be safe if they wanted to, so I created the `using` keyword for function declarations. You explicitly declare what you intend to overwrite in the lexical scope, including overwriting nothing at all with `using nil`.
- showell30 15y agoFWIW this is how CS works: top_level_variable = null f = -> top_level_variable = "hello" f() console.log top_level_variable # prints hello
- cheald 15y agoThe concern is that because Coffeescript automatically scopes variables to the scope of their first reference, it can introduce maintenance issues. Consider the following: foo = -> bar = "woot!" console.log bar This compiles to: var foo; foo = function() { var bar; bar = "woot!"; return console.log(bar); }; bar is locally scoped to foo(). Now, 2 weeks later and 200 lines earlier, you come along and define: bar = -> alert "Holy crap cheese is awesome!" Which compiles to: var bar, foo; bar = function() { return alert("Holy crap cheese is awesome!"); }; foo = function() { bar = "woot!"; return console.log(bar); }; Now, all of a sudden, the "bar" reference in foo isn't scoped to foo() anymore, it's scoped globally, and once you invoke foo(), it'll replace the function bar with a string, potentially breaking your app. It's an ease-of-maintenance issue. This isn't consistent behavior, though. If you define your top-level bar() function after foo, like so: foo = -> bar = "woot!" console.log bar bar = -> alert "Holy crap cheese is awesome!" Then you get "correct" scoping (and the outer bar is shadowed): var bar, foo; foo = function() { var bar; bar = "woot!"; return console.log(bar); }; bar = function() { return alert("Holy crap cheese is awesome!"); }; On one hand, it could be argued that this is a "name things better" problem, but on the other, I have to agree that it'd be nice to be able to explicitly scope things when needed. Given that the behaviors are divergent based on what order the variables appear in, I'd say it's confusing enough that a way to explicitly say "hey, I know what I'm doing, I want to shadow any outer variables and declare local scope here" would be useful.
- mutewinter 15y agoThis is a better explanation of the problem than the blog post. Thanks!
- satyr 15y ago
- tjholowaychuk 15y agoI like the look of coffeescript's assignment better, but I cant help but think "let" is much less ambiguous, once you see it you look no further. This reminds me a bit of Ruby, where "foo" could be a function or variable potentially from anywhere so it's a little unclear although better looking.
- stoodder 15y agoWhy not allow CoffeeScript to use the 'var' keyword, explicitly telling CS that this variable should be scoped locally even though it may be shadowing another variable? This seems consistent with their approach of allowing (although optional) native javascript syntax such as {}, and []. This still allows CS to stick to it's paradigm of forbidding shadowing unless we explicitly state that we know what we're doing.
- jashkenas 15y agoBecause we're aiming for a conceptual simplification. Pretend like you're a beginner, learning this stuff for the first time. If everywhere you see a variable "A", within a certain lexical scope, it means the same thing ... that's much simpler to understand than if "A" means three different things at three different places, because you happened to shadow it twice.
- stoodder 15y agoYea, I completely get what you're saying and for the most part agree. To be frank, I think the author of the article made an error in deconstructing on the 'Math' object (at least at a global scope). 'Math' provides a namespace for all of its methods and a similar approach should be taken to other libraries/pieces of code. I also agree that keeping things simple and straight forward makes sense, but you're doing it by forcing one to abide by those standards although someone might have completely legitimate reasons for explicitly scoping their variables. Either way, I think it is what it is and the benefits of CS very much outweigh the cons. Thanks for the feedback
- gerggerg 15y agoConsidering we won't see this changed since the author has already closed the issue and expressed his satisfaction with the current rules this article should at least serve as a reminder for errors not to repeat with the next language someone designs. It's open source. Why not fork it and get some like minded coders to change it with you?
- deleted 15y ago[deleted]
- scribu 15y agoThere already is a fork that changes this (Coco), already mentioned in another comment: http://news.ycombinator.org/item?id=3380423 http://news.ycombinator.org/item?id=3380423
- limeblack 15y agoAnother Issue: Although the following examples could become unambiguous with parenthesis, these examples demonstrates how a trivially overlooked ending delimiter further complicated the language. Not only is the intent of the CoffeScript code unclear in the examples below but the slight variation in the CoffeScript, produces radically different output. The CoffeeScript differences are so small it would be easy for someone to add accidentally while editing. Anonymous function passing and function calling in Javascript require no additional wrappers or edits, while in CoffeeScript you must add special case clarity. http://img542.imageshack.us/img542/7379/coffeescripttojavascrip.png http://img542.imageshack.us/img542/7379/coffeescripttojavasc...
- gcv 15y agoIt's worth pointing out that JavaScript 1.7 resolves this mess by introducing block scoping using the "let" keyword. It works just like it does in Scheme, Common Lisp, and Clojure (i.e., correctly). Not supported in anything except Firefox, unfortunately.
- swannodette 15y ago... and Standard ML, OCaml, Haskell, Smalltalk, etc
- draegtun 15y ago... and Perl except its called my instead of let
- buddydvd 15y agoI found one of the referenced links in Github issue #712 quite interesting: http://www.rubyist.net/~matz/slides/rc2003/mgp00010.html http://www.rubyist.net/~matz/slides/rc2003/mgp00010.html Source: https://github.com/jashkenas/coffee-script/issues/712#issuecomment-979127 https://github.com/jashkenas/coffee-script/issues/712#issuec...
- showell30 15y agoThese links explain the thought process behind CS's current behavior: https://github.com/jashkenas/coffee-script/issues/712#issuecomment-430673 https://github.com/jashkenas/coffee-script/issues/712#issuec... https://github.com/jashkenas/coffee-script/issues/712#issuecomment-760853 https://github.com/jashkenas/coffee-script/issues/712#issuec...
- perfunctory 15y agoThis is my biggest problem with CoffeeScript. And the author stubbornly refuses to fix it. Apparently it's some sort of Ruby religion.
- danmaz74 15y agoWith "var" and shadowing you can still shoot yourself in the foot, it's just the other foot. If you need global variables, it's sensible to just adopt a simple naming convention, like prepending g_ (or whatever pleases you) to all your variables. I already did that with plain JS and it's well worth the "effort".
- mhansen 15y agoI'll copy below jashkenas' longer answer from the old github issue about this, https://github.com/jashkenas/coffee-script/issues/712#issuecomment-430673 https://github.com/jashkenas/coffee-script/issues/712#issuec... """ Sorry, folks, but I'm afraid I disagree completely with this line of reasoning -- let me explain why: Making assignment and declaration two different "things" is a huge mistake. It leads to the unexpected global problem in JavaScript, makes your code more verbose, is a huge source of confusion for beginners who don't understand well what the difference is, and is completely unnecessary in a language. As an existence proof, Ruby gets along just fine without it. However, if you're not used to having a language without declarations, it seems scary, for the reasons outlined above: "what if someone uses my variable at the top of the file?". In reality, it's not a problem. Only the local variables in the current file can possibly be in scope, and well-factored code has very few variables in the top-level scope -- and they're all things like namespaces and class names, nothing that risks a clash. And if they do clash, shadowing the variable is the wrong answer. It completely prevents you from making use of the original value for the remainder of the current scope. Shadowing doesn't fit well in languages with closures-by-default ... if you've closed over that variable, then you should always be able to refer to it. The real solution to this is to keep your top-level scopes clean, and be aware of what's in your lexical scope. If you're creating a variable that's actually a different thing, you should give it a different name. Closing as a wontfix, but this conversation is good to have on the record. """
- the_mitsuhiko 15y ago> As an existence proof, Ruby gets along just fine without it. Missing that Ruby stops scoping variables at a method and uses separate lexical scoping rules for constants thereby avoiding this issue mostly.
- cheald 15y agoAlso, Ruby can shadow method names with local variable names just fine: class Foo def bar "bar" end def baz bar = "foo" puts bar end def bang puts bar end end foo = Foo.new foo.baz # => "foo" foo.bang # => "bar"
- cvshepherd 15y ago> The simple solution is to either add a nonlocal keyword like Python has or to introduce a := parameter that works like = but explicitly overrides a higher level variable. I disagree. The simple solution to this is to write tests.
- Deestan 15y ago> I disagree. The simple solution to this is to write tests. That's a step backwards. Code error checking should be done as early as possible. In order of earliness: * Typing in the code. (Ideal: it is clear from the syntax that the code performs X instead of Y.) * Compiling. (Strong type checking ensures you cannot return 5.3e7 or null from GetHostName.) * Running the code at all. (Code contracts and assertions trigger if GetHostName returns "".) * Automated unit tests. (Check that DB.GetHostName() returns the same string given to DB.Connect().) * Automated integration tests. (Check that the DB module can connect to and retrieve useful data from a dummy database.) * QA ("Hey Joe, the system hangs when I give "¤;\@" as my username and press the connect button rapidly for a few seconds.") * Customer ("Hi the system has a problem, please fix.") The further up, the faster, more accurately and with less "noise" the error can be discovered.
- shaunxcode 15y agoHere is a ghetto "let" form in coffee ((a = 5, b = 6, log = x -> console.log x) -> log a + b)()