To be clear, I'm not suggesting behavior like `getOwnPropertyNames` be overridden by anything on the prototype, just a way to use proxies without having to instantiate identical copies that all use the same handler.
I still believe a proxy on the prototype should always have a `receiver` sent to each trap, but if I need to return a proxy for each object it doesn't really matter for the example I made. On Fri, Mar 18, 2016 at 5:43 PM, Tom Van Cutsem <[email protected]> wrote: > 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

