Prototypes as the new class declaration
On Jun 14, 2011, at 10:08 PM, Allen Wirfs-Brock wrote:
The correspondence is even closer if we slightly extend the new operator. Using current new operator semantics and my alternative way of defining SkinnedMeesh and then executing: let aSM = new SkinnedMesh(aGeo, aMat); we would get a TypeError because SkinnedMesh does not have a [[Construct]] internal method. However, it would be a small extension to the new operator for it to invoke its operand's constructor method if the operand is an object that is not a function (alternative, we could give every object created using an object literal a [[Construct]] internal method that invokes the constructor method, its really just an alternative way to specify the same thing.)
The extension to use .constructor seems better because less magic. Either way, there's an extension to runtime semantics that could make old code that fails (throws) today run tomorrow, but that is different from changing the meaning of non-failing code. Should be ok.
We should make a parallel change to instanceof, so (aSM instanceof SkinnedMesh) works.
If we do all of this, we don't need a separate class declaration concept. We still have:
- a declarative way to abstract over a family of related objects -- an object literal
- a way to name these class-like abstractions -- a const or let binding
- a way to specify inheritance for the abstraction -- <|
- a way to specify how each instance of a class-like abstraction will differ -- the construct method defined in the object literal
- the ability to continue to use the new operator to create new instances of a named class-like abstraction -- extended new operator
- kept the basic JS focus on prototypal inheritance
I'm still ineffably sad about classes, but your proposal cures that melancholy. It's more JavaScript-y. I like it, so far.
What we have dropped is a lot of complexity:
- a new class declaration form
- a emphasis on creating classes
- class properties and their declarative forms.
- parallel class-side inheritance hierarchy
Parallel class-side inheritance can be done, IINM:
const SkinnedMesh = THREE.Mesh <| { constructor: THREE.Mesh <| function(geometry, materials) { super.constructor(geometry, materials); ... }, ... };
This brings out a difference between your class-less proposal and classes, you've covered it but it may not pop out: you cannot call a class method cm via SkinnedMesh.cm() . Instead you must call SkinnedMesh.constructor.cm() or equivalent.
Probably this is unfixable. The prototype object named here by SkinnedMesh is a singleton containing a single constructor function. Properties of the prototype show up, unless shadowed, in instances via delegation. Properties of the constructor, if inherited as I show above, come from the super-constructor. But there are no classes, so talking about class methods (never mind the "static" misnomer) does not make sense.
Constructor methods and other constructor properties can be created, but naming them requires that .constructor component.
- issues concerning class-side private state
However, something we abandon is the constructor function/prototype abstraction model that the ES built-ins use and that is implicit in the current new operator definition and the association of a prototype property with each function. This is a change, but perhaps one for the better.
This is where we may lose people. We're no longer sugaring the prototypal pattern as people know it.
On Jun 14, 2011, at 10:59 PM, Brendan Eich wrote:
Parallel class-side inheritance can be done, IINM:
const SkinnedMesh = THREE.Mesh <| { constructor: THREE.Mesh <| function(geometry, materials) {
Er, that should be ... THREE.Mesh.constructor <| function(...) {
Again with the .constructor component. Reinforcing the degree to which I, at least, have been conditioned to think of the "class name" as the constructor and not the prototype.
super.constructor(geometry, materials);
There it is again (.constructor, I mean).
Perhaps we need a shorthand, something even one character suffix operator. Not sure.
From: Brendan Eich <brendan at mozilla.com> Date: June 15, 2011 7:59:34 GMT+02:00
This brings out a difference between your class-less proposal and classes, you've covered it but it may not pop out: you cannot call a class method cm via SkinnedMesh.cm() . Instead you must call SkinnedMesh.constructor.cm() or equivalent.
Probably this is unfixable. The prototype object named here by SkinnedMesh is a singleton containing a single constructor function. Properties of the prototype show up, unless shadowed, in instances via delegation. Properties of the constructor, if inherited as I show above, come from the super-constructor. But there are no classes, so talking about class methods (never mind the "static" misnomer) does not make sense.
Constructor methods and other constructor properties can be created, but naming them requires that .constructor component.
Wouldn’t you add a class method like this?
const SkinnedMesh = THREE.Mesh <| { cm: function() { } ... };
Then it could be invoked as SkinnedMesh.cm() (which to me is what a class method is: there is a class named SkinnedMesh and it has a method cm()).
Furthermore, it would automatically be inherited:
const SkinnedMeshExtended = SkinnedMesh <| { ... }
Then the following class method could also be called:
Right?
On Jun 14, 2011, at 11:59 PM, Axel Rauschmayer wrote:
Wouldn’t you add a class method like this?
const SkinnedMesh = THREE.Mesh <| { cm: function() { } ... };
Then it could be invoked as SkinnedMesh.cm() (which to me is what a class method is: there is a class named SkinnedMesh and it has a method cm()).
Furthermore, it would automatically be inherited:
const SkinnedMeshExtended = SkinnedMesh <| { ... }
Then the following class method could also be called:
Right?
It has the right plurality -- I mean, the prototype is a singleton and so is the constructor.
You invoke it on the name of the abstraction, via dot -- check. Just like String.fromCharCode.
So let's go with that built-in: new String("hi") instanceof String is true, and same works for SkinnedMesh in both the classes proposal and Allen's class-free variant.
But String.fromCharCode is not inherited such that "hi".fromCharCode delegates to String.fromCharCode. Hrm.
I don't mean to single out String as a built-in. Whether user-defined or built-in, today's constructor functions can have "class properties" that are not inherited by instances. That seems like it could matter, e.g. to avoid having Object.keys suddenly make keys properties appear in all objects -- including in the global object!
But String.fromCharCode is not inherited such that "hi".fromCharCode delegates to String.fromCharCode. Hrm.
I don't mean to single out String as a built-in. Whether user-defined or built-in, today's constructor functions can have "class properties" that are not inherited by instances. That seems like it could matter, e.g. to avoid having Object.keys suddenly make keys properties appear in all objects -- including in the global object!
Agreed. Ironically, having class methods also be instance methods makes things more Java-like, where all static methods can also be accessed via instances (albeit most IDEs give you a warning nowadays, but it’s legal in the language).
On Jun 15, 2011, at 12:25 AM, Brendan Eich wrote:
On Jun 14, 2011, at 11:59 PM, Axel Rauschmayer wrote:
Wouldn’t you add a class method like this?
const SkinnedMesh = THREE.Mesh <| { cm: function() { } ... };
Then it could be invoked as SkinnedMesh.cm() (which to me is what a class method is: there is a class named SkinnedMesh and it has a method cm()).
Furthermore, it would automatically be inherited:
const SkinnedMeshExtended = SkinnedMesh <| { ... }
Then the following class method could also be called:
Right?
It has the right plurality -- I mean, the prototype is a singleton and so is the constructor.
You invoke it on the name of the abstraction, via dot -- check. Just like String.fromCharCode.
So let's go with that built-in: new String("hi") instanceof String is true, and same works for SkinnedMesh in both the classes proposal and Allen's class-free variant.
But String.fromCharCode is not inherited such that "hi".fromCharCode delegates to String.fromCharCode. Hrm.
I don't mean to single out String as a built-in. Whether user-defined or built-in, today's constructor functions can have "class properties" that are not inherited by instances. That seems like it could matter, e.g. to avoid having Object.keys suddenly make keys properties appear in all objects -- including in the global object!
As far as I can tell, the equivalent of "class methods" just don't show up in the self libraries.
There are various alternatives that are used:
For something like the Object methods self would simply use a singleton object with those methods. That's essentially what we did when we decided to attach them to Object for ES5. Object was just a convenient singleton that already existed. EG, ObjectReflection.create()
For something like String.fromCharCode there are several alternative strategies that might be used.
For something as trivial as this, it just might be added as a method to String (the prototype) and it could be directly invoked there or via any available instance (if you weren't concerned about possible instance specific over-rides). EG, String.newCharCode(0) or "".newCharCode(0)
Or, since the most common form of fromCharCode essentially converts a number to a char, it might be added Number.prototype EG, 0.asCharacter() or for the multiple value case Array.prototype, EG, [1,2,3,4].asStringFromCharCodes()
Alternatively, if there was a more complex set of methods or there was an a strong reason why you didn't want instances to share the behavior. You would create a separate singleton helper/factory object to hold the methods, EG, StringBulider.fromCharCode(1,2,3,4,5) StringBuilder.fromStrings("abc","def","xyz")
Of course, such an object could participate in its own inheritance hierarchy that was decoupled from Strings, EG, let I18NStringBuilder = StringBuilder <| {fromLocaleString(template,locate) {...}};
And if you didn't want to "globally" name such factories you can make the factory be a property of the instances. EG, String.builder.fromCharCode(1,2,3);
This is almost the same as hanging methods off the constructor function. However, practically speaking in ES, it is more awkward to attach properties to the constructor. You would probably say: { ... constructor: (Function.prototype <| {fromCharCode(...args) {}...}) <| function (arg) { /* constructor body / } } which seems more awkward than: { ... ~builder: {fromCharCode(...args) {}...}, constructor (arg) { / constructor body */ } }
These examples seem to suggest that there are plenty of reasonable ways to approach what currently would be constructor methods. However, they all require some rethinking that starts with the concept that you name the prototype and not the constructor. The biggest hurdle to me seems to be that the existing built in objects don't follow that pattern. One way around that might be to give the built-in prototypes their own explicit names (in a module). For example: ProtoString, ProtoArray, etc. and then use those names in as the new way to talk about those abstractions. (More radical and probably less acceptable would be to actually re-associate the names String, Array, etc. with the prototypes using such a module).
A basic question is whether learning to think about prototypes as the key named abstraction is more or less of a burden than learning when and how to use a class declaration.
I really like this direction, I think it much better feet for JS than a class sugar!
I also experimented something similar not a long time ago as a library: Gozala/selfish/blob/master/selfish.js Also as an extension to the Built-ins: gist.github.com/d33befccbe1e0ed492d5
-- Irakli Gozalishvili Web: www.jeditoolkit.com Address: 29 Rue Saint-Georges, 75009 Paris, France (goo.gl/maps/3CHu)
On Jun 17, 2011, at 5:14 AM, Irakli Gozalishvili wrote:
I really like this direction, I think it much better feet for JS than a class sugar!
I also experimented something similar not a long time ago as a library: Gozala/selfish/blob/master/selfish.js Also as an extension to the Built-ins: gist.github.com/d33befccbe1e0ed492d5
Thanks, this is really useful. I had expected the the inability to rationalize this approach with the existing built-in constructors/prototypes would be stake through its heart. Your work suggests that it might actually be possible to do a reasonable integration of the two approaches. I'll put some additional work into it.
Now consider rewriting using proposed object literal extensions as:
const SkinnedMesh = THREE.Mesh <| { constructor(geometry, materials) { super.constructor(geometry, materials);
this.identityMatrix = new THREE.Matrix4(); this.bones = []; this.boneMatrices = []; ...
}, update(camera) { ... super.update(); } } Given this declaration, you could create a new instance by: let aSM = new SkinnedMesh.constructor(aGeo, aMat); The object literal form is very similar to the class declaration form except that the entity that we named and which serves as the conceptual focus of abstraction is the prototype object rather than the constructor object.
Where would the prototype of the new instance come from? Would SkinnedMesh.constructor.prototype point to SkinnedMesh?
Prototypes already have a property called “constructor”, so this part of it works out nicely.
On Jun 18, 2011, at 7:29 AM, Axel Rauschmayer wrote:
Where would the prototype of the new instance come from? Would SkinnedMesh.constructor.prototype point to SkinnedMesh?
Of course -- Allen's proposal just "rotates" the (constructor, prototype) pair so the named or "outer" object is the prototype.
This matches the syntax. It's a bit odd that class syntax has a body that contains constructor, yet the class name binds to that inner constructor function. It works, or we can make it work -- it just seems unusual given the otherwise-pellucid rules for naming objects expressed by braced bodies (initialisers, functions).
Prototypes already have a property called “constructor”, so this part of it works out nicely.
Exactly.
Now, we could have a "second protocol" as Irakli's gists show. He wires up both per-instance and "static" (ugh, I mean constructor-side) 'new' methods that bottom out by calling the constructor. This is not quite the second protocol you proposed that we agreed would be less desirable, all else equal, than a single constructor protocol. It's a layering on top, rather than an additional parallel novel protocol.
For reference, Irakli's gist.github.com/d33befccbe1e0ed492d5 has:
example.js #raw 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const SkinnedMesh = THREE.Mesh.extend({ new: function SkinnedMesh(geometry, materials) { var self = THREE.Mesh.new.apply(SkinnedMesh, arguments); // initialize instance properties self.identityMatrix = THREE.Matrix4.new(); self.bones = []; self.boneMatrices = []; return self; }, update: function(camera) { ... // call base version of same method THREE.Mesh.update.call(this); } }); selfish.js #raw 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 /* vim:set ts=2 sw=2 sts=2 expandtab */ /*jshint undef: true es5: true node: true devel: true evil: true forin: true latedef: false supernew: true */ /*global define: true */
"use strict";
Object.prototype.new = function new() { return new this.constructor(); }; Object.prototype.extend = function extend(properties) { var constructor = new Function(), descriptor = {}; properties = properties || {}; Object.getOwnPropertyNames(properties).forEach(function(name) { descriptor[name] = Object.getOwnPropertyDescriptor(properties, name); }); descriptor.constructor = { value: constructor }; return Object.freeze(constructor.prototype = Object.create(this, descriptor)); };
Object.new = function new() { return Object.prototype.new(); }; Object.extend = function extend(properties) { return Object.prototype.extend(properties); }; Comments are parsed with GitHub Flavored Markdown
The Object.prototype.extend method serves the role of <| but also wires up 'constructor' automatically. The user has only to define a 'new' method. This was the more complicated option against which I argued when we mooted classes last month, on the grounds that we did not want to pre-empt 'new', and we didn't need two names ('constructor' and 'new') where one would do -- and where one has done for JS for 16 years.
But beyond the 'new' convenience/prettiness, Irakli's gist builds on Allen's idea of naming the prototype instead of the constructor. extend returns the prototype (not sure why it freezes; let's not worry about that right now ;-). With prototype-first abstraction naming, the argument that we don't need classes becomes stronger. The 'new' protocol allows old-style builtins and user-defined functions to be constructed by calling .new on their instances (delegating up to Object.prototype.new).
For me, this does not close the deal that we don't need classes. But it is getting close, and it is working in the right direction. I don't want to kill classes, but I think we all (Harmony types at least) want to minimize additions to the language and prefer orthogonal primitives that have good usability, and which compose cleanly.
Where would the prototype of the new instance come from? Would SkinnedMesh.constructor.prototype point to SkinnedMesh?
Of course -- Allen's proposal just "rotates" the (constructor, prototype) pair so the named or "outer" object is the prototype.
This matches the syntax. It's a bit odd that class syntax has a body that contains constructor, yet the class name binds to that inner constructor function. It works, or we can make it work -- it just seems unusual given the otherwise-pellucid rules for naming objects expressed by braced bodies (initialisers, functions).
Right, I agree that the appeal of binding the prototype to the class name (instead of the constructor) is that everything makes more sense, especially instanceof and subclassing.
I still don’t understand the “rotates” part, though. The following looks to me like a normal object literal (with the prototype set via the prototype operator). Nothing special about it.
const SkinnedMesh = THREE.Mesh <| { constructor(geometry, materials) { super.constructor(geometry, materials);
this.identityMatrix = new THREE.Matrix4(); this.bones = []; this.boneMatrices = []; ... }, update(camera) { ... super.update(); } }
Then I would have to make the following assignment:
SkinnedMesh.constructor.prototype = SkinnedMesh;
Now the following works (without a separate protocol):
var sm = new SkinnedMesh.constructor(...);
A next step would be the syntactic sugar suggested by Allen: new SkinnedMesh(...) => new SkinnedMesh.constructor(...)
However: THREE.Mesh would have to refer to the prototype (similarly to how things are done above), for this to work.
On Tue, Jun 14, 2011 at 10:08 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
The correspondence is even closer if we slightly extend the new operator. Using current new operator semantics and my alternative way of defining SkinnedMeesh and then executing:
let aSM = new SkinnedMesh(aGeo, aMat);
we would get a TypeError because SkinnedMesh does not have a [[Construct]] internal method. However, it would be a small extension to the new operator for it to invoke its operand's constructor method if the operand is an object that is not a function (alternative, we could give every object created using an object literal a [[Construct]] internal method that invokes the constructor method, its really just an alternative way to specify the same thing.
Interesting extension of "new".
BTW, I believe that somebody else recently on one of these threads also suggested allowing the new operator to be applied to any object so feel free to take credit.
Don't need credit but perhaps you were referring to my email...
www.mail-archive.com/[email protected]/msg07968.html
On Wed, Jun 15, 2011 at 8:20 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
Alternatively, if there was a more complex set of methods or there was an a strong reason why you didn't want instances to share the behavior. You would create a separate singleton helper/factory object to hold the methods, EG, StringBulider.fromCharCode(1,2,3,4,5) StringBuilder.fromStrings("abc","def","xyz")
Of course, such an object could participate in its own inheritance hierarchy that was decoupled from Strings, EG, let I18NStringBuilder = StringBuilder <| {fromLocaleString(template,locate) {...}};
It would be nice to see constructors heading in this direction of being methods on objects that can inherit.
These examples seem to suggest that there are plenty of reasonable ways to approach what currently would be constructor methods. However, they all require some rethinking that starts with the concept that you name the prototype and not the constructor. The biggest hurdle to me seems to be that the existing built in objects don't follow that pattern.
and host objects?
On Sat, Jun 18, 2011 at 8:09 AM, Brendan Eich <brendan at mozilla.com> wrote:
For me, this does not close the deal that we don't need classes. But it is getting close, and it is working in the right direction.
Do I smell a strawman?
but I think we all (Harmony types at least) want to minimize additions to the language and prefer orthogonal primitives that have good usability, and which compose cleanly.
+1
Peter
Now, we could have a "second protocol" as Irakli's gists show. He wires up both per-instance and "static" (ugh, I mean constructor-side) 'new' methods that bottom out by calling the constructor. This is not quite the second protocol you proposed that we agreed would be less desirable, all else equal, than a single constructor protocol. It's a layering on top, rather than an additional parallel novel protocol.
Note though that there shouldn’t be too many steps to make this happen.
I like the image you mentioned (of flipping things around):
- Current “class”: a constructor containing a prototype
- New-style class: a prototype containing a constructor
An interesting question is how much this approach would go against the expectations of existing code (and if it really has to conform %100 to them). The following measures should make the differences minimal:
-
Object.getPrototypeOf(obj).constructor: should it always point to the constructor? Or is it a reference to a class construct (either old-style or new-style)? Most of the code will probably only use it as the operand of the "new" operator.
-
o instanceof C: treat non-function operands differently. Then simply desugars to C.isPrototypeOf(o).
-
new C(): treat non-function operands differently. After construction, things have to look similar to what they look like today. I don’t think it necessarily matters how to get there.
On Jun 18, 2011, at 8:53 AM, Axel Rauschmayer wrote:
Where would the prototype of the new instance come from? Would SkinnedMesh.constructor.prototype point to SkinnedMesh?
Of course -- Allen's proposal just "rotates" the (constructor, prototype) pair so the named or "outer" object is the prototype.
This matches the syntax. It's a bit odd that class syntax has a body that contains constructor, yet the class name binds to that inner constructor function. It works, or we can make it work -- it just seems unusual given the otherwise-pellucid rules for naming objects expressed by braced bodies (initialisers, functions).
Right, I agree that the appeal of binding the prototype to the class name (instead of the constructor) is that everything makes more sense, especially instanceof and subclassing.
I still don’t understand the “rotates” part, though. The following looks to me like a normal object literal (with the prototype set via the prototype operator). Nothing special about it.
I was responding to your question "Would SkinnedMesh.constructor.prototype point to SkinnedMesh?"
In the classes proposal, we have
12345678901234567890123456789012345678901234567890123456789012345678901234567890 [the binding environment] -- 'SkinnedMesh' -+ . | . v . [The class object, i.e., the constructor] . | ^ . | | . 'prototype' 'constructor' . | | . v | . [The object defined by the class body]
By "rotate" I meant turn the 2-cycle at the bottom 180 degrees.
const SkinnedMesh = THREE.Mesh <| { constructor(geometry, materials) { super.constructor(geometry, materials);
this.identityMatrix = new THREE.Matrix4(); this.bones = []; this.boneMatrices = []; ... }, update(camera) { ... super.update(); } }
Then I would have to make the following assignment:
SkinnedMesh.constructor.prototype = SkinnedMesh;
You don't need to, in Allen's proposal. But perhaps it was unclear, or even unstated.
Now the following works (without a separate protocol):
var sm = new SkinnedMesh.constructor(...);
Allen's post discussed this.
A next step would be the syntactic sugar suggested by Allen: new SkinnedMesh(...) => new SkinnedMesh.constructor(...)
Yes, and likewise for instanceof -- that all seems fine, although Irakli's gists can't work in JS today assuming such extensions, wherefore his 'new' protocol, which is clever.
However: THREE.Mesh would have to refer to the prototype (similarly to how things are done above), for this to work.
Right. I thought we went over this already here :-).
The twist with Irakli's gist is that it adds a 'new' method that can work on old-style built-in and user-defined constructor functions, thanks to Object.prototype.new.
But it is a new (no pun) protocol. It's not separate, though -- it works on old-style constructor functions. You can call x.new for any instance x to get a new instance of the same class or built by the same constructor, provided Object.prototype.new is unshadowed.
(Irakli, you could pass along arbitrary arguments from Object.prototype.new to new this.constructor, in ES.next using rest and spread, and even in ES5 using the bind trick [whereswalden.com/2010/09/07/now-in-spidermonkey-and-firefox-es5s-function-prototype-bind].)
So Irakli's work suggests we don't need to extend new and instanceof if we're willing to switch to the new '.new()' protocol.
But that is a big switch.
Extending new and instanceof to probe for .constructor is plausible too, and it avoids breaking the use of these operators on prototype-first "new-style" abstractions.
On Jun 18, 2011, at 10:30 AM, Axel Rauschmayer wrote:
Now, we could have a "second protocol" as Irakli's gists show. He wires up both per-instance and "static" (ugh, I mean constructor-side) 'new' methods that bottom out by calling the constructor. This is not quite the second protocol you proposed that we agreed would be less desirable, all else equal, than a single constructor protocol. It's a layering on top, rather than an additional parallel novel protocol.
Note though that there shouldn’t be too many steps to make this happen.
I like the image you mentioned (of flipping things around):
- Current “class”: a constructor containing a prototype
- New-style class: a prototype containing a constructor
An interesting question is how much this approach would go against the expectations of existing code (and if it really has to conform %100 to them).
That is the $64,000 question.
The following measures should make the differences minimal:
- Object.getPrototypeOf(obj).constructor: should it always point to the constructor? Or is it a reference to a class construct (either old-style or new-style)? Most of the code will probably only use it as the operand of the "new" operator.
I'm not sure what you mean here. The 'constructor' property of prototype objects is writable, but overwriting it could be a useful technique. It's unlikely to be accidentally clobbered, but user-defined and unfrozen function objects can have their .prototype properties overwritten too, so there' s no greater integrity problem in relying on .constructor compared to .prototype for user-defined functions.
- o instanceof C: treat non-function operands differently. Then simply desugars to C.isPrototypeOf(o).
The details matter. Currently instanceof layers on spec-internal [[HasInstance]], implemented in the spec only by Function objects. We could add [[HasInstance]] for all objects that does as you say: tests this.isPrototypeOf(o) for argument o (the left operand of instanceof; |this| refers to the right operand). But do we want to add [[HasInstance]] for all objects?
Or we could check for Y.constructor (object-detect it, as it were) in the x instanceof Y semantics, and use that property's value instead of Y as |this| when calling [[HasInstance]]. This is less efficient and at the limit less reliable given the writability of .constructor.
- new C(): treat non-function operands differently. After construction, things have to look similar to what they look like today. I don’t think it necessarily matters how to get there.
Details always matter. Right now new Y() delegates to Y.[[Construct]] -- but given proxies and other precedents, I think we do not want all objects to grow a [[Construct]] internal method. Compare to [[HasInstance]] above: I believe that is more plausible as a universal internal method.
So we could elaborate the 11.2.2 "The new Operator" semantics to object-detect .constructor. Again some efficiency concerns but modern JITs can optimize; also the reliance on writable .constructor.
I see the value in the <| operator in that it deals with any object. But I think the prototypes as classes idea is focusing on creating an object that describes a prototype and creating a constructor under the hood. This functionality can already be implemented in ES5 (see gist.github.com/1033258). It is pretty much what frameworks are doing today with different names for Function.create (new Class, Class.create, dojo.define, etc).
However, isn't it the idea of classes to have a simple way of declaring instance, prototype and constructor properties (and make some of those private) in a single syntactic structure?
Juan
On Jun 18, 2011, at 12:51 PM, Juan Ignacio Dopazo wrote:
I see the value in the <| operator in that it deals with any object. But I think the prototypes as classes idea is focusing on creating an object that describes a prototype and creating a constructor under the hood.
No, not under the hood -- explicitly via a constructor(){} method in the initialiser on the right of <|.
This functionality can already be implemented in ES5 (see gist.github.com/1033258).
So for the most part can classes as proposed -- they are only sugar for the prototypal pattern, until you get into class-private instance variables, which are an open issue.
It is not enough to say "ES5 can do X". ES5 can do almost anything. We have good reason to add usable syntax for common patterns that entail too much boilerplate, therefore carry risk of errors in rewriting the boilerplace over and over, or production and download costs in acquiring it from a library.
It is pretty much what frameworks are doing today with different names for Function.create (new Class, Class.create, dojo.define, etc).
Yes, and that is true of classes as proposed, modulo private details. Indeed if you prefer the closure pattern for privacy and can stand the overhead, private can be done today. That does not mean classes or similar syntax (such as class-free super and <|) should not be added.
However, isn't it the idea of classes to have a simple way of declaring instance, prototype and constructor properties (and make some of those private) in a single syntactic structure?
There's no single structure in classes as proposed if you read closely: constructor is (as in JS as used today) a prototype method with a separate body.
The public and private declarations within the constructor body for instance variables are novel and grammatically confined to that body, indeed, but there are two bodies: class and constructor. Thus "single" is not accurate.
The class methods and other "statics" are perhaps part of a "single [complex] syntactic structure" but they are not exactly singular -- they imitate declaration-with-initializer and method-in-object-literal forms but with some kind of prefix (BTW "static" is not the front-runner).
So classes have a number of parts, syntactically and semantically.
Allen's work on super and <|, plus the concise method syntax in object initialisers (adopted as part of classes), points toward another means to the same end. This approach has
const WobblyPoint = Point <| {...}
instead of
class WobblyPoint extends Point {...}
It has parity for new and instanceof usage via extensions to those operators that we've just been discussing.
Both classes and Allen's proposal have
class Point { constructor(x, y) {...} ... }
Classes as proposed do have public and private declarations within the constructor body, with some big open issues for private.
I won't speak for Allen, but he, dherman and I have discussed the trade-offs of bundling such magic syntax into constructor body within class.
First, the instance variables seem a bit lost there. We put the declarations there for want of a better place.
Second, using declaration forms to define properties (in the case of public) looks like a mistake. This applies to prototype property bindings created by declarations nested directly in the class body (as ClassElements) too.
Third, private name objects are in ES.next and could be used (possibly with syntactic support for . instead of []) independently of classes and outside of so narrow a context as the constructor body.
Last, private open issues are making at least some of us wonder if we wouldn't have a better ES.next sooner by deferring the private part of classes as proposed.
This last point is no doubt contentious, but at least the third point means one can have private instance state that is efficiently allocated (as part of the one instance of a class). We would need to agree that Object.freeze does not freeze properties with private names.
Anyway, you can see what I am getting at: classes are not simple or unitary, they mix together separable features. We have accepted proposals for some of those features. Others as proposed as part of classes have open issues.
Therefore, it may pay to consider a class-free alternative that is about as syntactically convenient, but that is not such a hardcoded compound syntactic form, with some extra semantics for private and static that we still have to resolve.
Again I'm not trying to kill classes. They will emerge better and stronger, if they prevail, from doing this exercise, with as much attention to detail as we can stand.
Making classes into a "Scenario-solving" outcome where we hardcode unorthogonal forms and functions together would be a mistake. We've avoided that for the easy parts. I think we've done a bit of that for the harder parts (statics, privates), and it has left us with big open issues.
Where would the prototype of the new instance come from? Would SkinnedMesh.constructor.prototype point to SkinnedMesh?
Of course -- Allen's proposal just "rotates" the (constructor, prototype) pair so the named or "outer" object is the prototype.
The only thing I don’t understand is how/where the assignment SkinnedMesh.constructor.prototype = SkinnedMesh is made. Would Allen’s proposal change the language so that whenever a function f is assigned to the property "constructor" of an object o, there is the following assignment? f.prototype = o
I have no trouble understanding that everything (modulo builtins) works fine if the above assignment is made somewhere.
- Object.getPrototypeOf(obj).constructor: should it always point to the constructor? Or is it a reference to a class construct (either old-style or new-style)? Most of the code will probably only use it as the operand of the "new" operator.
I'm not sure what you mean here. The 'constructor' property of prototype objects is writable, but overwriting it could be a useful technique. It's unlikely to be accidentally clobbered, but user-defined and unfrozen function objects can have their .prototype properties overwritten too, so there' s no greater integrity problem in relying on .constructor compared to .prototype for user-defined functions.
Sorry, that was me misunderstanding your previous answer: I thought that one could have an abstraction called “class” denoting a factory for instances. An old-style class would be a function (constructor -> prototype), a new-style class would be a non-function object (prototype -> constructor). Once you have that abstraction, you have to fulfill the expectations of existing code. One of those expectations is that you can apply "new" to the property "constructor" of the prototype of an object to create a new instance. Thus, that property would have to point to the class (either old-style or new-style) that originally produced the object.
- o instanceof C: treat non-function operands differently. Then simply desugars to C.isPrototypeOf(o).
The details matter. Currently instanceof layers on spec-internal [[HasInstance]], implemented in the spec only by Function objects. We could add [[HasInstance]] for all objects that does as you say: tests this.isPrototypeOf(o) for argument o (the left operand of instanceof; |this| refers to the right operand). But do we want to add [[HasInstance]] for all objects?
Or we could check for Y.constructor (object-detect it, as it were) in the x instanceof Y semantics, and use that property's value instead of Y as |this| when calling [[HasInstance]]. This is less efficient and at the limit less reliable given the writability of .constructor.
Right. It probably doesn’t matter how it is handled internally, as long as we can pretend externally that o instanceof C means “look for C in the prototype chain of o”. IMHO that would make things much easier to understand for newbies.
Right now, JS inheritance is hard to understand and hard to explain (especially subclassing). With “prototypes as classes”, a proto operator (such as <|) and "super", we would have a long-term simplification that to me almost feels like a clarification of what is going on already.
Axel
On Jun 19, 2011, at 9:23 AM, Axel Rauschmayer wrote:
Where would the prototype of the new instance come from? Would SkinnedMesh.constructor.prototype point to SkinnedMesh?
Of course -- Allen's proposal just "rotates" the (constructor, prototype) pair so the named or "outer" object is the prototype.
The only thing I don’t understand is how/where the assignment SkinnedMesh.constructor.prototype = SkinnedMesh is made.
Good point, sorry for missing it.
Would Allen’s proposal change the language so that whenever a function f is assigned to the property "constructor" of an object o, there is the following assignment? f.prototype = o
I don't think so, that's too broad and incompatible.
I have no trouble understanding that everything (modulo builtins) works fine if the above assignment is made somewhere.
It seems to me it would occur when in an ObjectLiteral where a function expression is assigned as the initial value of a 'constructor'-named PropertyAssignment.
just catching up after 2+ days of no net access...
On Jun 20, 2011, at 5:14 AM, Brendan Eich wrote:
On Jun 19, 2011, at 9:23 AM, Axel Rauschmayer wrote:
Would Allen’s proposal change the language so that whenever a function f is assigned to the property "constructor" of an object o, there is the following assignment? f.prototype = o
I don't think so, that's too broad and incompatible.
correct, too much magic
I have no trouble understanding that everything (modulo builtins) works fine if the above assignment is made somewhere.
It seems to me it would occur when in an ObjectLiteral where a function expression is assigned as the initial value of a 'constructor'-named PropertyAssignment.
correct, whatever magic there is occurs as part of defining a "constructor" property as part of an object literal. However, such things could still be wired up manually (most likely by somebody wanting to create procedural abstractions for class-like definitions.
On Friday, 2011-06-17 at 16:52 , Allen Wirfs-Brock wrote:
On Jun 17, 2011, at 5:14 AM, Irakli Gozalishvili wrote:
I really like this direction, I think it much better feet for JS than a class sugar!
I also experimented something similar not a long time ago as a library: Gozala/selfish/blob/master/selfish.js Also as an extension to the Built-ins: gist.github.com/d33befccbe1e0ed492d5
Thanks, this is really useful. I had expected the the inability to rationalize this approach with the existing built-in constructors/prototypes would be stake through its heart. Your work suggests that it might actually be possible to do a reasonable integration of the two approaches. I'll put some additional work into it.
Allen
Great, looking forward to see it!
It seems to me it would occur when in an ObjectLiteral where a function expression is assigned as the initial value of a 'constructor'-named PropertyAssignment.
correct, whatever magic there is occurs as part of defining a "constructor" property as part of an object literal. However, such things could still be wired up manually (most likely by somebody wanting to create procedural abstractions for class-like definitions.
Are you thinking along the lines of the following example? var MyClass = class({ ... });
-- Irakli Gozalishvili Web: www.jeditoolkit.com Address: 29 Rue Saint-Georges, 75009 Paris, France (goo.gl/maps/3CHu)
On Saturday, 2011-06-18 at 20:13 , Brendan Eich wrote:
On Jun 18, 2011, at 8:53 AM, Axel Rauschmayer wrote:
Where would the prototype of the new instance come from? Would SkinnedMesh.constructor.prototype point to SkinnedMesh?
Of course -- Allen's proposal just "rotates" the (constructor, prototype) pair so the named or "outer" object is the prototype.
This matches the syntax. It's a bit odd that class syntax has a body that contains constructor, yet the class name binds to that inner constructor function. It works, or we can make it work -- it just seems unusual given the otherwise-pellucid rules for naming objects expressed by braced bodies (initialisers, functions).
Right, I agree that the appeal of binding the prototype to the class name (instead of the constructor) is that everything makes more sense, especially instanceof and subclassing.
I still don’t understand the “rotates” part, though. The following looks to me like a normal object literal (with the prototype set via the prototype operator). Nothing special about it.
I was responding to your question "Would SkinnedMesh.constructor.prototype point to SkinnedMesh?"
In the classes proposal, we have
12345678901234567890123456789012345678901234567890123456789012345678901234567890 [the binding environment] -- 'SkinnedMesh' -+ . | . v . [The class object, i.e., the constructor] . | ^ . | | . 'prototype' 'constructor' . | | . v | . [The object defined by the class body]
By "rotate" I meant turn the 2-cycle at the bottom 180 degrees.
const SkinnedMesh = THREE.Mesh <| { constructor(geometry, materials) { super.constructor(geometry, materials);
this.identityMatrix = new THREE.Matrix4(); this.bones = []; this.boneMatrices = []; ... }, update(camera) { ... super.update(); } }
Then I would have to make the following assignment:
SkinnedMesh.constructor.prototype = SkinnedMesh;
You don't need to, in Allen's proposal. But perhaps it was unclear, or even unstated.
Now the following works (without a separate protocol):
var sm = new SkinnedMesh.constructor(...);
Allen's post discussed this.
A next step would be the syntactic sugar suggested by Allen: new SkinnedMesh(...) => new SkinnedMesh.constructor(...)
Yes, and likewise for instanceof -- that all seems fine, although Irakli's gists can't work in JS today assuming such extensions, wherefore his 'new' protocol, which is clever.
However: THREE.Mesh would have to refer to the prototype (similarly to how things are done above), for this to work.
Right. I thought we went over this already here :-).
The twist with Irakli's gist is that it adds a 'new' method that can work on old-style built-in and user-defined constructor functions, thanks to Object.prototype.new.
But it is a new (no pun) protocol. It's not separate, though -- it works on old-style constructor functions. You can call x.new for any instance x to get a new instance of the same class or built by the same constructor, provided Object.prototype.new is unshadowed.
(Irakli, you could pass along arbitrary arguments from Object.prototype.new to new this.constructor, in ES.next using rest and spread, and even in ES5 using the bind trick [whereswalden.com/2010/09/07/now-in-spidermonkey-and-firefox-es5s-function-prototype-bind].)
So Irakli's work suggests we don't need to extend new and instanceof if we're willing to switch to the new '.new()' protocol.
But that is a big switch.
I think there's also interesting side effect, I really like, that may not be obvious. With .new()
protocol .constructor
becomes obsolete and is kept for "backwards compatibility" only (new
can be implemented as return Object.create(this)
instead, which would behave slightly different, but will be closer to what my intent was). Such a direction has a potential of making inheritance very simple and much less confusing for language migrants. Static properties (own properties of constructors) also become unnecessary, as constructors are no longer used, instead overridden method new
may be used to set up an instance at initialization point.
I believe this is one of the good parts that Crockford has discovered: javascript.crockford.com/prototypal.html
On Jun 20, 2011, at 11:25 AM, Axel Rauschmayer wrote:
It seems to me it would occur when in an ObjectLiteral where a function expression is assigned as the initial value of a 'constructor'-named PropertyAssignment.
correct, whatever magic there is occurs as part of defining a "constructor" property as part of an object literal. However, such things could still be wired up manually (most likely by somebody wanting to create procedural abstractions for class-like definitions.
Are you thinking along the lines of the following example? var MyClass = class({ ... });
no, referring back to the example in my post that started this thread:
const SkinnedMesh = THREE.Mesh <| { constructor(geometry, materials) { super.constructor(geometry, materials);
this.identityMatrix = new THREE.Matrix4(); this.bones = []; this.boneMatrices = []; ...
}, update(camera) { ... super.update(); } }
the "constructor" function defined above would be automatically created with a "prototype" property whose value is the containing object created by the object literal. Put another way:
SkinnedMesh.constructor.prototype===SkinnedMesh
would evaluate to true
correct, whatever magic there is occurs as part of defining a "constructor" property as part of an object literal. However, such things could still be wired up manually (most likely by somebody wanting to create procedural abstractions for class-like definitions.
Are you thinking along the lines of the following example? var MyClass = class({ ... });
no, referring back to the example in my post that started this thread:
const SkinnedMesh = THREE.Mesh <| { constructor(geometry, materials) { super.constructor(geometry, materials);
this.identityMatrix = new THREE.Matrix4(); this.bones = []; this.boneMatrices = []; ...
}, update(camera) { ... super.update(); } } the "constructor" function defined above would be automatically created with a "prototype" property whose value is the containing object created by the object literal. Put another way:
SkinnedMesh.constructor.prototype===SkinnedMesh would evaluate to true
I was referring to “such things could still be wired up manually”. Where would the manual wiring occur?
On Jun 20, 2011, at 12:12 PM, Axel Rauschmayer wrote:
I was referring to “such things could still be wired up manually”. Where would the manual wiring occur?
SkinnedMesh = Object.create(THREE.Mesh); const ctor = constructor(geometry, materials) { super.constructor(geometry, materials);
this.identityMatrix = new THREE.Matrix4(); this.bones = []; this.boneMatrices = []; ... }, ctor.prototype=SkinnedMesh; SkinnedMesh.constructor=ctor;
However, in another thread today I mentioned that to allow super to be used in functions outside of object literals we probably a new reflection function that binds super. In that case the last line above would probably be replaed with something like: Object.defineMethod(SkinnedMesh,"constructor",ctor);
In esdiscuss/2011-June/015090 I described how self uses an explicit copy method on prototypes to construct instance objects and summarized the difference between self and JavaScript in this regard as:
In response to a reply from Axel I further said:
Having thought about this a bit more, I'm beginning to think that perhaps we could get by without the proposed class declarations. Consider the basic example from the classes proposal:
class SkinnedMesh extends THREE.Mesh { constructor(geometry, materials) { super(geometry, materials);
}
update(camera) { ... super.update(); } } You instantiate an instance of this class by saying: let aSM = new SkinnedMesh(aGeo, aMat); Now consider rewriting using proposed object literal extensions as:
const SkinnedMesh = THREE.Mesh <| { constructor(geometry, materials) { super.constructor(geometry, materials);
}, update(camera) { ... super.update(); } } Given this declaration, you could create a new instance by: let aSM = new SkinnedMesh.constructor(aGeo, aMat); The object literal form is very similar to the class declaration form except that the entity that we named and which serves as the conceptual focus of abstraction is the prototype object rather than the constructor object.
The correspondence is even closer if we slightly extend the new operator. Using current new operator semantics and my alternative way of defining SkinnedMeesh and then executing: let aSM = new SkinnedMesh(aGeo, aMat); we would get a TypeError because SkinnedMesh does not have a [[Construct]] internal method. However, it would be a small extension to the new operator for it to invoke its operand's constructor method if the operand is an object that is not a function (alternative, we could give every object created using an object literal a [[Construct]] internal method that invokes the constructor method, its really just an alternative way to specify the same thing.) BTW, I believe that somebody else recently on one of these threads also suggested allowing the new operator to be applied to any object so feel free to take credit.
If we extend new in this manner we are now able to name and use prototype objects defined using object literals as if they were class abstractions. The only thing we are missing at this point is various extensions such as private state and declarative definition of instance properties in constructors that we have in the class declaration proposals. However, those same extensions can be incorporated into object literals in same manner to how they are defined for class declarations. In particular we would allow public and private property declarations in the constructor body, just like in the class proposal.
If we do all of this, we don't need a separate class declaration concept. We still have:
What we have dropped is a lot of complexity:
However, something we abandon is the constructor function/prototype abstraction model that the ES built-ins use and that is implicit in the current new operator definition and the association of a prototype property with each function. This is a change, but perhaps one for the better.
When Brendan originally created JavaScript he was operating under directions to make it as Java-like possible. The use of constructor functions in association with the new operator were part of that Java-like facade. But this was arguably a left turn away from the prototypal inheritance that is at the core of the language. Maybe instead of going further down that side street we can just get back on track by changing how we think about object construction and abstraction.