Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

className property on class objects #494

Closed
jashkenas opened this issue Jul 13, 2010 · 16 comments
Closed

className property on class objects #494

jashkenas opened this issue Jul 13, 2010 · 16 comments

Comments

@jashkenas
Copy link
Owner

It's been suggested that we add a .className property on all classes for easier debugging. Basically:

class Connection
conn: new Connection
conn.constructor.className is 'Connection'

Do you think it would be too presumptuous of us to reserve it as a special property, or are you in favor of the idea?

@zmthy
Copy link
Collaborator

zmthy commented Jul 13, 2010

Why not just attach the name property that's only missing because named functions don't work in IE?
conn.constructor.name is 'Connection'

@StanAngeloff
Copy link
Contributor

I recall Harmony is adding a name property to function as well so good idea.

@jashkenas
Copy link
Owner Author

Wouldn't that be lovely. I'm afraid that function.name is read-only. Try this in your console.

> var func = function(){};
> func.name = "Bob";
> func.name;
""

@TrevorBurnham
Copy link
Collaborator

Hmm, right now there's no string representation of the class name anywhere in CoffeeScript, right? That does seem like an omission. But the proposed approach doesn't sound very intuitive. It would mean:

class Dog
Dog.className: 'Dog' # proposed addition

fido: new Dog

console.log(Dog.className)  # 'Dog'
console.log(fido.constructor.className) # 'Dog'
console.log(fido.className) # undefined

Now, maybe it's for the best that I can define .className to be whatever I want on Dog instances. But let's say that I want it to be the name of the class on all of my objects, so I make a parent object MyBaseObject like so:

class MyBaseObject
  className: @constructor.className # this won't work

class Dog extends MyBaseObject
Dog.className: 'Dog' # proposed addition

fido: new Dog

console.log(Dog.className)  # 'Dog'
console.log(fido.constructor.className) # 'Dog'
console.log(fido.className) # undefined

This fails because @constructor tries to reference the nonexistent constructor property of the prototype; it would work if I made MyBaseObject.className a function instead, which is a bit unintuitive. I guess that what I really don't like is using the .constructor syntax, which I realize is from JavaScript but doesn't quite mesh with the CoffeeScript class system.

I'll try to make my critique more coherent in a separate issue. This is interesting stuff. :)

@TrevorBurnham
Copy link
Collaborator

On further reflection, I think that the right approach is to override constructor.toString() with the name of the class. This would be optimal for debugging. If I write

console.log "${Dog}"

then I'd much rather get 'Dog' than the entire definition of the Dog constructor. I guess it's possible, in principle, that someone would want the function definition string at runtime so that they could modify it and run it through eval; but if they're doing anything that sophisticated, they probably don't need CoffeeScript's class syntax in the first place.

@TrevorBurnham
Copy link
Collaborator

[Github apparently ate my first attempt at making this comment. Gotta copy+paste before submitting in the future.]

On further reflection, I think that the right approach is to override .toString(). If I'm debugging, I'd like for

console.log "${Dog}"

to return 'Dog', rather than the entire Dog constructor. It's possible that someone might want the function definition at runtime in order to, say, modify it and run it through eval; but if they're doing anything that sophisticated, then they don't really need CoffeeScript's class functionality in the first place.

@StanAngeloff
Copy link
Contributor

Make this behaviour optional using a command line switch --reflect or whatever? PHP, .NET and other languages use reflection services to inspect objects and access class names, members, etc. There is no such animal in JS land so we can control the output using a switch -- if anyone needs this, they can always add it in and reference it, but don't bloat the generated code for the rest of us, no ?

@TrevorBurnham
Copy link
Collaborator

I wouldn't want to have to set a compiler flag in order to use rich, reflective classes... those should be a narrative part of the language. If you don't want to use those features, you don't have to use the class keyword. You can use a JS library instead, if that's less bloaty.

Also, Google Closure Compiler would probably remove any features you don't use during minification anyway. It's quite smart about detecting "dead code."

@bnolan
Copy link

bnolan commented Jul 22, 2010

+1 I do the following - if there was a non fruity way of it happening automatically I would like that.

class Zing < ManualRecord
@classname: 'Zing'

@jashkenas
Copy link
Owner Author

In the end, I think that if we did this, we should really do it through named function expressions. ie:

class Dog

Has this constructor in JS:

var Dog = function Dog(){};

But, because of the IE memory and scoping problems with named functions, documented in extensive detail here by kangax:

http://yura.thinkweb2.com/named-function-expressions/

I don't think that we want to go there, and risk subtle incompatibilities with certain uses of nested classes in Internet Explorer.

Adding an extra special property to every class feels a bit silly -- why not add it to every single function?

So, I think you have two good alternatives ... if you're simply checking for class identity, use instanceof. And if you want a string for pretty-printing of classes, override toString, or provide a custom inspect function.

Closing the ticket as a wontfix.

@lorensr
Copy link

lorensr commented Sep 25, 2011

For those looking at this issue and wondering why it is labeled fixed instead of wontfix:

class Foo
c = new Foo
c.__proto__.constructor.name is 'Foo'

@airhorns
Copy link

I would like to reopen the discussion of this.

@jashkenas is on the money about instanceof and inspection: I have no complaints there. However, the game changes when you start leveraging Function::name for real logic, and especially when minifying files. We use the names of functions to provide a sensible default for all sorts of different options in our project, such as API endpoint URLs, labels and foreign keys for associations, smart object retrieval from a namespace, and a bunch of error messages. When minified, all of these defaults stop working because the name of the named function representing the class gets mangled by a good compressor. We lose the ability to auto-generate all this stuff, and end up having to define it for every class defined:

class Shopify.Shop extends Batman.Model
  __name__: 'Shop'

or, having to override the specific options everywhere we use them, which looks equivalently silly.

class Shopify.Order extends Batman.Model
  @url: 'orders'

It's certainly possible to do this for every class so our handy string manipulators can provide these defaults, but it is a real bummer. This problem exists for anyone using Function::name in important logic in minified files. There are other solutions like macros or modifying our minifiers to not mangle the specific named functions that look like CoffeeScript classes, but both are a bandaid over this boo-boo in CoffeeScript. The fact is we need a string representation of the class name in our minified JavaScript, since it won't get mangled, and CoffeeScript has that information readily available.

@jashkenas jashkenas reopened this Nov 25, 2011
@devongovett
Copy link

@hornairs +100. I'm doing the same thing.

className is probably a bad name though because that is used by DOM elements already.

And to get the className on instances as well all you have to do is this:

class Dog
    constructor: ->
        @className = @constructor.className

I think it's actually quite intuitive.

@satyr
Copy link
Collaborator

satyr commented Nov 26, 2011

className is probably a bad name though because that is used by DOM elements already.

Coco uses .displayName. A little awkward for reflection, but has debugger supports.

@jashkenas
Copy link
Owner Author

Harry, Devon -- give master a try. The above commit gives named classes a name property, even in IE.

@airhorns
Copy link

It's a Christmas miracle. Thanks a bunch Jeremy!

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

No branches or pull requests

9 participants