(Weak){Set|Map} subclassing
Le 27/11/2012 14:02, David Bruant a écrit :
Hi,
var o = {}; WeakMap.call(o); WeakSet.call(o); Map.call(o); Set.call(o);
Currently, this works and makes o a weakmap, a weakset, a map and a set... I understand collections were spec'ed to enable subclassing, but I don't see the value of being able to subclass this way (I think Jason Orendorff already made the comment somewhere but I can't find it). Would it rather make sense that instead of internal [[WeakMapData]], [[MapData]], etc. each would work with a single [[CollectionData]] slot which value can change depending on how it's been defined?
Sorry, very bad working. I meant "can differ" instead of "can change". The idea is that [[CollectionData]] is of a certain type decide at initialization, but it couldn't change over time for a given object.
On Nov 27, 2012, at 5:07 AM, David Bruant wrote:
Le 27/11/2012 14:02, David Bruant a écrit :
Hi,
var o = {}; WeakMap.call(o); WeakSet.call(o); Map.call(o); Set.call(o);
Currently, this works and makes o a weakmap, a weakset, a map and a set...
Sort of. They won't inherit from any of the corresponding prototype types so none of the relevant methods will be available on those objects. However they could be explicitly invoked on the object. If you want to make them all available then the individual methods would have to be installed with non-conflicting names.
But overall, why should the above be a problem? It is how ES "construction" works and the way things work for any user defined objects. Why should built-ins be any different?
I understand collections were spec'ed to enable subclassing, but I don't see the value of being able to subclass this way (I think Jason Orendorff already made the comment somewhere but I can't find it). Would it rather make sense that instead of internal [[WeakMapData]], [[MapData]], etc. each would work with a single [[CollectionData]] slot which value can change depending on how it's been defined? Sorry, very bad working. I meant "can differ" instead of "can change". The idea is that [[CollectionData]] is of a certain type decide at initialization, but it couldn't change over time for a given object.
The [[MapData]], etc. properties are used as the "brand" that ties methods that are dependent upon a particular internal data structure to an objet that internally provides that data structure. If you simply replaced [[MapData]], [[SetData]], etc with [[CollectionData]] then then you would also have to provide another branding property for each kind of collection so the methods don't try to operate upon the wrong kind of [[CollectionData]]
It would still be possible to subclass each (which is the minimum class syntax needs since it's not possible to inherit from several things), but remove the possibility to subclass several of them.
But I don't see the point, since there are no similar restrictions on user defined "classes".
Le 27/11/2012 19:40, Allen Wirfs-Brock a écrit :
On Nov 27, 2012, at 5:07 AM, David Bruant wrote:
Le 27/11/2012 14:02, David Bruant a écrit :
Hi,
var o = {}; WeakMap.call(o); WeakSet.call(o); Map.call(o); Set.call(o);
Currently, this works and makes o a weakmap, a weakset, a map and a set... Sort of. They won't inherit from any of the corresponding prototype types so none of the relevant methods will be available on those objects. However they could be explicitly invoked on the object. If you want to make them all available then the individual methods would have to be installed with non-conflicting names.
But overall, why should the above be a problem? It is how ES "construction" works and the way things work for any user defined objects. Why should built-ins be any different?
hmm... that is... a good argument :-)
On Tue, Nov 27, 2012 at 12:40 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
On Nov 27, 2012, at 5:07 AM, David Bruant wrote:
WeakMap.call(o); WeakSet.call(o); Map.call(o); Set.call(o);
Currently, this works and makes o a weakmap, a weakset, a map and a set...
Sort of. They won't inherit from any of the corresponding prototype types so none of the relevant methods will be available on those objects. However they could be explicitly invoked on the object. If you want to make them all available then the individual methods would have to be installed with non-conflicting names.
But overall, why should the above be a problem? It is how ES "construction" works and the way things work for any user defined objects. Why should built-ins be any different?
I still don't care for this. It adds a level of indirection, people seem to find it surprising, and I don't see the benefit.
Here's a counterproposal, another way to support subclassing builtins. Suppose we introduce a method, Object.prototype.@@create, that's called when you call Object.create(x) or new Object. It takes no arguments except 'this'; all it does is create a new object with 'this' as the [[Prototype]]:
Object.create(x) <==> x.@@create()
new C(x, y) <==> C.call(C.prototype.@@create(), x, y)
Then add Map.prototype.@@create which creates a Map with empty [[MapData]]. Likewise Set.prototype.@@create and so on.
I think the reason I don't agree with the "this is how things work for user-defined objects" argument is that user-defined objects, by necessity, build their functionality compositionally out of the stuff the language and host already provide. Map and other builtin types are not like that. They exist specifically because they can't be built from other JS parts. There are already hundreds of such classes in the DOM.
On Nov 30, 2012, at 2:20 PM, Jason Orendorff wrote:
On Tue, Nov 27, 2012 at 12:40 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: On Nov 27, 2012, at 5:07 AM, David Bruant wrote:
WeakMap.call(o); WeakSet.call(o); Map.call(o); Set.call(o);
Currently, this works and makes o a weakmap, a weakset, a map and a set...
Sort of. They won't inherit from any of the corresponding prototype types so none of the relevant methods will be available on those objects. However they could be explicitly invoked on the object. If you want to make them all available then the individual methods would have to be installed with non-conflicting names.
But overall, why should the above be a problem? It is how ES "construction" works and the way things work for any user defined objects. Why should built-ins be any different?
I still don't care for this. It adds a level of indirection, people seem to find it surprising, and I don't see the benefit.
Here's a counterproposal, another way to support subclassing builtins. Suppose we introduce a method, Object.prototype.@@create, that's called when you call Object.create(x) or new Object. It takes no arguments except 'this'; all it does is create a new object with 'this' as the [[Prototype]]:
Object.create(x) <==> x.@@create() new C(x, y) <==> C.call(C.prototype.@@create(), x, y)
Then add Map.prototype.@@create which creates a Map with empty [[MapData]]. Likewise Set.prototype.@@create and so on.
This is not unreasonable. It is actually pretty great but I think needs a little tweaking. Let me try to explain in somewhat different terms and with a slight variation.
The ordinary [[Construct]] internal method (which is what the new operator actually uses) when invoked upon a constructor function performs two steps. It first instantiates a new object instances (and sets its [[Prototype]] internal property). It then calls the constructor function using the new object as the this value so that the constructor can perform initialization.
In the past on this list we have discussed the utility of being able to separately over-ride the [[Construct]] and [[Call]] behavior of a function (and Proxies enable this). What Jason is proposing is a variation of this. Instead of exposing a @@method that completely replaces [[Construct]] he is proposing a method that replaces the allocation step of [[Construct]]. Such a method, in addition to performing actual allocation, could also perform any allocation time initialization that should be separate from the constructor functionality. This permits the constructor function to be super invoked without doing allocation-time initialization upon the subclass instance.
Overall, I like this approach. However, I don't think @@create belongs on the prototype object. This make the @@create functionality for a particular kind of object available to anyone who gets their hands on an instance object of the class. This smells like a capability leak.
Instead, I would make @@create a property of the actual constructor function and I would place the default @@create on Function.prototype, which all functions inherit from.
So roughly speaking, Foo.[Constructor] would be defined as:
- Let creator be Foo.[Get] 2 ) Let newObj be creator.call(foo). //Foo is passed as the this value to @@create
- Let ctorResult be Foo.[call]
- If Type(ctorResult) is Object, return ctorResult
- else return newObj
The definition of the Function.prototype.@@create would be loosely
function() { return Object.create(this.prototype); }
So, Map would be defined with a Map.@@create method that in sorta specTalk would say something like:
- Let obj be a new ordinary object.
- Let proto be this.[Get].
- Set the [[Prototype]] of obj to proto.
- Add a [[MapData]] internal property to obj.
- Return obj
The actual Map constructor function would be specified just like it is currently. It primarily deals with initialize the new object from arguments passed to the constructor. Then somebody could code:
class MyMap extends Map{ constructor (...args) { super(...args); this.myAnswer = 42 } }
And then new MyMap(); would produce an object that has full map functionality but also has a myAnswer property.
so, I'm in! I should have done this originally. I didn't because I was avoiding tackling the problem of providing separate call and construct entry points for constructors at this time. But it really looks like that is the best way to support built-in subclassing.
I think the reason I don't agree with the "this is how things work for user-defined objects" argument is that user-defined objects, by necessity, build their functionality compositionally out of the stuff the language and host already provide. Map and other builtin types are not like that. They exist specifically because they can't be built from other JS parts. There are already hundreds of such classes in the DOM.
Not all built-ins and DOM objects have the characteristic that they can't be built from JS parts. We should want to reduce that hundreds to a handful.
What's nice about this solution is that applies to both built-in and self-hosted objects.
A self-hosted implementation of Set (which might be implemented using a Map) could defined its @@create as:
let setDate = new Symbol(true); //private name, as of this week @-name have gone away and [ ] for symbol keyed property access is back
class Set {
constructor (args) {
//use args to populate this object
//likely uses setData property of a Set instance
}
...
}
Set[Symbols.create] = function() { //a
let obj = super(); //create an ordinary object inheriting from Set.prototype
obj[setData] = new Map();
return obj
}
Good job, Jason!
On Fri, Nov 30, 2012 at 7:40 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
Overall, I like this approach. However, I don't think @@create belongs on the prototype object. This make the @@create functionality for a particular kind of object available to anyone who gets their hands on an instance object of the class. This smells like a capability leak.
I think you're right.
Instead, I would make @@create a property of the actual constructor function and I would place the default @@create on Function.prototype, which all functions inherit from.
So roughly speaking, Foo.[Constructor] would be defined as:
- Let creator be Foo.[Get] 2 ) Let newObj be creator.call(foo). //Foo is passed as the this value to @@create
- Let ctorResult be Foo.[call]
- If Type(ctorResult) is Object, return ctorResult
- else return newObj
The definition of the Function.prototype.@@create would be loosely
function() { return Object.create(this.prototype); }
So, Map would be defined with a Map.@@create method that in sorta specTalk would say something like:
- Let obj be a new ordinary object.
- Let proto be this.[Get].
- Set the [[Prototype]] of obj to proto.
- Add a [[MapData]] internal property to obj.
- Return obj
All of this looks pretty great to me, certainly an improvement on what I proposed. This is actually similar to what Python does. I wonder if it can be simplified a bit.
One issue with this is initializing fields that we want to be immutable. Consider subclassing something like the String wrapper class: String.@@create() should return an object with [[PrimitiveValue]] set to... an empty string? It gets initialized later? It's kind of gross for that field to change observably after allocation. Python fixes this by adding another wrinkle: it passes the constructor arguments to the allocation hook (new) as well as the initialization hook (init). Maybe we can achieve the same end by simplifying instead.
On Dec 1, 2012, at 10:40 AM, Jason Orendorff wrote:
On Fri, Nov 30, 2012 at 7:40 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: Overall, I like this approach. However, I don't think @@create belongs on the prototype object. This make the @@create functionality for a particular kind of object available to anyone who gets their hands on an instance object of the class. This smells like a capability leak.
I think you're right.
Instead, I would make @@create a property of the actual constructor function and I would place the default @@create on Function.prototype, which all functions inherit from.
So roughly speaking, Foo.[Constructor] would be defined as:
- Let creator be Foo.[Get] 2 ) Let newObj be creator.call(foo). //Foo is passed as the this value to @@create
- Let ctorResult be Foo.[call]
- If Type(ctorResult) is Object, return ctorResult
- else return newObj
The definition of the Function.prototype.@@create would be loosely
function() { return Object.create(this.prototype); }
So, Map would be defined with a Map.@@create method that in sorta specTalk would say something like:
- Let obj be a new ordinary object.
- Let proto be this.[Get].
- Set the [[Prototype]] of obj to proto.
- Add a [[MapData]] internal property to obj.
- Return obj
All of this looks pretty great to me, certainly an improvement on what I proposed. This is actually similar to what Python does. I wonder if it can be simplified a bit.
It's also essentially what Smalltalk (basicNew) and Ruby (allocate) do, so we're probably on the right track.
One issue with this is initializing fields that we want to be immutable. Consider subclassing something like the String wrapper class: String.@@create() should return an object with [[PrimitiveValue]] set to... an empty string? It gets initialized later? It's kind of gross for that field to change observably after allocation. Python fixes this by adding another wrinkle: it passes the constructor arguments to the allocation hook (new) as well as the initialization hook (init). Maybe we can achieve the same end by simplifying instead.
Do you have a simplification in mind?
I'm reluctant to pass the new arguments to both. Argument passing has a runtime cost and new occurs a lot. Plus, as soon as you pass the same arguments to both you muddy the waters about the purpose of the @@create method and the constructor function. Without the arguments its a clean separation. One allocates the object and slots for its private state. The other initializes the object, including both private and public state. If we pass the new arguments to @@create people some people are going to get mixed up about this.
As you move into complex objects with complex initialization semantics it becomes pretty much impossible to guarantee that a half initialized object hasn't escaped from a constructor. All it takes is a global assigned of the constructor this value while the constructor is active followed by a throw out of the constructor. That sort of thing is a (relatively rare, I think) bug that people just have to deal with.
I'm not sure that leaking a not fully initialized string wrapper object would be any worse that the half initialized user defined object. A perhaps hard to find but rare bug. My inclination is to live with the bug possibility and keep the simplicity of a no-args @@reate call.
BTW, since the spec. will simply define the [[PrimitiveValue]] state (it's now named something else in the spec...) as an "internal data property" which basically just means "private slot", an implementation could do magic to ensure such an object never exposed the [[PrimitiveValue]] before it is initialized. For example, it could implement it as a special single-assignent slot that that throws if read before it is initialized. Or it just might initialize it with a special marker value that all of the built-in methods that know how to access the slot check for. (If it really is implemented using a private named property, then the private name itself is a pretty good thing to use as the uninitialized value marker.)
The simplification I've thought about is eliminating [[Construct]] as an internal method/Proxy trap and just making the call @@Create/call consturctor sequence the evaluation semantics of the new operator. But I've not yet convinced myself that this is sufficient to capture all of the "called as constructor"/"called as a function" semantic silliness that some chapter 15 built-ins have. I'm also not sure that DOM and friends don't have other dependencies on a reified [[Construt]]
On Sat, Dec 1, 2012 at 2:38 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
The simplification I've thought about is eliminating [[Construct]] as an internal method/Proxy trap and just making the call @@Create/call consturctor sequence the evaluation semantics of the new operator. But I've not yet convinced myself that this is sufficient to capture all of the "called as constructor"/"called as a function" semantic silliness that some chapter 15 built-ins have. I'm also not sure that DOM and friends don't have other dependencies on a reified [[Construt]]
The simplification I had in mind was changing [[Construct]] from an internal method/Proxy trap to an ordinary .@@construct method. There would be a @@construct/constructor split rather than a [[Construct]]/@@create/constructor split.
But on reflection, it didn't seem like that would be simpler in practice. Having two separately hookable phases of object construction is just right. A class can easily customize either behavior in a way that not only works, but will still work when the class is subclassed further.
On Dec 3, 2012, at 3:03 PM, Jason Orendorff wrote:
On Sat, Dec 1, 2012 at 2:38 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: The simplification I've thought about is eliminating [[Construct]] as an internal method/Proxy trap and just making the call @@Create/call consturctor sequence the evaluation semantics of the new operator. But I've not yet convinced myself that this is sufficient to capture all of the "called as constructor"/"called as a function" semantic silliness that some chapter 15 built-ins have. I'm also not sure that DOM and friends don't have other dependencies on a reified [[Construt]]
The simplification I had in mind was changing [[Construct]] from an internal method/Proxy trap to an ordinary .@@construct method. There would be a @@construct/constructor split rather than a [[Construct]]/@@create/constructor split.
But on reflection, it didn't seem like that would be simpler in practice. Having two separately hookable phases of object construction is just right. A class can easily customize either behavior in a way that not only works, but will still work when the class is subclassed further.
-j
OK, so it sounds like we have a plan. I'll update the spec. to use @@create.
Allen Wirfs-Brock wrote:
On Dec 3, 2012, at 3:03 PM, Jason Orendorff wrote:
On Sat, Dec 1, 2012 at 2:38 PM, Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>> wrote:
The simplification I've thought about is eliminating [[Construct]] as an internal method/Proxy trap and just making the call @@Create/call consturctor sequence the evaluation semantics of the new operator. But I've not yet convinced myself that this is sufficient to capture all of the "called as constructor"/"called as a function" semantic silliness that some chapter 15 built-ins have. I'm also not sure that DOM and friends don't have other dependencies on a reified [[Construt]]
The simplification I had in mind was changing [[Construct]] from an internal method/Proxy trap to an ordinary .@@construct method. There would be a @@construct/constructor split rather than a [[Construct]]/@@create/constructor split.
But on reflection, it didn't seem like that would be simpler in practice. Having two separately hookable phases of object construction is just right. A class can easily customize either behavior in a way that not only works, but will still work when the class is subclassed further.
-j
OK, so it sounds like we have a plan. I'll update the spec. to use @@create.
Maybe it has been lost somewhere (esdiscuss/2012-December/026757), but I'd like to propose a little bit around call vs. init separation, that fits into this restructuting, copying older post here:
Overall, I like this approach. However, I don't think @@create belongs on the prototype object. This make the @@create functionality for a particular kind of object available to anyone who gets their hands on an instance object of the class. This smells like a capability leak.
Instead, I would make @@create a property of the actual constructor function and I would place the default @@create on Function.prototype, which all functions inherit from.
So roughly speaking, Foo.[Constructor] would be defined as:
I have just thought about this, and came to similar conclusion, but not using @@create, but by splitting [[Construct]] into [[Alloc]] and [[Init]]. My reasoning was more about speccing that [[Init]] is used instead of [[Call]] in new as well as in super(...). So I don't care if [[Alloc]] is [[Alloc]] or @@create.
But I did not know whether there is will to respecify [[Construct]]. If there is, would it be reasonable to define [[Call]] on functions as well as [[Init]] on constructor functions, as invoke it instead of [[Call]] in [[Construct]] (and also super(...), but there the [[Call]] is buried deeper in some abstract operation)? The default [[Init]] is to delegate to [[Call]], but is potentially different.
===============
As a second step / food for thought, not a part of previous proposal, so please take them separately, thanks, [[Construct]] could be refactored from internal operation ([[...]]) into abstract spec operation, and constructor could be tested by presence of [[Init]].
The definition of the Function.prototype.@@create would be loosely
function() { return Object.create(this.prototype); }
On Mon, Dec 3, 2012 at 6:52 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
OK, so it sounds like we have a plan. I'll update the spec. to use @@create.
Thanks for resolving this. Having the class side inheritance for @@create makes complete sense to me and it solves a lot of issues. It can be used to explain how subclassing Array and DOM objects work too.
For example:
HTMLButtonElement.@@create = function() { return document.createElement('button'); };
class MyButton extends HTMLButtonElement { constructor(text) { this.textContent = text; } }
Is there a missing step in that outlined algorithm? Where is [[Prototype]] set?
So roughly speaking, Foo.[Constructor] would be defined as:
- Let creator be Foo.[Get] 2 ) Let newObj be creator.call(foo). //Foo is passed as the this value to @@create
2.5) Call newObj.[SetInheritance]
- Let ctorResult be Foo.[call]
- If Type(ctorResult) is Object, return ctorResult
- else return newObj
On Dec 4, 2012, at 5:37 AM, Herby Vojčík wrote:
Allen Wirfs-Brock wrote:
So roughly speaking, Foo.[Constructor] would be defined as:
I have just thought about this, and came to similar conclusion, but not using @@create, but by splitting [[Construct]] into [[Alloc]] and [[Init]]. My reasoning was more about speccing that [[Init]] is used instead of [[Call]] in new as well as in super(...). So I don't care if [[Alloc]] is [[Alloc]] or @@create.
But I did not know whether there is will to respecify [[Construct]]. If there is, would it be reasonable to define [[Call]] on functions as well as [[Init]] on constructor functions, as invoke it instead of [[Call]] in [[Construct]] (and also super(...), but there the [[Call]] is buried deeper in some abstract operation)? The default [[Init]] is to delegate to [[Call]], but is potentially different.
super(...) is just shorthand for super.constructor(...) (when it occurs in a constructor) so it is just a use of [[Call]]. No magic.
Making the allocation step a regular method call is good because it eliminates the need to expand the MOP to include an additional internal method/trap. My assumption here is that it generally better to do things at the object level than at the meta-object level and to only use the meta-object level for things that are impossible to accomplish using regular objects properties.
It isn't clear to me if there is a general need to distinguish [[Init]] (ie, called via the new operator) from [[Call]] (ie, called any other way). I need to take a close look at the chapter 15 functions that have differed "called as a constructor" and "called as a function" behaviors in order to see if we can discriminate those cases some other way
===============
As a second step / food for thought, not a part of previous proposal, so please take them separately, thanks, [[Construct]] could be refactored from internal operation ([[...]]) into abstract spec operation, and constructor could be tested by presence of [[Init]].
I don't see a major benefit of getting rid of [[Construct]] and adding [[Init]]. That doesn't reduce the width of the MOP. However, we've both noted that [[Construct]] could potentially be replaced with an abstract operation (or just inlined as part of the semantics of the new operator).
The real question is whether there are any existing use cases (probably in the DOM) that requires intervention prior to the call to @@create. If so, we need [[Construct]]. If not, we probably could eliminate it.
A secondary question, if we only have @@create and [[Call]] what is the IsConstructor test criteria. Or perhaps IsConstructor isn't need at all. It is new in the ES6 spec. and only has two uses, both of which look like they could probably be eliminated.
On Dec 4, 2012, at 7:21 AM, Erik Arvidsson wrote:
Is there a missing step in that outlined algorithm? Where is [[Prototype]] set?
So roughly speaking, Foo.[Constructor] would be defined as:
- Let creator be Foo.[Get] 2 ) Let newObj be creator.call(foo). //Foo is passed as the this value to @@create
2.5) Call newObj.[SetInheritance]
- Let ctorResult be Foo.[call]
- If Type(ctorResult) is Object, return ctorResult
- else return newObj
I do it in @@create:
The definition of the Function.prototype.@@create would be loosely
function() { return Object.create(this.prototype); }
The this.prototype access gets the "prototype" property value from the leaf constructor ( in the example, Foo) and Object.create sets it upon creation.
I'm a bit reluctant to separate object allocation from initializing [[Prototype]] as I suspect that in many cases implementations will want to do these as an atomic step and separating them would be visible, via a Proxy.
On the other hand, requiring @@create to explicitly take care of setting [[Prototype]] maybe a bit of a foot gun.
Ideally, any @@create method that is just adding private state to an ordinary object should be coded as:
Foo.assign({ //class methods of Foo [create] () { let newObj = supercreate; //eventually calls Function.prototype.@@create. We really need to allow super in obj lits to support this newObj[myPrivate] = undefined return newObj } });
If you forget to do the super[create] call you don't get [[Prototype]] initialized. However, if you leave out that super call you will also not allocate any superclass provided per instance state. So, it would be buggy anyway.
Allen Wirfs-Brock wrote:
On Dec 4, 2012, at 5:37 AM, Herby Vojčík wrote:
Allen Wirfs-Brock wrote:
So roughly speaking, Foo.[Constructor] would be defined as:
- Let creator be Foo.[Get] 2 ) Let newObj be creator.call(foo). //Foo is passed as the this value to @@create
- Let ctorResult be Foo.[call]
- If Type(ctorResult) is Object, return ctorResult
- else return newObj I have just thought about this, and came to similar conclusion, but not using @@create, but by splitting [[Construct]] into [[Alloc]] and [[Init]]. My reasoning was more about speccing that [[Init]] is used instead of [[Call]] in new as well as in super(...). So I don't care if [[Alloc]] is [[Alloc]] or @@create.
But I did not know whether there is will to respecify [[Construct]]. If there is, would it be reasonable to define [[Call]] on functions as well as [[Init]] on constructor functions, as invoke it instead of [[Call]] in [[Construct]] (and also super(...), but there the [[Call]] is buried deeper in some abstract operation)? The default [[Init]] is to delegate to [[Call]], but is potentially different.
super(...) is just shorthand for super.constructor(...) (when it occurs in a constructor) so it is just a use of [[Call]]. No magic.
Making the allocation step a regular method call is good because it eliminates the need to expand the MOP to include an additional internal method/trap. My assumption here is that it generally better to do things at the object level than at the meta-object level and to only use the meta-object level for things that are impossible to accomplish using regular objects properties.
It isn't clear to me if there is a general need to distinguish [[Init]] (ie, called via the new operator) from [[Call]] (ie, called any other way). I need to take a close look at the chapter 15 functions that have differed "called as a constructor" and "called as a function" behaviors in order to see if we can discriminate those cases some other way
Some of the builtins do that (though it is a weak argument). But drawing from their example, one may want to create a class so that new Foo(...args) creates and Foo(obj) can do something else, like adapt/transform.
I meant [[Init]] not as a spec-only thing for the already existing precedents, but to allow user to distinguish [[Call]] and [[Construct]] behaviour, like:
class Foo [extends ...] { init { .... } call { .... } // create { .... } ... }
and use [init]/[call] (or init/call if the do-not-bring-in-computed-name-just-for-iterator mentioned in "Comments on Meeting Notes" thread wins) to specify [[Init]] and [[Call]] behaviour, with fallbacks to use when only one is present ([init] -> [call] if [init] not present; [call] fails with "Initializer only" when not present).
===============
As a second step / food for thought, not a part of previous proposal, so please take them separately, thanks, [[Construct]] could be refactored from internal operation ([[...]]) into abstract spec operation, and constructor could be tested by presence of [[Init]].
I don't see a major benefit of getting rid of [[Construct]] and adding [[Init]]. That doesn't reduce the width of the MOP. However, we've both noted that [[Construct]] could potentially be replaced with an abstract operation (or just inlined as part of the semantics of the new operator).
So you know if function is a constructor or not. You need something that bears authority for this, and without [[Construct]] it is [[Init]].
The real question is whether there are any existing use cases (probably in the DOM) that requires intervention prior to the call to @@create. If so, we need [[Construct]]. If not, we probably could eliminate it.
A secondary question, if we only have @@create and [[Call]] what is the IsConstructor test criteria. Or perhaps IsConstructor isn't need at all. It is new in the ES6 spec. and only has two uses, both of which look like they could probably be eliminated.
If they can, fine; but there definitely are non-constructor functions, and you should be able to detect if something is a constructor.
On Tue, Dec 4, 2012 at 12:42 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
If you forget to do the super[create] call you don't get [[Prototype]] initialized. However, if you leave out that super call you will also not allocate any superclass provided per instance state. So, it would be buggy anyway.
I was really hoping this would solve the Array subclass problem. Is this how you imagined it working then?
Array.@@create = function() { var newObj = []; newObj.proto = this.prototype; return newObj; };
On Dec 4, 2012, at 10:57 AM, Erik Arvidsson wrote:
On Tue, Dec 4, 2012 at 12:42 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
If you forget to do the super[create] call you don't get [[Prototype]] initialized. However, if you leave out that super call you will also not allocate any superclass provided per instance state. So, it would be buggy anyway.
I was really hoping this would solve the Array subclass problem. Is this how you imagined it working then?
Array.@@create = function() { var newObj = []; newObj.proto = this.prototype; return newObj; };
This indeed should solve the array subclassing problem. You code is logically what Array.@@create needs to do. In the spec. it will be expressed as a call to an abstraction operation that creates an exotic array object. That call is parameterized with the [[Prototype]] value for the new instance.
var o = {}; WeakMap.call(o); WeakSet.call(o); Map.call(o); Set.call(o);
Currently, this works and makes o a weakmap, a weakset, a map and a set... I understand collections were spec'ed to enable subclassing, but I don't see the value of being able to subclass this way (I think Jason Orendorff already made the comment somewhere but I can't find it). Would it rather make sense that instead of internal [[WeakMapData]], [[MapData]], etc. each would work with a single [[CollectionData]] slot which value can change depending on how it's been defined?
It would still be possible to subclass each (which is the minimum class syntax needs since it's not possible to inherit from several things), but remove the possibility to subclass several of them.