Proxy handler.has() does not have a receiver argument?

Andrea Giammarchi andrea.giammarchi at gmail.com
Fri Mar 18 19:55:10 UTC 2016


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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160318/d918c966/attachment-0001.html>


More information about the es-discuss mailing list