Extended Object Literals to review
I hope you don't mind a couple of questions about this proposal.
The <superclass> meta property definition says:
This causes the [[Prototype]] value of the new class’ prototype to be set to
the prototype property value of the designated constructor function
Shouldn't the superclass' prototype be chained with the class' constructor prototype instead of replacing it, as in Douglas Crockford's prototypal inheritance article javascript.crockford.com/prototypal.html? I
believe this is easier to understand and less error prone, because when modifying properties in the new class prototype they won't be added to the superclass' prototype.
Why were class bodies chosen as object initializers instead of function bodies? Function bodies allow for a couple of good concepts such as defining private variables by default with the var statement, avoiding the use of the this keyword for private variables and thus producing shorter code when minified.
Also, what happens to methods in these proposal? Are they defined as properties of the instance or as part of the prototype? If they are fresh instance properties, is there a method for defining prototype methods when using the <superclass> meta property?
Thanks! Harmony is looking awesome! ,
Juan
On Mar 12, 2011, at 6:04 AM, Juan Ignacio Dopazo wrote:
Hi! I hope you don't mind a couple of questions about this proposal.
The <superclass> meta property definition says:
This causes the [[Prototype]] value of the new class’ prototype to be set to the prototype property value of the designated constructor function
Shouldn't the superclass' prototype be chained with the class' constructor prototype instead of replacing it, as in Douglas Crockford's prototypal inheritance article? I believe this is easier to understand and less error prone, because when modifying properties in the new class prototype they won't be added to the superclass' prototype.
I think I many not have been clear enough. In my proposal the follow two class initialisers mean exactly the same same:
class c { <proto: s.prototype> }
class c { <superclass: s> }
both are equivalent to:
function c() {}; c.prototype=s.prototype;
proto: is the most general form, superclass: is provided as a convenience and to push non-essential details of ECMAScript prototypical inheritance into the background for the most common use cases.
Why were class bodies chosen as object initializers instead of function bodies?
In order to provide a declarative form for the conventional constructor/prototype/instance "class" definition pattern. In a declarative form you (or an implementation) can determine the initial "shape" of the objects (the complete set of properties) without having to either simulate execution of the constructor or wait until runtime and observe actual execution of the constructor. This makes the original programmers intent clearer for future readers and maintainers of the code, it enables software tooling, and opens the door for optimizations that might not otherwise had been performed.
Function bodies allow for a couple of good concepts such as defining private variables by default with the var statement, avoiding the use of the this keyword for private variables and thus producing shorter code when minified.
But their main problem is that they cannot be understood without either simulated or actual evaluation and in the worse cases their results will differ from evaluation to evaluation.
Nothing has been taken away, you can continue to use closure capture if you wish, with or without using the extended objects or class initilisers. Closure captured variables are a distinct concept and have different semantics and optimization characteristics than properties.
I'll concede on the minification argument. I don't think it should be a primary concern in driving this sort of design. Also this.foo and foo semantically mean quite different things. I want both.
Also, what happens to methods in these proposal? Are they defined as properties of the instance or as part of the prototype? If they are fresh instance properties, is there a method for defining prototype methods when using the <superclass> meta property?
quote from the wiki page:
The body of a class initialiser is quite different from that of a function. The bracketed body of a Class Initializer is essentially an extended object initialiser rather than a function body. Except as elaborated below, the properties defined in the object initiliser are created as properties of the prototype. For example:
I guess I should at that emphasis to the wiki page.
(note correction in function c() {} example below. the Object.create is an essential part of the semantics)
On Mar 12, 2011, at 6:04 AM, Juan Ignacio Dopazo wrote:
Hi! I hope you don't mind a couple of questions about this proposal.
The <superclass> meta property definition says:
This causes the [[Prototype]] value of the new class’ prototype to be set to the prototype property value of the designated constructor function
Shouldn't the superclass' prototype be chained with the class' constructor prototype instead of replacing it, as in Douglas Crockford's prototypal inheritance article? I believe this is easier to understand and less error prone, because when modifying properties in the new class prototype they won't be added to the superclass' prototype.
I think I many not have been clear enough. In my proposal the follow two class initialisers mean exactly the same same:
class c { <proto: s.prototype> }
class c { <superclass: s> }
both are equivalent to:
function c() {}; c.prototype=Object.create(s.prototype);
proto: is the most general form, superclass: is provided as a convenience and to push non-essential details of ECMAScript prototypical inheritance into the background for the most common use cases.
Why were class bodies chosen as object initializers instead of function bodies?
In order to provide a declarative form for the conventional constructor/prototype/instance "class" definition pattern. In a declarative form you (or an implementation) can determine the initial "shape" of the objects (the complete set of properties) without having to either simulate execution of the constructor or wait until runtime and observe actual execution of the constructor. This makes the original programmers intent clearer for future readers and maintainers of the code, it enables software tooling, and opens the door for optimizations that might not otherwise had been performed.
Function bodies allow for a couple of good concepts such as defining private variables by default with the var statement, avoiding the use of the this keyword for private variables and thus producing shorter code when minified.
But their main problem is that they cannot be understood without either simulated or actual evaluation and in the worse cases their results will differ from evaluation to evaluation.
Nothing has been taken away, you can continue to use closure capture if you wish, with or without using the extended objects or class initilisers. Closure captured variables are a distinct concept and have different semantics and optimization characteristics than properties.
I'll concede on the minification argument. I don't think it should be a primary concern in driving this sort of design. Also this.foo and foo semantically mean quite different things. I want both.
Also, what happens to methods in these proposal? Are they defined as properties of the instance or as part of the prototype? If they are fresh instance properties, is there a method for defining prototype methods when using the <superclass> meta property?
quote from the wiki page:
The body of a class initialiser is quite different from that of a function. The bracketed body of a Class Initializer is essentially an extended object initialiser rather than a function body. Except as elaborated below, the properties defined in the object initiliser are created as properties of the prototype. For example:
I guess I should at that emphasis to the wiki page.
(note correction in function c() {} example below. the Object.create is an essential part of the semantics)
That makes complete sense! So the resulting prototype will be a new object. Perfect!
Why were class bodies chosen as object initializers instead of function
bodies?
In order to provide a declarative form for the conventional constructor/prototype/instance "class" definition pattern. In a declarative form you (or an implementation) can determine the initial "shape" of the objects (the complete set of properties) without having to either simulate execution of the constructor or wait until runtime and observe actual execution of the constructor
I thought that would be the point, but I wasn't sure. Sounds great.
Thanks for clarifying my doubts!
Juan
I've added basic desugaring patterns to the class initialisers wiki page to clarify things.
On Sat, Mar 12, 2011 at 11:02 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote: [...]
class c { <proto: s.prototype> }
class c { <superclass: s> }
both are equivalent to:
function c() {}; c.prototype=Object.create(s.prototype); [...]
So if "var b = new c", then "b.constructor" will be "s" instead of "c"?
good point, the desugaring should be:
function c() {}; c.prototype=Object.create(s,protoype,{'constructor': { value:c, enumerable: false, writable: false, configurable:false}});
We can debate about the attributes of the constructor (and the constructor's prototype) property but my stake in the ground is that these should be frozen because they are defined declaratively.
Correct me if I'm wrong, but I think it should be like this:
function S() {}
function C() { Object.defineProperty(this, 'constructor', { value: C, enumerable: false, writable: false, configurable: false }); } C.prototype = Object.create(S.prototype);
var o = new C(); console.log(o.constructor == C); // true console.log(o.constructor.prototype.constructor == S); // true
Juan
On Mar 12, 2011, at 2:58 PM, Juan Ignacio Dopazo wrote:
Correct me if I'm wrong, but I think it should be like this:
function S() {}
function C() { Object.defineProperty(this, 'constructor', { value: C, enumerable: false, writable: false, configurable: false }); } C.prototype = Object.create(S.prototype);
var o = new C(); console.log(o.constructor == C); // true console.log(o.constructor.prototype.constructor == S); // true
But this also puts an own property on every instance, which is not how .constructor works with functions:
js> function C(x) { this.x = x; }
js> c = new C(42) ({x:42}) js> c.hasOwnProperty('x')
true js> c.hasOwnProperty('constructor')
false js> c.constructor
function C(x) {this.x = x;} js> c.constructor === C
true
Right, my bad then.
Juan
Allen Wirfs-Brock worte: .... I've updated all of the Harmony extended object literal proposals strawman:object_initialiser_extensions based upon discussions at the last several TC39 meetings. ... These items are on the agenda for the next TC39 meeting but discussion here is certainly welcome.
Very useful proposals. Thank you!
Upon my preliminary read, one question (or rather discussion point) does come to mind.
In the context of the proposals(*), are the following syntactic constructs valid within object initialisers - and what are their semantics?
- var toString : const() { ... }
- var toString : function() {... }
- var toString : #() {... }
My initial thoughts are that if valid, (1) would behave the same as 'method'. (2) would be a property that is not enumerable, but can be over-written (by data or another function). (3) would be the same as (2), but with 'pounder' semantics (re: this, return, tco etc...).
But are they valid? (Or is var confined only to data properties) And if not, what was the argument for disallowing the use of 'var' to annotate a property name that could be bound to a function.
Thanks again! Faisal Vali
On Mar 12, 2011, at 6:05 PM, Juan Ignacio Dopazo wrote:
Right, my bad then.
Maybe not, it's a difference but possibly someone will make the case for it. I still favor putting 'constructor' on the class prototype (constructor function .prototype in plain old ES5). It is more efficient and usually per-instance is neither wanted for some "hasOwnProperty('constructor') must return true" reason (presumably to prevent shadowing).
If one wants higher integrity, Object.defineProperty(C.prototype, 'constructor', {value:C, writable:false, configurable:false}} does the trick (that is verbose on purpose to be clear, there's a much shorter way to say it, using the default undefined->false conversions for missing attributes).
Allen, did you want 'constructor' to be in the class prototype?
@Brendan My mistake was thinking that setting enumerable to false also made it return false on hasOwnProperty().
The idea behind it was to be able to walk down the prototype chain by doing o.constructor.prototype.contructor.proto... But then I realized that's not the case even in today's javascript.
Maybe adding a 'superclass' property to the constructor could be useful? As in...
function S() {} function C() {} C.protototype = Object.create(S.prototype, { 'constructor': {value: C} }); Object.defineProperty(C, 'superclass', { value: S });
Juan
On Mar 13, 2011, at 2:16 PM, Brendan Eich wrote:
... Allen, did you want 'constructor' to be in the class prototype?
Absolutely. Here is what the proposal says
The value a class initialiser is a new function object. It has a “prototype” property whose value is also a new object. For brevity, the rest of this proposal will use the term “constructor” to refer to the the function object created by such a declaration and the term “prototype” to refer to the object that is created as the value of the function’s “prototype” property. The prototype has a property named “constructor” whose value is the constructor. Both the prototype property and the constructor property have the attributes {writable: false, enumerable: false, configurable: false}. The only real variance from a function expression is the that these properties are non-configurable and that the “prototype attribute is not writable.
Rationale : {writable: false, enumerable: false, configurable: false} is the attribute settings used for built-in “prototype” properties. The same attributes are used for “constructor” as a reflection of the declarative manner in which it was constructed. Class initialiser establish immutable relationships between a constructor and it prototype. If mutable relationships are needed, existing ECMAScript constructs and conventions should be used to define such objects.
On Mar 13, 2011, at 2:10 PM, Faisal Vali wrote: ...
In the context of the proposals(*), are the following syntactic constructs valid within object initialisers - and what are their semantics?
- var toString : const() { ... }
- var toString : function() {... }
- var toString : #() {... }
I didn't explicitly incorporate any new harmony syntax proposal into my proposal. This is just to minimize interdependencies among proposals. In general, any new syntactic forms that parses as a AssignmentExpression are allowed to the right of the : (or : const). Give that, according to the currently proposal they would all define a toString property that had the attributes {enumerable: false, writable: true, configurable: true}. In order to make it a read only property you would have to code: 1c) var toString: const const() { ...} 2c) var toString: const function() { ...} 3c) var toStrong: const #() { ... }
Case 1 presents some parsing issues issues because a parser would have to lookahead to the { before it can determine whether the right had side of the : is a parenthesized expression with the const property modifier or a const function. var c: const (a,b,c,d,e) { }
A someday we will have to start making global design trade-off among proposals to address issues like this. For example, if we have the #() { } form, then perhaps const() { } will be less necessary.
Or we may decide that using : const to mean non-writable is too problematic and re-explore some of the other alternatives.
BTW, the reason the proposal creates a non-enumerable, non-writable property as var x: const expr, rather than: const x: expr is because of feedback on earlier proposals asking for the ability to orthogonally define the full complement of properties attributes. So, 'var' became the modifier meaning enumerable: false and ': const' means writable: false. Another alternative would be have three prefix keys, for example: sealed, const, and var corresponding to configurable: false, writable: false, and enumerable: false. Eg:
{sealed const var answer: 42}
However, that means that the grammar and users have to deal with all eight combinations of the prefix keywords (in combination with the detail that the property name may itself be one of the keywords). None of this is insurmountable but moving 'const' to the right of the colon simplifies the prefix combinations to four and (to me) seemed more readable: {sealed var answer: const 42}
On 2011-03-13, at 18:15, Juan Ignacio Dopazo wrote:
@Brendan My mistake was thinking that setting enumerable to false also made it return false on hasOwnProperty().
The idea behind it was to be able to walk down the prototype chain by doing o.constructor.prototype.contructor.proto... But then I realized that's not the case even in today's javascript.
I agree that we need this. Can something like:
On Sat, Mar 12, 2011 at 4:36 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: good point, the desugaring should be:
function c() {}; c.prototype=Object.create(s,protoype,{'constructor': { value:c, enumerable: false, writable: false, configurable:false}});
make 'walking the prototype chain' work without giving each instance a constructor property?
see = new c();
see.constructor => c see.constructor.prototype.constructor => s
?
ES3 does not have the luxury of Object.create, so to get the same effect I have put the constructor value in each instance. This is what OpenLaszlo does today. I'm looking forward to the day when I won't have to any more.
It seems the desugaring quoted above should do this for you (assuming s
is similarly defined).
On 14/03/2011, at 18:32, P T Withington wrote:
On 2011-03-13, at 18:15, Juan Ignacio Dopazo wrote:
The idea behind it was to be able to walk down the prototype chain by doing o.constructor.prototype.contructor.proto... But then I realized that's not the case even in today's javascript.
I agree that we need this. (...)
Isn't that the purpose of Object.getPrototypeOf() ?
On 11:59 AM, P T Withington wrote:
ES3 does not have the luxury of Object.create,
Object.create is easily implemented with the ES3 language.
I guess something along the lines of new ((function () { return this; }).prototype = String); How would you do Object.create(null) ?
On 2011-03-14, at 14:29, Jorge wrote:
On 14/03/2011, at 18:32, P T Withington wrote:
On 2011-03-13, at 18:15, Juan Ignacio Dopazo wrote:
The idea behind it was to be able to walk down the prototype chain by doing o.constructor.prototype.contructor.proto... But then I realized that's not the case even in today's javascript.
I agree that we need this. (...)
Isn't that the purpose of Object.getPrototypeOf() ?
If I have a class c
whose superclass is s
, I'm trying to understand how I can get from an object that is an instance of c
to the superclass. I believe the current desugaring that Alan proposed, which obeys Brendan's recommendation that constructor
be a property of the prototype results in:
o = new c(); o.constructor === c o instanceof c => true o instanceof s => true o.constructor.prototype instanceof s => true
but:
o.constructor.prototype.constructor === c
?
I don't see how I can recover s
from o
(or c
), which seems like a useful operation. What am I missing?
I don't see how I can recover
s
fromo
(orc
), which seems like a useful operation. What am I missing?
I totally forgot about Object.getPrototypeOf(). I definitely need to play a little more with ES5. Try this:
function S() {} function C() {} C.prototype = Object.create(S.prototype, {'constructor':{value:C}});
var o = new C();
var proto = Object.getPrototypeOf(o); while (proto) { console.log(proto, proto.constructor); proto = Object.getPrototypeOf(proto); }
Juan
On 14/03/2011, at 21:34, P T Withington wrote:
On 2011-03-14, at 14:29, Jorge wrote:
Isn't that the purpose of Object.getPrototypeOf() ?
If I have a class
c
whose superclass iss
, I'm trying to understand how I can get from an object that is an instance ofc
to the superclass. I believe the current desugaring that Alan proposed, which obeys Brendan's recommendation thatconstructor
be a property of the prototype results in:o = new c(); o.constructor === c o instanceof c => true o instanceof s => true o.constructor.prototype instanceof s => true
but:
o.constructor.prototype.constructor === c
?
I don't see how I can recover
s
fromo
(orc
), which seems like a useful operation. What am I missing?
ISTM, you would just need to follow the [[prototype]] chain, like so:
function S () { } S.prototype.constructor= S;
function C () { } C.prototype= new S; C.prototype.constructor= C;
o= new C;
do { o= Object.getPrototypeOf(o); console.log(o && o.constructor); } while (o);
--> OUTPUT:
function C() { } function S() { } function Object() { [native code] } null
No ?
On 2011-03-14, at 17:13, Juan Ignacio Dopazo wrote:
I totally forgot about Object.getPrototypeOf().
On 2011-03-14, at 17:14, Jorge wrote:
ISTM, you would just need to follow the [[prototype]] chain, like so:
Indeed. That is what I was missing. (I don't have a way to portably emulate that on all the (ES3-like) platforms I compile to, which is why I was misled.)
One more question about the future of classes on Harmony.
Although the <meta: property> syntax is very clear, I'm wondering if it
isn't better to be less innovative and stick to what ES4/Java/etc have been doing for a long time. Wouldn't something like this ease the learning curve of the new features?
frozen class C extends S { }
In this case frozen would apply to both the constructor, the prototype and the instances created from the class. My guess is that most developers will usually chose to apply a generic property instead of a more granular approach (sealing only the prototype, etc). Of course, this could coexist with <meta: properties> for when it is necessary to be more specific.
class C extends S { <sealed: instance> }
Juan
The starting point for the class initialiser proposal are the Object Initialiser Extensions. I wanted to add the option to specify the prototype for ObjectLiteral and ArrayLiteral but because they aren't prefixed by a keyword, the extends like syntax doesn't work for them. The <proto: foo> syntax does. If we accept that, then it seems to make sense to use a similar convention for Class Initalisers. Put another way, I generally prioritize internal consistency ahead of similarity with other languages.
There are both advantages and disadvantages to copying syntax from another language. One of the disadvantages is familiar syntax creates an expectation for similar semantics. JavaScript isn't Java and the objects defined by this proposal don't have the semantics of Java objects. It may be a good thing if unfamiliar syntax causes a Java programmer to slow down a bit and think about what they really are defining.
At a more meta level, I don't think about this proposal as adding classes to ECMAScript. I see it as adding syntax that more directly supports a common object usage patterns. The extension doesn't add or change ECMAScript's fundamentally instance-based object model and it doesn't preclude other patterns of object-based inheritance or composition. That was why in earlier iterations of this proposal I used the keyword "constructor" instead of "class".
BTW, the above is mostly a mild rant in reaction to your phrase "the future of classes on Harmony". I really hope that the meme doesn't get started that we're trying to remake JavaScript as a Java-style class-based language. I suspect you didn't mean that by the phrase, but I'm sensitive about how the things we talk about here and on the wiki are sometimes misinterpreted by members of the wider JavaScript community.
Absolutely not! I read the word "meme" and I panicked! Please understand that I'm trying to write about highly technical topics in a foreign language (I'm from Argentina), so sometimes my word choice may not be the best.
I used "classes" mostly because it's what most of JS developers I know use today to talk about constructors. Some even talk about inheritance when just using object composition (yes, you read right). I liked the "constructor" keyword too. I thought it explained much better what was going on under the hood. Too bad it is too long.
Consistency with extended object initializers makes a lot of sense. I hadn't considered it.
Thanks for taking the time for explaining your reasoning! I'll do my best to evangelize this vision.
Juan
I've updated all of the Harmony extended object literal proposals strawman:object_initialiser_extensions based upon discussions at the last several TC39 meetings. In particular a very productive informal discussion at the Sept. meeting.
The major change in this iterations is a new "class" declaration based upon object literals. This declaration supports the declarative object definition that exactly mirrors the constructor/prototype/instance organization used by the EMAScript built-in objects.
I also did some updates to the Private Names proposal strawman:private_names to clarify some issues that have come up in previous meetings. There is also a proposal for integrating Private Names with the extended object literals.
These items are