michael.lee.theriot at gmail.com (2016-03-18T23:43:11.586Z)
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.
Edit:
Actually I seem to have it backwards... I believe the correct way would be the following:
```js
function B() {
let proxy = new Proxy(A.call(this), {
get: (target, property, receiver) => property === 'ham' || target[property]
});
wm2.set(proxy, {});
return proxy;
}
```michael.lee.theriot at gmail.com (2016-03-18T22:22:46.090Z)
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.
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 < michael.lee.theriot at gmail.com> 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 < > andrea.giammarchi at gmail.com> 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 <erights at google.com> >> 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 < >>> allen at wirfs-brock.com> wrote: >>> >>>> >>>> On Mar 18, 2016, at 9:24 AM, Andrea Giammarchi < >>>> andrea.giammarchi at gmail.com> 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 < >>>> michael.lee.theriot at gmail.com> 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 <tomvc.be at gmail.com> >>>>> 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 < >>>>>> michael.lee.theriot at gmail.com>: >>>>>> >>>>>>> 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 >>>>>>> es-discuss at mozilla.org >>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>> >>>>>>> >>>>>> >>>>> >>>>> _______________________________________________ >>>>> es-discuss mailing list >>>>> es-discuss at mozilla.org >>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>> >>>>> >>>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>>> >>>> >>>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>>> >>> >>> >>> -- >>> Cheers, >>> --MarkM >>> >> >> >> _______________________________________________ >> es-discuss mailing list >> es-discuss at mozilla.org >> https://mail.mozilla.org/listinfo/es-discuss >> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160318/99184ad3/attachment-0001.html>