Skip to content

Instantly share code, notes, and snippets.

@geraldalewis
Created February 18, 2012 02:08
Show Gist options
  • Save geraldalewis/1856904 to your computer and use it in GitHub Desktop.
Save geraldalewis/1856904 to your computer and use it in GitHub Desktop.
Implicit `this` in CoffeeScript
class Sprite
@id: 0
id: -1
x: 0
y: 0
width: 0
height: 0
constructor: ->
id = ++Sprite.id # ~> this.id = ++Sprite.id
move: (x,y) ->
this.x = x
this.y = y
# `this` is sometimes still useful/necessary (identical params/class body vars)
getRect: ->
new Rectangle x, y, width, height
# ~> new Rectangle this.x, this.y, this.width, this.height
@geraldalewis
Copy link
Author

I'm not proposing this for the whole of CoffeeScript (at this point in time). This is useful for a personal project. Once I've lived with it, and determined the benefits/pitfalls, I may propose it formally for CoffeeScript.

@michaelficarra
Copy link

Coincidentally, I was just talking about what a bad idea this was with a lab-mate of mine today. I was helping an undergrad with a project they were writing in Java. Being unfamiliar with Java myself, I was confused over why they were occasionally using qualified references to instance variables and occasionally unqualified references. Then I noticed they had local variables in scope with the same name as their instance variables. It instantly struck me that this was a terrible decision. It's basically JS's with, just kind of flipped around. If you want to reference an instance variable in an unqualified manner, you first have to check that there's no variable with the same name in scope. Then you have to remember not to add it to scope when you come back a year later to make some changes. That is a bad idea. Not quite as bad as with, but it's still a terrible idea. I wouldn't even bother bringing it up for inclusion in CS.

@geraldalewis
Copy link
Author

It instantly struck me that this was a terrible decision.

CoffeeScript aside, it's a wonderful language feature. ActionScript 2 introduced "implicit this" after the class keyword was added to the language. As you know, AS is an ES dialect (and AS2 hemmed especially close to ES3). I was interested to see decompiled ActionScript output, where it's clear the compiler simply did a tiny bit of static analysis to determine class members and prepend this to member references within method bodies where unambiguous.

If you want to reference an instance variable in an unqualified manner, you first have to check that there's no variable with the same name in scope. Then you have to remember not to add it to scope when you come back a year later to make some changes. That is a bad idea.

You don't have to try every idea to know it's a bad one, but in the case I urge you to keep an open mind. I've used this feature in AS and Java for years, and it's literally never been a problem (I'll discuss why this may not be the case for CoffeeScript below). I have never shadowed an instance member with a local var and then confused it with that member. I have never revisited code and created a local var that shadows references intended for members within the rest of my function body. I can imagine those scenarios too, but in a decade of writing code in that manner I haven't once been bitten.

Then I noticed they had local variables in scope with the same name as their instance variables. It instantly struck me that this was a terrible decision.

This argument is similar (if inverted?) to those put forward by critics of CoffeeScript's lack of var. Arguing that it's a bad idea for the language to require you to check carefully to see if a reference is already in scope applies to CoffeeScript as well. In CoffeeScript's case, are you initializing or re-assigning a variable?:

foo = ->
  # snipped
  bar = ->
    baz = 0 # is this initialization or reassignment? 
            # you'll have to check through the code above this to be sure

I'd guess that many people who put forward that argument haven't coded in CoffeeScript, and I should point out that I've never been bitten by CoffeeScript's lack of a var before.

Why it won't work for CoffeeScript
Languages that support implicit this also have var. This is likely why I've never been bitten by it:

class Header {
  var style: Object = null;
  function Header(){}
}

// years later, I create this subclass:

class Subheader extends Header {
  function Subheader(){
  }
  function setStyle(value) {
    // whoops, here I forgot that "style" is a class member
    var style = new Object; 
    // doesn't matter, though, since I've explicitly indicated (with var) 
    // that this "style" is *not* the class member
  }
}

Since CoffeeScript has no such mechanism for establishing whether I'm creating a reference or assigning to it, I would indeed have to look through every parent class to determine if a reference would be considered a member. If I modified any parent classes, I'd have to look through every single subclass method body to ensure that I never used that identifier as a local var. Ick.

@jashkenas
Copy link

Agree with much of what @geraldalewis is saying above, but there's a much bigger problem than "lack of var". Ruby also doesn't have "var", and does have "implicit this", and it works beautifully.

But the reason why we can't do it is that our classes are dynamic. We have no idea, at compile time, what properties are on the prototype. You may have listed some in the class body, but more might be mixed in, more might be added to the prototype directly later in an extension file, and you might yourself be subclassing from an external script that we're not compiling. If we had access to the runtime method resolution, we'd be able to know, but at compile type JS is too dynamic to find out.

Unlike our current pure lexical scope for implicit 'var'....

@geraldalewis
Copy link
Author

Ruby also doesn't have "var", and does have "implicit this", and it works beautifully.

Interesting... Time for me to finally finish the pickaxe book :)

and you might yourself be subclassing from an external script that we're not compiling.

Great point. I'm accustomed to compilers that require all the source up front. That actually ties into #558 (debug support). One of the big challenges is what to do when CoffeeScript code calls buggy code from a precompiled CoffeeScript file. Since the compiler doesn't have access to the original CoffeeScript, the compiler can't show the original line of code responsible for the error (without an intermediary form like a source map).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment