I was on board with potentially making `has()` receiver-dependent but you lost me when you expected `getOwnPropertyNames` to trigger a trap on a proxy-as-prototype. Adding `receiver` to every MOP method is a no-go. It fundamentally changes the meaning of these operations and it would destroy the pay-only-when-you-use-it performance model of proxies, since operations that used to be local to only the 'own' object would now need to search the proto-chain for a potential proxy trap.
Using a proxy-as-prototype was never intended as a way to be able to intercept arbitrary operations on whatever object happened to inherit from the proxy. A proxy-as-prototype still emulates only a single object. It just happens to be an object that serves as a prototype for other objects. Cheers, Tom 2016-03-18 23:18 GMT+01:00 Michael Theriot <[email protected]>: > I think I figured out how to make inheritance work... > > ```js > var wm1 = new WeakMap(); function A() { let proxy = new Proxy(this, { get: > (target, property, receiver) => property === 'bacon' || target[property] > }); wm1.set(proxy, {}); return proxy; } var wm2 = new WeakMap(); function > B() { let proxy = A.call(new Proxy(this, { get: (target, property, > receiver) => property === 'ham' || target[property] })); wm2.set(proxy, > {}); return proxy; } var a = new A(); var b = new B(); wm1.has(a); // true > wm2.has(a); // false wm1.has(b); // true wm2.has(b); // true > > a.bacon; // true > a.ham; // undefined > > b.bacon; // true > b.ham; // true > ``` > > I can't imagine this is good for optimizations though. But I guess it does > what I need. > > On Fri, Mar 18, 2016 at 4:45 PM, Michael Theriot < > [email protected]> wrote: > >> Michael’s preferred approach also introduces observable irregularity into >>> the standard JS inheritance model for ordinary objects. >>> Consider an object created using Michael’s preferred approach: >>> ```js >>> var arr = [0, 1]; >>> console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own >>> property >>> var subArr = Object.create(arr); >>> console.log(Reflect.has(subArr,”0”)); //true, all unshadowed properties >>> of proto visible from ordinary objects >>> var b = new ArrayView2(arr); >>> console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array >>> elements visible as if properties of b >>> var subB= Object.create(b); >>> console.log(Reflect.has(subB,”0”)); //false, some unshadowed properties >>> of proto is not visible from subB >>> ``` >> >> >> I think this relates to the original concern; if you could pass the >> receiver this could be resolved. That still leaves `getOwnPropertyNames` >> reporting wrong values though and I see no foreseeable way to resolve that >> without returning an actual proxy itself. >> >> The reason I'm trying this approach is because I read on the MDN that >> when used in the prototype a `receiver` argument is passed that references >> the instance, so I assumed this was the intent behind it. The only other >> explanation I could think of is that proxies have a receiver to mimic the >> `Reflect.set`/`Reflect.get` methods which need a receiver for >> getters/setters to work properly, not so you can use them on the prototype >> chain. >> >> The other case I would make is every instance would have an identical >> proxy, and it just makes sense to put that on the prototype for the same >> reasons you put shared methods/properties there. >> >> Note that we are not really talking about a new capability here. >>> Michael’s first design shows that ES proxies already have the capability to >>> implement the object level semantics he desires. >> >> >> To be fair I had several obstacles with inheritance using the first >> version. >> >> ```js >> var wm1 = new WeakMap(); >> >> function A() { >> wm1.set(this, {}); >> return new Proxy(this, {}); >> } >> >> var wm2 = new WeakMap(); >> >> function B() { >> A.call(this); >> wm2.set(this, {}); >> return new Proxy(this, {}); >> } >> >> var a = new A(); >> var b = new B(); >> >> wm1.has(a); // true >> wm2.has(a); // false >> >> wm1.has(b); // false >> wm2.has(b); // true >> ``` >> >> As you can see storing a reference to `this` can't work anymore, since we >> actually return a proxy. You can try to work around this... >> >> ```js >> var wm1 = new WeakMap(); >> >> function A() { >> let self = this; >> if(new.target === A) { >> self = new Proxy(this, {}); >> } >> wm1.set(self, {}); >> return self; >> } >> >> var wm2 = new WeakMap(); >> >> function B() { >> let self = this; >> if(new.target === B) { >> self = new Proxy(this, {}); >> } >> A.call(self); >> wm2.set(self, {}); >> return self; >> } >> >> var a = new A(); >> var b = new B(); >> >> wm1.has(a); // true >> wm2.has(a); // false >> >> wm1.has(b); // true >> wm2.has(b); // true >> ``` >> >> But then problems arise because the new proxy doesn't go through the old >> proxy. So anything guaranteed by A()'s proxy is not guaranteed by B()'s >> proxy. >> >> ```js >> var wm1 = new WeakMap(); >> >> function A() { >> let self = this; >> if(new.target === A) { >> self = new Proxy(this, { >> get: (target, property, receiver) => property === 'bacon' || >> target[property] >> }); >> } >> wm1.set(self, {}); >> return self; >> } >> >> var wm2 = new WeakMap(); >> >> function B() { >> let self = this; >> if(new.target === B) { >> self = new Proxy(this, { >> get: (target, property, receiver) => property === 'ham' || >> target[property] >> }); >> } >> A.call(self); >> wm2.set(self, {}); >> return self; >> } >> >> var a = new A(); >> var b = new B(); >> >> wm1.has(a); // true >> wm2.has(a); // false >> >> wm1.has(b); // true >> wm2.has(b); // true >> >> a.bacon; // true >> a.ham; // undefined >> >> b.bacon; // undefined >> b.ham; // true >> ``` >> >> (I'm open to solutions on this particular case... One that doesn't >> require me to leak the handler of the A proxy) >> >> Ultimately I can actually achieve both what I want with the ArrayView >> example and inheritance by using a **lot** of `defineProperty` calls on >> `this` in the constructor, but performance is a disaster as you might >> expect. >> >> On Fri, Mar 18, 2016 at 2:55 PM, Andrea Giammarchi < >> [email protected]> wrote: >> >>> AFAIK the reason there is a `receiver` is to deal with prototype cases >>> ... if that was a good enough reason to have one, every prototype case >>> should be considered for consistency sake. >>> >>> We've been advocating prototypal inheritance for 20 years and now it's >>> an obstacle or "not how JS is"? >>> >>> ```js >>> class Magic extends new Proxy(unbe, lievable) { >>> // please make it happen >>> // as it is now, that won't work at all >>> } >>> ``` >>> >>> Best Regards >>> >>> >>> On Fri, Mar 18, 2016 at 7:30 PM, Mark S. Miller <[email protected]> >>> wrote: >>> >>>> I agree with Allen. I am certainly willing -- often eager -- to revisit >>>> and revise old design decisions that are considered done, when I think the >>>> cost of leaving it alone exceeds the cost of changing it. In this case, the >>>> arguments that this extra parameter would be an improvement seem weak. Even >>>> without the revising-old-decision costs, I am uncertain which decision I >>>> would prefer. Given these costs, it seems clear we should leave this one >>>> alone. >>>> >>>> Unless it turns out that the cost of leaving it alone is much greater >>>> than I have understood. If so, please help me see what I'm missing. >>>> >>>> >>>> >>>> >>>> >>>> >>>> On Fri, Mar 18, 2016 at 12:17 PM, Allen Wirfs-Brock < >>>> [email protected]> wrote: >>>> >>>>> >>>>> On Mar 18, 2016, at 9:24 AM, Andrea Giammarchi < >>>>> [email protected]> wrote: >>>>> >>>>> Agreed with everybody else the `receiver` is always needed and `Proxy` >>>>> on the prototype makes way more sense than per instance. >>>>> >>>>> >>>>> I don’t agree. While you certainly can imagine a language where each >>>>> object’s “prototype” determines that object’s fundamental behaviors and >>>>> provides the MOP intercession hooks(in fact that’s how most class-based >>>>> languages work). But that’s not the JS object model. Each JS object is >>>>> essentially a singleton that defines it’s own fundamental behaviors. >>>>> Whether or this model is better or worse than the class-based model isn't >>>>> really relevant, but in the context of JS there are advantage to >>>>> consistently adhering to that model, >>>>> >>>>> For example, in Michael’s desired approach, the instance objects of >>>>> his ArrayView abstraction are “ordinary objects”. One of the fundamental >>>>> behavioral characteristics of ordinary objects is that all of there own >>>>> properties are defined and available to the implementation in a standard >>>>> way. Implementations certainly make use of that characteristic for >>>>> optimization purposes. Michael’s approach would make such optimizations >>>>> invalid because every time an own property needed to be access a prototype >>>>> walk would have to be performed because there might be an exotic object >>>>> somewhere on the prototype chain that was injecting own property into the >>>>> original “receiver”. >>>>> >>>>> Michael’s preferred approach also introduces observable irregularity >>>>> into the standard JS inheritance model for ordinary objects. >>>>> >>>>> Consider an object created using Michael’s preferred approach: >>>>> >>>>> ```js >>>>> var arr = [0, 1]; >>>>> console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own >>>>> property >>>>> var subArr = Object.create(arr); >>>>> console.log(Reflect.has(subArr,”0”)); //true, all unshadowed >>>>> properties of proto visible from ordinary objects >>>>> >>>>> var b = new ArrayView2(arr); >>>>> console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array >>>>> elements visible as if properties of b >>>>> var subB= Object.create(b); >>>>> console.log(Reflect.has(subB,”0”)); //false, some unshadowed >>>>> properties of proto is not visible from subB >>>>> ``` >>>>> >>>>> Note the his original Proxy implementation does not have this >>>>> undesirable characteristic. >>>>> >>>>> So what about the use of `receiver` in [[Get]]/[[Set]]. That’s a >>>>> different situation. [[Get]]/[[Set]] are not fundamental, rather they are >>>>> derived (they work by applying other more fundamental MOP operations). The >>>>> `receiver` argument is not used by them to perform property lookup (they >>>>> use [[GetOwnProperty]] and [[GetPrototypeOf]]) for the actual property >>>>> lookup). `receiver` is only used in the semantics of what happens after >>>>> the property lookup occurs. Adding a `receiver` argument to the other MOP >>>>> operations for the purpose of changing property lookup semantics seems >>>>> like >>>>> a step too far. The ES MOP design is a balancing act between capability, >>>>> implementability, and consistency. I think adding `receiver` to every MOP >>>>> operation would throw the design out of balance. >>>>> >>>>> Finally, >>>>> >>>>> Note that we are not really talking about a new capability here. >>>>> Michael’s first design shows that ES proxies already have the capability >>>>> to >>>>> implement the object level semantics he desires. So, we are only talking >>>>> about exactly how he goes about using Proxy to implement that semantics. >>>>> He >>>>> would prefer a different Proxy design than what was actually provided by >>>>> ES6. But that isn’t what was specified or what has now been implemented. >>>>> We >>>>> can all imagine how many JS features might be “better” if they worked >>>>> somewhat differently. But that generally isn’t an option. The existing >>>>> language features and their implementations are what they are and as JS >>>>> programmers we need to work within that reality. >>>>> >>>>> Allen >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> Also the `getPrototypeOf` trap is really pointless right now >>>>> >>>>> ```js >>>>> function Yak() {} >>>>> Yak.prototype = new Proxy(Yak.prototype, { >>>>> getPrototypeOf: (target) => console.log('lulz') >>>>> }); >>>>> >>>>> var yup = new Yak; >>>>> Object.getPrototypeOf(yup); >>>>> ``` >>>>> >>>>> The `target` is actually the original `Yak.prototype` which is already >>>>> the `yup` prototype: useless trap if used in such way. >>>>> >>>>> Being also unable to distinguish between `getOwnPropertyNames` vs >>>>> `keys` is a bit weird. >>>>> >>>>> `Proxy` looks so close to be that powerful but these bits make it >>>>> kinda useless for most real-world cases I've been recently dealing with. >>>>> >>>>> Thanks for any sort of improvement. >>>>> >>>>> Regards >>>>> >>>>> >>>>> >>>>> >>>>> On Fri, Mar 18, 2016 at 1:54 PM, Michael Theriot < >>>>> [email protected]> wrote: >>>>> >>>>>> I'm trying to make the proxy-as-a-prototype pattern work but I've >>>>>> just discovered the `ownKeys` trap is never called on traps on the >>>>>> prototype. So even if the `has` trap is allowed to see the `receiver`, >>>>>> and >>>>>> thus verify the properties "0", "1" exist, this pattern would fail to >>>>>> return the properties "0", "1" exist on an `Object.getOwnPropertyNames` >>>>>> call. Disappointing! I'd rather use a proxy on the prototype than create >>>>>> one for each instance but without a correct `ownKeys` return it just >>>>>> doesn't come full circle. Is there a trick to make this work or am I out >>>>>> of >>>>>> luck here? I can only think of actually defining the properties to make >>>>>> it >>>>>> work, which defeats the idea of using a proxy on the prototype to begin >>>>>> with. >>>>>> >>>>>> Regardless I agree that traps called on a prototype chain should >>>>>> always receive the `receiver` as an argument. I think the only trap other >>>>>> than `set`, `get`, and `has` that can do this is the `getPrototypeOf` >>>>>> trap >>>>>> (currently does not have a `receiver`) when the `instanceof` check needs >>>>>> to >>>>>> climb the prototype chain. >>>>>> >>>>>> On Thu, Mar 17, 2016 at 6:29 PM, Tom Van Cutsem <[email protected]> >>>>>> wrote: >>>>>> >>>>>>> The rationale for not having a `receiver` argument to `has` is that >>>>>>> the value produced by the "in" operator is not normally dependent on the >>>>>>> receiver. This is in contrast with `get` and `set` which may find an >>>>>>> accessor up the proto chain that needs to run with a `this` bound to the >>>>>>> receiver. >>>>>>> >>>>>>> That said, I follow your line of reasoning and it is true that >>>>>>> `has`, `get` and `set` are the three traps that can be called on a >>>>>>> proxy-used-as-prototype (now that `enumerate` is considered >>>>>>> deprecated), so >>>>>>> it would be consistent to allow all of them to refer back to the >>>>>>> original >>>>>>> receiver. This enables the general pattern that you illustrate. >>>>>>> >>>>>>> As you note, the weirdness of this is apparent because it doesn't >>>>>>> normally make sense to pass a `receiver` argument to Reflect.has(). >>>>>>> However, if `receiver` would be made visible in a Proxy handler's `has` >>>>>>> trap, then `Reflect.has` should nevertheless be likewise extended so >>>>>>> that >>>>>>> one can faithfully forward the `receiver` argument. >>>>>>> >>>>>>> Spec-wise, I think the only significant change is that 7.3.10 >>>>>>> HasProperty >>>>>>> <http://www.ecma-international.org/ecma-262/6.0/#sec-hasproperty>, >>>>>>> step 3 must be changed to `O.[[HasProperty]](P, O)` and all >>>>>>> [[HasProperty]] >>>>>>> internal methods must likewise be extended with an extra argument (which >>>>>>> they ignore). Only the Proxy implementation in 9.5.7 would then actually >>>>>>> refer to that argument. >>>>>>> >>>>>>> Cheers, >>>>>>> Tom >>>>>>> >>>>>>> 2016-03-17 11:46 GMT+01:00 Michael Theriot < >>>>>>> [email protected]>: >>>>>>> >>>>>>>> I feel like it should, or I am misunderstanding something >>>>>>>> fundamental. I made a basic scenario to explain: >>>>>>>> >>>>>>>> ```js >>>>>>>> var arrays = new WeakMap(); >>>>>>>> >>>>>>>> function ArrayView(array) { >>>>>>>> arrays.set(this, array); >>>>>>>> >>>>>>>> return new Proxy(this, { >>>>>>>> set: (target, property, value) => (arrays.has(this) && property >>>>>>>> in arrays.get(this)) ? arrays.get(this)[property] = value : >>>>>>>> target[property] = value, >>>>>>>> get: (target, property) => (arrays.has(this) && property >>>>>>>> in arrays.get(this)) ? arrays.get(this)[property] : >>>>>>>> target[property], >>>>>>>> has: (target, property) => (arrays.has(this) && property >>>>>>>> in arrays.get(this)) || property in target >>>>>>>> }); >>>>>>>> } >>>>>>>> >>>>>>>> ArrayView.prototype = Object.create(Array.prototype, { >>>>>>>> arrayLength: { >>>>>>>> get() { >>>>>>>> return arrays.get(this).length; >>>>>>>> } >>>>>>>> } >>>>>>>> }); >>>>>>>> ``` >>>>>>>> >>>>>>>> When `new ArrayView(somearray)` is called the reference to >>>>>>>> `somearray` is stored in the `arrays` weak map and a proxy is returned >>>>>>>> that >>>>>>>> allows you to manipulate indices on it, or fallback to the object for >>>>>>>> other >>>>>>>> properties. >>>>>>>> >>>>>>>> This could be simplified by putting the proxy on the prototype >>>>>>>> chain to reduce overhead and actually return a genuine `ArrayView` >>>>>>>> object >>>>>>>> instead: >>>>>>>> >>>>>>>> ```js >>>>>>>> var arrays = new WeakMap(); >>>>>>>> >>>>>>>> function ArrayView2(array) { >>>>>>>> arrays.set(this, array); >>>>>>>> } >>>>>>>> >>>>>>>> var protoLayer = Object.create(Array.prototype, { >>>>>>>> arrayLength: { >>>>>>>> get() { >>>>>>>> return arrays.get(this).length; >>>>>>>> } >>>>>>>> } >>>>>>>> }); >>>>>>>> >>>>>>>> ArrayView2.prototype = new Proxy(protoLayer, { >>>>>>>> set: (target, property, value, receiver) => (arrays.has(receiver) >>>>>>>> && property in arrays.get(receiver)) ? arrays.get(receiver)[property] >>>>>>>> = >>>>>>>> value : Reflect.set(target, property, value, receiver), >>>>>>>> get: (target, property, receiver) => (arrays.has(receiver) >>>>>>>> && property in arrays.get(receiver)) ? arrays.get(receiver)[property] >>>>>>>> : Reflect.get(target, property, receiver), >>>>>>>> has: (target, property) => (arrays.has(target) >>>>>>>> && property in arrays.get(target)) || Reflect.has(target, property) >>>>>>>> }); >>>>>>>> ``` >>>>>>>> >>>>>>>> Under this setup `target` refers to the protoLayer object which is >>>>>>>> useless here, but we can use the `receiver` argument in its place to >>>>>>>> access >>>>>>>> the weak map, and replace our set/get operations with >>>>>>>> Reflect.set/Reflect.get calls to the target (protoLayer) using a >>>>>>>> receiver >>>>>>>> (the instance) to pass the correct `this` value to the `arrayLength` >>>>>>>> getter >>>>>>>> and prevent infinite recursion. >>>>>>>> >>>>>>>> One problem - handler.has() lacks a receiver argument. So in this >>>>>>>> scenario when using the `in` operator it will always fail on array >>>>>>>> properties because we cannot check the weak map by passing in the >>>>>>>> instance. >>>>>>>> >>>>>>>> ```js >>>>>>>> var arr = [0, 1]; >>>>>>>> >>>>>>>> var a = new ArrayView(arr); >>>>>>>> a.arrayLength; // 2 >>>>>>>> 'arrayLength' in a; // true >>>>>>>> '0' in a; // true >>>>>>>> '1' in a; // true >>>>>>>> '2' in a; // false >>>>>>>> >>>>>>>> var b = new ArrayView2(arr); >>>>>>>> b.arrayLength; // 2 >>>>>>>> 'arrayLength' in b; // true >>>>>>>> '0' in b; // false >>>>>>>> '1' in b; // false >>>>>>>> '2' in b; // false >>>>>>>> ``` >>>>>>>> >>>>>>>> Without a receiver argument on handler.has(), it is practically >>>>>>>> useless for proxies used as a prototype. You can't reference the >>>>>>>> instance >>>>>>>> calling it and your target is simply the parent prototype. >>>>>>>> >>>>>>>> Is there a reason the handler.has() trap should not obtain the >>>>>>>> receiver when used on the prototype chain? I can understand why >>>>>>>> Reflect.has() wouldn't have a receiver argument (that wouldn't make >>>>>>>> sense) >>>>>>>> but this seems like a legitimate use for it. Otherwise I don't see a >>>>>>>> reason >>>>>>>> to use the handler.has() trap at all on prototype proxies except for >>>>>>>> bizarre behaviors that have nothing to do with the instance. It will >>>>>>>> always >>>>>>>> have the same behavior across all instances since you can't >>>>>>>> differentiate >>>>>>>> them. >>>>>>>> >>>>>>>> _______________________________________________ >>>>>>>> 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 >>>>>> >>>>>> >>>>> _______________________________________________ >>>>> 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 >>>>> >>>>> >>>> >>>> >>>> -- >>>> Cheers, >>>> --MarkM >>>> >>> >>> >>> _______________________________________________ >>> 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 > >
_______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss

