Live data from Hacker News

The Problem with Implicit Scoping in CoffeeScript

lucumr.pocoo.org

121–130 of 137 posts

Re: The Problem with Implicit Scoping in CoffeeScript

#121
post #90

I'll copy below jashkenas' longer answer from the old github issue about this, 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 hug…

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

Also, 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"

Re: The Problem with Implicit Scoping in CoffeeScript

#122
post #121

Earlier quoted context omitted.

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

Also, 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"

Well, this is a case where the equivalent CS behaves exactly like Ruby.

  class Foo
    bar: -> 'bar'
    baz: ->
      bar = "foo"
      console.log bar
    bang: ->
      console.log @bar()

  foo = new Foo()
  foo.baz()  # => "foo"
  foo.bang() # => "bar"

Re: The Problem with Implicit Scoping in CoffeeScript

#123
post #121

Earlier quoted context omitted.

Also, 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"

Well, this is a case where the equivalent CS behaves exactly like Ruby. class Foo bar: -> 'bar' baz: -> bar = "foo" console.log bar bang: -> console.log @bar() foo = new Foo() foo.baz() # => "foo" foo.bang() # => "bar"

Not quite; @bar is "this.bar", and is an unambiguous reference, not just "bar". In Ruby, because self can be implicit, it's literally just "bar" in both cases, and Ruby gives the local variable precedence over the method name. You can still invoke the method via self.bar, but accessing the method as just "bar" is shadowed by the variable. Additionally, even though the method may be invoked by referencing bar, you can't redefine the method by assigning a new method to bar. Neither Javascript nor Coffeescript has that behavior.

Here's a better example:

Ruby:

    def bar
      "I called bar!"
    end

    def foo
      puts bar
      bar = "I manually assigned bar"
      return bar
    end

    puts foo()
    puts bar()

    # =>

    I called bar!
    I manually assigned bar
    I called bar!
Coffeescript:

    bar = ->
      "I called bar!"

    foo = ->
      console.log bar()
      bar = "I manually assigned bar!"
      return bar

    console.log foo()
    console.log bar()

    # =>

    I called bar!
    I manually assigned bar!
    TypeError: string is not a function

Re: The Problem with Implicit Scoping in CoffeeScript

#124
post #123

Earlier quoted context omitted.

Well, this is a case where the equivalent CS behaves exactly like Ruby. class Foo bar: -> 'bar' baz: -> bar = "foo" console.log bar bang: -> console.log @bar() foo = new Foo() foo.baz() # => "foo" foo.bang() # => "bar"

Not quite; @bar is "this.bar", and is an unambiguous reference, not just "bar". In Ruby, because self can be implicit, it's literally just "bar" in both cases, and Ruby gives the local variable precedence over the method name. You can still invoke the method via self.bar, but accessing the method as just "bar" is shadowed by the variable. Additionally, even though the method may be invoked by referencing bar, you can…

When I said the two programs have the same behavior, I was simply referring to the end result--what they output. I understand how both languages work here. Ruby solves a problem that simply doesn't exist in CS. In JS/CS "bar" never implicitly refers to "this.bar", so there's no ambiguity in the first place.

Re: The Problem with Implicit Scoping in CoffeeScript

#125
post #123

Earlier quoted context omitted.

Well, this is a case where the equivalent CS behaves exactly like Ruby. class Foo bar: -> 'bar' baz: -> bar = "foo" console.log bar bang: -> console.log @bar() foo = new Foo() foo.baz() # => "foo" foo.bang() # => "bar"

Not quite; @bar is "this.bar", and is an unambiguous reference, not just "bar". In Ruby, because self can be implicit, it's literally just "bar" in both cases, and Ruby gives the local variable precedence over the method name. You can still invoke the method via self.bar, but accessing the method as just "bar" is shadowed by the variable. Additionally, even though the method may be invoked by referencing bar, you can…

Your example does demonstrate how CS works. When you assign bar a new value inside its original scope, the value of bar does indeed change.

  bar = ->
    "I called bar!"

  foo = ->
    console.log bar()
    bar = "I manually assigned bar!"
    return bar

  console.log foo()
  console.log bar()
  
Here is a slightly less contrived example:

  log = (data) ->
    console.log data
    
  enhance_logging = ->
    i = 0
    log = (data) ->
      i += 1
      console.log i, data

  log "no line numbers"
  enhance_logging()
  log "line one"
  log "line two"
  
Here is the output:

  > coffee foo.coffee 
  no line numbers
  1 'line one'
  2 'line two'

Re: The Problem with Implicit Scoping in CoffeeScript

#127

Earlier quoted context omitted.

You haven't said anything new to me here; I took all this for granted and it isn't what I'm criticizing. You haven't actually addressed my summary of the apparent reasoning for why accidental assignments to top-level symbols isn't a pathology of CoffeeScript. The bit I'm criticizing is the failure mode of "if you happen to try to use the same name for two different things within the same lexical scope, it won't work"…

I see. The reason I can't address your "failure mode" with an explicit suggestion is because it isn't a failure mode in CoffeeScript: If you assign a new value to a variable in an inner scope, the variable now has a new value. If you're thinking "I want to use the same name for two different variables" ... the answer is: choose a different (better) name for one of them. But perhaps I'm still not getting at the answer…

You keep stating and restating how and why CoffeeScript doesn't support shadowing of symbols via lexical scoping.

That's not what I'm criticizing. Lexical shadowing is not what I'm advocating. I think your choice is fine in so far as it goes. It has its logic.

What I am criticizing is how it can fail. The top scope is different, quantitatively and qualitatively, from almost all nested scopes. It's much larger, and spread lexically over a larger area. If you have a team of developers, it will be modified concurrently. No one developer necessarily knows the full set of symbols defined in the top scope while they are writing an individual procedure.

