[Harmony proxies] Proxies, prototype chain and inheritance
are you aware of < strawman:proxy_instanceof>?
I just heard from Brendan today that Andreas Gal has encountered some DOM emulation issues that might make us want us to consider this sooner rather than later. How well would it also address the cases you have in mind?
Another way of solving the inconsistency I see in providing the 6 "prototype-climbing" traps ("getPropertyDescriptor", "getPropertyNames", "has", "get", "set" and "enumerate") and protecting Object.getPrototypeOf and instanceof could be to provide a softer version of proxies where these traps wouldn't be provided. Prototype-climbing internal methods would be delegated to the ECMAScript engine with the default behavior they have on objects (but still calling own traps when their algorithm requires them to). By design of this softer version, proxy writers would be ensured that inheritance will be done properly for them and they don't have to worry about it. They would just have to take care of the own properties "layer". (This paragraph is a slightly different topic. If you feel it requires its own thread, feel free to fork)
I think this "softer version" already exists. If we can get agreement to turn getPropertyDescriptor and getPropertyNames into derived traps (see < strawman:proxy_derived_traps>), then
all of the prototype-climbing traps become derived. As such, proxy writers that stick to overriding only fundamental traps will only need to take care of the "own layer", and inheritance will be taken care of by the default implementation of the derived traps.
Le 22/03/2011 02:22, Mark S. Miller a écrit :
Hi David, are you aware of strawman:proxy_instanceof?
Yes, I am. This strawman suggests to trap if you're a constructor from the right hand side of the expression. My idea is to try to find a trade-off to offer some liberty (not a trap) to observe inheritance for the left hand side as a proxy.
I just heard from Brendan today that Andreas Gal has encountered some DOM emulation issues that might make us want us to consider this sooner rather than later.
Just to make sure we're trying to solve the same problem, I'm going to describe it. Within the DOM interface as used in browsers, an HTML element implements both the Element and the EventTarget interfaces. With the ECMAScript prototypal inheritance mechanism, having "(e instanceof Element && e instanceof EventTarget) === true" would need to mean that all Element instances are also EventTarget or the opposite, both being inaccurate because the two are orthogonal concerns. The problem with ECMAScript as it is is that it doesn't allow "orthogonal inheritance" (aka multiple inheritance)
So we're trying to find a way to offer "(e instanceof Element && e instanceof EventTarget) === true" without imposing anything on the prototype chain.
If I understand the strawman correctly, the approach is to work on the right hand side, add a hasInstance trap to the constructor. The approach I describe in the initial message is to provide to the left-hand side a list of objects which describes the list of [[Prototype]] objects used in the native internal [[HasInstance]] algorithm. One of the main advantage I see with what I have described is that all instances of the same class can communicate through their prototype and still dynamically inherit from the "same" object (same or a proxy forwarding to it. Which is the same thing when it comes to inheritance, but which is different if you really care about object identity).
How well would it also address the cases you have in mind?
Actually, we're trying to solve the same problem with two different approaches, so I would answer yes to this question. Here are a couple of issues I see with working on the right hand side (constructor) though:
- You may have to keep references of all the instances you want to be say true to (I currently do not see how you can do differently) which may cost a lot of memory.
- If you want to define multiple inheritance with your own classes (not DOM), you have to define all your classes as a Proxy.
- instanceof will be inconsistent with Object.getPrototypeOf -- Your object can be "instanceof" one thousand constructors while Object.getPrototypeOf returns null. -- Another problem is that if a1 instanceof A && a2 instanceof A, you would wish a way to make a1 and a2 communicate through A.prototype (shared prototype properties, for instance). Since Object.getPrototypeOf isn't of any help reaching A.prototype, a1 is disconnected from a2 (which is weird since they're supposed to inherit from the same class). You cannot retrieve the prototype to create other object inheriting from the same prototype (var a3 = Object.create(Object.getPrototypeOf(a1)) is not possible)
I have tried to provide an idea (once again, it's incomplete as I have exposed it) on how to keep instanceof and Object.getPrototypeOf consistent with each other to some extent. Of course, it comes at a couple of costs (like the solution in the strawman), but I have kept communication between all object inheriting from the same prototype.
Once again, I understand that it's too dangerous to provide traps to Object.getPrototypeOf (which could return an object with cyclic references) or instanceof (which would provide the ability to the handler writer to keep a reference to the constructor and to create instances of the tested constructor). However, when it comes to emulate new and different inheritance mechanism, I think that proxies should provide something to emulate these new inheritance mechanisms on instanceof and Object.getPrototypeOf, otherwise, you will always face the issues I have raised above under "instanceof will be inconsistent with Object.getPrototypeOf".
Le 22/03/2011 00:08, David Bruant a écrit :
Hi,
Proxies can be helpful to emulate multiple inheritance (journal.stuffwithstuff.com/2011/02/21/multiple-inheritance-in-javascript). Long story short, with the get and set traps, you can emulate this multiple inheritance without having the required prototype chain. However, since "Object.getPrototypeOf" and "instanceof" cannot be trapped, these cannot be used to observe inheritance. There are perfectly valid reasons for which they cannot be trapped, I won't question this. My present suggestion is a mechanism to unable inheritance observation while keeping a couple of language properties safe. "unable"... I actually meant "enable", sorry.
Proxies can be helpful to emulate multiple inheritance (journal.stuffwithstuff.com/2011/02/21/multiple-inheritance-in-javascript). Long story short, with the get and set traps, you can emulate this multiple inheritance without having the required prototype chain. However, since "Object.getPrototypeOf" and "instanceof" cannot be trapped, these cannot be used to observe inheritance. There are perfectly valid reasons for which they cannot be trapped, I won't question this. My present suggestion is a mechanism to unable inheritance observation while keeping a couple of language properties safe.
One ECMAScript invariant when dealing with objects is that the prototype chain of an object is set once for all at object creation. Consequently, for all function F, the ordered list of objects traversed while calling F.[[HasInstance]] (ES5 15.3.5.3 step 4 a) will always be the same throughout the entire object lifetime. My idea consists of being allowed to provide this such a list at proxy creation time. With this list, multiple inheritance can be observed if 'instanceof' calls looks at this list. This is the idea, then comes "how to achieve it?". Everything said from now on is just thoughts of how the idea could be achieved, neither definitive thoughts nor exhaustive.
The idea can be achieved at different degrees of consistency with how objects currently work. Here are the questions that I have asked myself while thinking about the topic: At initialization, should this list replace the second prototype argument of Proxy.create or should it be provided as a complement just for the purpose of instanceof? What level of consistency with Object.getPrototypeOf? Let's imagine for a minute that the list replaces the prototype object as second argument:
// a, b, c are already existing object function A(){} A.prototype = a; function B(){} B.prototype = b; function C(){} C.prototype = c;
var p = Proxy.create(handler, [a,b,c]); // p instanceof A && p instanceof B && p instanceof C === true
Instead of returning "a", "Object.getPrototypeOf(p)" could return something like: "Proxy.create(forwardingHandler(a), [b,c]);". Please notice the recursivity. If the list is empty, Object.getPrototypeOf returns null, obviously. One little issue is that "Object.getPrototypeOf(p) !== Object.getPrototypeOf(p)" since Proxy.create creates a new object (new identity) each time, but a cache will solve the issue easily and the same object can be returned each time. This cache trick will preserve the invariant that an object has a unique prototype chain. Two invariants (at least) are broken, though:
The idea is a bit new, so there are certainly plenty of other questions I haven't thought of. And once again, this was just one possible beginning of how to achieve my idea above.
Anyway, through the "getPropertyDescriptor", "getPropertyNames", "has", "get", "set" and "enumerate" traps (let's call them "prototype-climbing traps" since there behavior on regular object is to climb the prototype chain), there are already plenty of ways to break ECMAScript inheritance invariants. My personal opinion is that "Object.getPrototypeOf" and "instanceof" are currently overprotected with regard to the already broken invariants. I agree that trapping them is too dangerous, because it would provide too much freedom (and there is the security issue with instanceof). However, I think there is room in between to provide some power to the proxy writer while keeping things under control. I have tried to show one such idea and one way to partially achieve this idea, there are different achievement tracks for the same idea and certainly other ideas.
Another way of solving the inconsistency I see in providing the 6 "prototype-climbing" traps ("getPropertyDescriptor", "getPropertyNames", "has", "get", "set" and "enumerate") and protecting Object.getPrototypeOf and instanceof could be to provide a softer version of proxies where these traps wouldn't be provided. Prototype-climbing internal methods would be delegated to the ECMAScript engine with the default behavior they have on objects (but still calling own traps when their algorithm requires them to). By design of this softer version, proxy writers would be ensured that inheritance will be done properly for them and they don't have to worry about it. They would just have to take care of the own properties "layer". (This paragraph is a slightly different topic. If you feel it requires its own thread, feel free to fork)