Do we really need the [[HasOwnProperty]] internal method and hasOwn trap

Allen Wirfs-Brock allen at wirfs-brock.com
Tue Nov 13 18:08:48 PST 2012


On Nov 13, 2012, at 12:25 PM, Tom Van Cutsem wrote:

> 2012/11/13 Allen Wirfs-Brock <allen at wirfs-brock.com>
> 
> I think there is agreement that [[HasOwnProperty]] is just an optimization of ToBoolean([[GetOwnPropertuy]]).  Its only purpose is to avoid unnecessary reification of property descriptors. If that optimization isn't important we should just eliminate [[HasOwnProperty]].
> 
> I understand your point that on normal objects, implementations are free to avoid actually allocating a property descriptor for has or hasOwn checks. As far as the spec is concerned, the derived traps are completely unnecessary: we could hypothetically rewrite the entire spec to use only fundamental traps, and implementors would be free to cut as many corners as they want for non-proxy objects.
> 
> When I mentioned that derived traps avoid unnecessary allocations, I was really thinking about this from the point-of-view of the ES6 metaprogrammer. Say I'm creating my own virtual object abstraction whose properties reside in an external map, so I dutifully implement all the traps:
> 
> var store = new Map()
> var p = new Proxy( { } /* target doesn't matter */ , {
>   hasOwn: function(ignoreTarget, name) { return store.has(name); },
>   getOwnPropertyDescriptor: function(ignoreTarget, name) {
>     var e = store.get(name);
>     return e === undefined ? undefined : {value:e, ...};
>   },
>   ...
> };
> 
> Say we remove the "hasOwn()" trap, calling the getOwnPropertyDescriptor trap instead. Now my virtual object abstraction does need to allocate a descriptor. Your proposal of adding a flag to the trap fixes that, but IMHO in a way that confuses the intent of the operation being intercepted:
> 
> var store = new Map()
> var p = new Proxy( { } /* target doesn't matter */ , {
>   getOwnPropertyDescriptor: function(ignoreTarget, name, needDescriptor) {
>     if (!needDescriptor) { return store.has(name); }
>     var e = store.get(name);
>     return e === undefined ? undefined : {value:e, ...};
>   },
>   ...
> };
> 
> It's dawning on me that the main reason I dislike adding flags to the fundamental traps is that it breaks the 1-to-1 symmetry with the operations that they intercept:
> 
> Object.getOwnPropertyDescriptor(proxy, name) // traps as getOwnPropertyDescriptor(target, name)
> 
> This symmetry should make it relatively easy for an ES5 programmer to pick up the handler API. The signatures look familiar (at least for all operations that are expressed as function calls). Adding extra flags breaks the symmetry.

Interesting, the 1-to-1 symmetry I've been look for is between the internal methods and the proxy traps.  To me, that is the MOP and things like Object.getOwnPropertyDescriptor are a connivence layer a level above the MOP.   From that perspective, it feels fine to me to have a MOP operation (a trap or internal method), called say, CheckOwnProperty that has a flag argument that controls whether you want to get a property descriptor or a boolean result.   That MOP operation can be used at the language level to implement the in operator and other language level-semantics and at the library level to implement convenience functions such as O.p.hasOwnProperty and Object.getOwnPropertyDescriptor.  The MOP programmer   (whether implementing ordinary objects in the implementation, host objects using low level extension points or using Proxy to implement exotic objects)r sits in the middle between those two layers.  All they really need to know about is the MOP.

Looking at it another way.  The existing reflection APIs: Object.*, O.p.hasOwnProperty, etc.  weren't really designed as a conceptual unit.  They aren't a complete or self consistent MOP.  I'm not sure we can design a good MOP that doesn't differ in significant ways with those existing APIs. That doesn't worry me too much if we can get a consistent MOP that manifests itself in the spec., in the Proxy traps, and in the Reflect.* APIs. Then we would have a clean foundation that can also be used to define the semantics of the existing APIs that have a less consistent API and build new higher level facilities in the future.

> 
> We've explored the security issues or proxies in depth.  Now is the time to explore the usability issues.  Proxies are not intended for novices but there still will be thousands of developers trying to use proxies who are neither metaprogramming experts or  have a deep understand of the ES MOP.  We should try to minimize the number of footguns and other hazards that they have to face.
> 
> Allen, I'm completely on the same page here. I myself have previously expressed concerns about the inconsistencies introduced by derived traps. I think our disagreement lies only with how to deal with the inconsistencies.

And, I not even so sure we disagree on how to deal.  I've just been exploring alternatives.  Condensing partially redundant traps into a single trap with more parameters is just one approach but not something I'm particularly in love with. I mostly interesting in finding a more usable, less footgun-ish alternative to parts of the current design.


> 
> So if I don't like adding extra flags, what's my counterproposal?
> 
> I knew I previously wrote about some of these issues, and I finally found out where: an old strawman for the old Proxy API: <http://wiki.ecmascript.org/doku.php?id=strawman:derived_traps_forwarding_handler>.
> 
> That strawman defines two possible semantics for derived traps:
> - “forwarding” semantics: forward the derived operation to the target
> - “fallback” semantics: implement the “default” semantics in terms of the fundamental forwarding traps
> 
> "Forwarding" semantics is what you get 'out of the box' with direct proxies.
> To obtain "fallback" semantics for derived traps, you need to subclass Handler.
> 
> I thought this fixed the footgun, except it doesn't deal with the case where one overrides only the derived trap, but leaves the fundamental trap in "forwarding" mode.
> 
> I just realized that in a previous version of the Handler API, the fundamental traps were specced to be "abstract" methods (i.e. they would throw when called). Given that semantics, the Handler API would avoid the footgun entirely. For example:
> 
> 1) if you subclass Handler and override the derived "hasOwn" trap, but forget to implement the fundamental "getOwnPropertyDescriptor" trap, you'll get a noisy exception (instead of inconsistent behavior).
> 2) if you subclass Handler and override the fundamental "getOwnPropertyDescriptor", but forget to override "hasOwn", things will just work (the subclass automatically inherits a compatible "hasOwn" trap).
> 
> So, my proposal: let's revert the fundamental traps of Handler to become abstract methods again. This forces subclasses of Handler to provide all fundamentals at once, avoiding the footgun.
> 
> I'm fully aware that this fixes the footgun *only* if the programmer decided to subclass Handler. I think we need to do a better job evangelizing the Handler API and calling out the benefits of using it (I now know the topic of my next blog post on proxies ;-)

This sounds like a quite reasonable approach to explore.  Note that subclassing  isn't all that much more ugly to write:

var p = new Proxy( { } /* target doesn't matter */ , new class extends Handler {
  getOwnPropertyDescriptor: function(ignoreTarget, name, needDescriptor) {
      ...
  },
  ...
};

Also something I have been thinking about is moving some derived traps out of the the handler interface and into Reflect.  For example, in the spec. I current have [[HasOwnProerty]] as as internal method and HasProperty as an abstract operation that takes an object and property key as arguments.  It doesn't need to be handler dispatched becase the proto chain walking algorithm is so generic.  A simply function could be add to Reflect

Allen


> 
> Cheers,
> Tom

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20121113/351da43b/attachment-0001.html>


More information about the es-discuss mailing list