And thus the problem: a developer thinks they've chosen a "different (better) name" for a some variable, but in fact they've chosen one that a different developer also thought was a "different (better) name", only one of them is in a lexically enclosing scope. This problem isn't likely to occur on the level of nested procedures or nested blocks, because the definitions would be visually close. But it's much more likely to happen when one of the symbols is defined in the top scope. Here, the definition could be many hundreds or thousands of lines away. It may even be in a separate commit, waiting to be merged, such that there's no way for either developer to know without closely reviewing every change.

And this is the criticism: the failure mode for this inadvertent reuse of a variable name is subtle bugs, as what one developer thought was a global symbol turns out to be modified and acquire strange values through unexpected codeflow, almost like the VM was corrupted and memory was behaving unreliably.

The qualitative difference of the top scope in situations like this is the reason why I suggested sigils or somesuch to disambiguate those scenarios. Perhaps top-level symbols can't be reassigned from nested scopes unless you use '$' as a prefix to their name; a visual shorthand that you are definitely not creating a new local symbol.

The reason I summarized your argument in the way I did is because your argument against this failure mode seems to be "don't create top scopes with lots of symbols". That's a fine argument (or rather, exhortation), but it isn't a realistic one. If the language is problematic with lots of symbols in the top scope, it should be unpleasant to use with lots of symbols in the top scope. And the unpleasantness shouldn't come from subtle bugs (the passive aggressiveness I mentioned); it should come from awkward and ugly sigils, or some other intrinsic way of discouraging those styles.

Re: The Problem with Implicit Scoping in CoffeeScript

#128

Earlier quoted context omitted.

I see. The reason I can't address your "failure mode" with an explicit suggestion is because it isn't a failure mode in CoffeeScript: If you assign a new value to a variable in an inner scope, the variable now has a new value. If you're thinking "I want to use the same name for two different variables" ... the answer is: choose a different (better) name for one of them. But perhaps I'm still not getting at the answer…

You keep stating and restating how and why CoffeeScript doesn't support shadowing of symbols via lexical scoping. That's not what I'm criticizing. Lexical shadowing is not what I'm advocating. I think your choice is fine in so far as it goes. It has its logic. What I am criticizing is how it can fail. The top scope is different, quantitatively and qualitatively, from almost all nested scopes. It's much larger, and sp…

barrkel, I think you should create a new programming language with more sensible scoping than CoffeeScript, so that developers can create bug-free code while working in parallel on thousand-line codebases without close reviews. I would start by using "$" as a sigil to prevent top-level variables from being reassigned in nested scopes. It would be a visual shorthand that indicates that you are definitely not creating a new local symbol. This would prevent developers from needing to know the full set of symbols in the top scope. In fact, by eliminating the inadvertent reuse of variable names, you would eliminate the failure mode whereby what one developer thought to be a global symbol actually turned out to be modified. Eliminating this unexpected codeflow would be tantamount to eliminating memory corruptions in VM.

You should definitely go down this track. It would be a massive achievement.

Re: The Problem with Implicit Scoping in CoffeeScript

#129

Earlier quoted context omitted.

You keep stating and restating how and why CoffeeScript doesn't support shadowing of symbols via lexical scoping. That's not what I'm criticizing. Lexical shadowing is not what I'm advocating. I think your choice is fine in so far as it goes. It has its logic. What I am criticizing is how it can fail. The top scope is different, quantitatively and qualitatively, from almost all nested scopes. It's much larger, and sp…

barrkel, I think you should create a new programming language with more sensible scoping than CoffeeScript, so that developers can create bug-free code while working in parallel on thousand-line codebases without close reviews. I would start by using "$" as a sigil to prevent top-level variables from being reassigned in nested scopes. It would be a visual shorthand that indicates that you are definitely not creating…

I think you think you're being sarcastic. But I do actually work for a developer tools company, and I implement language features on a continuous basis. I implemented anonymous methods - closures - for Delphi, a task that involves no small amount of scope wrangling; and FWIW, Delphi almost certainly has a larger userbase than CoffeeScript, albeit one with greyer hairs. While my specialism is in statically typed, compiled languages, issues around lexically nested scope are pretty much the same as in dynamic languages.

If you want to have a constructive conversation, you could try dialing back the snark, and addressing my arguments directly.

Re: The Problem with Implicit Scoping in CoffeeScript

#130

Earlier quoted context omitted.

barrkel, I think you should create a new programming language with more sensible scoping than CoffeeScript, so that developers can create bug-free code while working in parallel on thousand-line codebases without close reviews. I would start by using "$" as a sigil to prevent top-level variables from being reassigned in nested scopes. It would be a visual shorthand that indicates that you are definitely not creating…

I think you think you're being sarcastic. But I do actually work for a developer tools company, and I implement language features on a continuous basis. I implemented anonymous methods - closures - for Delphi, a task that involves no small amount of scope wrangling; and FWIW, Delphi almost certainly has a larger userbase than CoffeeScript, albeit one with greyer hairs. While my specialism is in statically typed, comp…

Ok, I'll dial back the snark and just be blunt. I think all your concerns about CoffeeScript are hypothetical exaggerations. I don't think you've written much CoffeeScript at all, and I suspect that if you did, you'd quickly see that your concerns are overblown. In theory, you could have thousands of lines in a single file, and two commits from a separate branch could cause an undetectable accidental naming collision. In practice, this rarely happens. Files tend to be smaller, merge changes do to tend to get scrutiny, and name collisions often have fairly obvious symptoms once you run your tests on the code.
Post reply on HN