On Nov 30, 2012, at 2:20 PM, Jason Orendorff wrote:
> On Tue, Nov 27, 2012 at 12:40 PM, Allen Wirfs-Brock <[email protected]>
> 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]](...args) would be defined as:
1) Let creator be Foo.[[Get]](@@create)
2 ) Let newObj be creator.call(foo). //Foo is passed as the this value to
@@create
3) Let ctorResult be Foo.[[call]](newObj,args)
4) If Type(ctorResult) is Object, return ctorResult
5) 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:
1) Let obj be a new ordinary object.
2) Let proto be this.[[Get]]('Prototype').
2) Set the [[Prototype]] of obj to proto.
3) Add a [[MapData]] internal property to obj.
4) 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!
Allen
>
> -j
>
> _______________________________________________
> es-discuss mailing list
> [email protected]
> https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss