jashkenas / coffeescript

Unfancy JavaScript
https://coffeescript.org/
MIT License
16.5k stars 1.98k forks source link

Reflection Enhancement #675

Closed danielribeiro closed 14 years ago

danielribeiro commented 14 years ago

It would be nice to have class name from objects, created from classes defined by class keyword. This would be possible to retrieve from constructor property, provided the classes were not anonymous functions.

Edit: It would also be very nice to have pythons' locals (akin to Ruby's local_variables) and Ruby's method instrospection mechanisms.

jashkenas commented 14 years ago

It would be nice, but Internet Explorer's implementation of named functions breaks scoping and leaks memory.

danielribeiro commented 14 years ago

Really? Well, using named functions was just one way. Setting the classname as a propety of the class, and giving something classname as a function on the prototype of all classes would probably work on all browsers, while still giving the capability of doing x.getClass().name().

jashkenas commented 14 years ago

Using regular JavaScript, you can do:

object instanceof Klass

And also:

object.constructor

... to get a reference to the class.

does that not work for you?

danielribeiro commented 14 years ago

Consider this example class Animal constructor: (@name) ->

    an =  new Animal()
    alert Animal.constructor

On firefox 3.6 and Chrome 5, I get: function Function() { [native code] }

So, yeah, it doesn't work. Not to mention getting the reference to the class itself (with the name, it would be possible to easily get it from the global scope, but having less nice things make a language less nice).

jashkenas commented 14 years ago

I'm not saying that it contains the name of the class -- just that the name of the class is often used to get a handle on it -- and you can get a handle on it directly.

Do you have a real-world piece of JS that depends on the class name, as an example that you can share with us?

danielribeiro commented 14 years ago

Class names are mostly useful for debugging names. In all oo languages really. Either directly, or indirectly as default toString implementation or stacktrace.

On our project, that is still using JS.Class, and from these posts will still use it even if we move to Coffee-script, we set an atribute for all objects and classes, with the following code: // Debug Function that creates instance variabel _class with the class name for all // JS.classes function createDisplayName(target) { if (!target) { target = window } for (var name in target) { try { var clas = target[name] if (clas && clas.includes && clas.includes(JS.Kernel) && clas.displayName.length == 0) { clas.define('_class', name) clas.extend({ displayName: name }) } } catch (exception) { //Yeah, some objects from window can't be accessed } } }

It is important to stress that JS.Class can set displayName of all classes defined within a module with a name. Module in JS are not nearly as convenient to write as in ruby, and we find that displayName alone is too far for firebug, this is why we set the attribute _class on all objects.

This is mostly dev code. But development is not only easier with it, it is possible. Javascript has historically lacked several facilities, but thankfully this is starting to change, not with one project alone, with all of them together.

jashkenas commented 14 years ago

Then you'll be glad to hear that modern JavaScript debuggers are starting to use names from variables, and not just named functions. For example, given this CoffeeScript that throws an exception through three classes:

class One
  one: -> (new Two).two()

class Two
  two: -> (new Three).three()

class Three
  three: -> throw new Error('three called')

(new One).one()

Chrome logs this stacktrace:

stacktrace

danielribeiro commented 14 years ago

That is great news. Which chrome version? If it works even on debug mode (which is really important to notice the actual name of the current object's class name, as the line of code is not enough to determine for methods invoked from subclasses), this whole enhancement can be ignored. At least on such browsers.

jashkenas commented 14 years ago

I'm using the stable build of Mac Chrome, if you want to investigate further ... number 6.0.472.55.

danielribeiro commented 14 years ago

Well, we are on Ubuntu, but it should work the same. About classes, as it stands, we will probably stick to JS.Class. It is extra nice on coffeescript. We also remembered a valid reason for class names on runtime, besides debugging: automatic population of a dsl. For instance, by just creating a class called HBox, we can write ui.hbox(ui.list(1, 2, 3, 4)) instead of new HBox(new List(1, 2, 3, 4)) (it is the same in coffescript and javascript).

Since the dsl is extensible, it is required to have the class name. Since we plan to open source this in the next months, we can't really require people to automatically define the class names. With the callbacks from JS.Class it suffices people to include a module or extend a class, and we walk through all the global namespace looking for its name (lazily, so that we don't do this more than once). Or we do a minor fork of coffeescript to create a JS.Class with the class keyword, and we set the name when we do it. It might not pay off, as we will have to have both ways, as we will support JS clients as well.

Thanks for all info/patience/attention.

danielribeiro commented 14 years ago

Well, now that we decided to give coffescript classes a go, instead of just using JS.Class', one trouble is clear: debugging the coffescript compiled version is worse, as there is no mark that global attributes are classes. This prevents us from setting their true name, which is horrible when debugging even shallow hierarquies (ok, I know this method is on class A, but i don't know the instances's actual subclass).

We will look into the compiler pre-processor plugins to enable this. Of course, we will already setup the displayName as well, making the iteration process unnecessary.

Edit: Well, the iteration mechanism actually might work. Just looking for functions with a non-empty prototype instead of clas && clas.includes && clas.includes(JS.Kernel). No need to tamper with ClassNode (line 717) on http://github.com/jashkenas/coffee-script/blob/master/src/nodes.coffee#L717

danielribeiro commented 14 years ago

About named functions having memory leaks, I wonder, if this is true, why prototype still uses them (a quick search on function $A on http://prototypejs.org/assets/2009/8/31/prototype.js will show this)...

danielribeiro commented 14 years ago

Edited the original to add some more suggestions.

jashkenas commented 14 years ago

CoffeeScript classes now have names on the constructor, which can be accessed like:

@constructor.name

or

klass.name

Closing the ticket.

danielribeiro commented 14 years ago

This is really appreciated! And I suspect not just by me. Thanks.