Sean, Thanks for sharing your alternative design. Some follow-up comments below:
2011/4/18 Sean Eagan <[email protected]> > > I don't think the traps take that many arguments that they merit a > > keyword-arguments API. > > Agreed, the quantity of arguments, even if one were to be added, is > manageable without keyword arguments. However, if any arguments need > to be selectively added to certain traps in the future, then the proxy > would no longer be the last argument to all traps, which could make > the parameter lists difficult to remember. > True, specifying |proxy| as an optional trailing argument makes it difficult to extend the argument lists later without exposing it. Still, I'd argue keyword-arguments aren't worth the extra syntax in the common case. > > The cached getPrototypeOf trap behavior falls prone to the same source of > > confusion that caused us to steer away from defining call and construct > as > > separate traps (cf. the other discussion thread on the uniform proxy > API). > > Note that it is not the "getPrototypeOf" trap that is cached (as I was > proposing for the "call" and "construct" trap in the other thread), > but rather its return value, and that the only reason for that is to > match current (and AFAIK future) behavior that an object's > [[Prototype]] is immutable, in order to not break proxy transparency. > If mutable prototypes were allowed in the future, a "getPrototypeOf" > trap would be needed, but would be, at that point, very difficult to > add since the prototype argument would probably want to be removed > from Proxy.create / Proxy.createFunction, which would involve breaking > existing code. > Whether it's the trap or its return value that is cached, the confusion remains the same. The current API makes it crystal-clear to the programmer that the prototype is to be calculated ahead-of-time and can't be changed. I don't see why this API needs to change. > > I agree with David that the use cases of shared proxy handlers are not > > sufficiently explored to merit the addition of customizable "internal > proxy > > state". > > Agreed, which is why I took some time to start exploring it further > :). Here are my findings up to this point... > > Storing proxy instance state directly within handlers: > ============================================================== > > Trap namespace pollution: > > The default handler, Proxy.Handler, stores proxy instance state > directly in each handler's "target" property. Notice that this > immediately prevents a "target" trap from being added in the future, > which of course isn't so bad as it is an unlikely future trap name. > However, as soon as proxies are standardized and start to be used, if > user defined proxy instance state is allowed (and even strongly > encouraged by implementing Proxy.Handler this way) to be stored in > handlers, the entire remainder of the trap namespace will be open to > pollution, and thus prevent *any* new traps from being added in the > future without the likelihood of breaking existing code. Beyond proxy > instance state, users may also add helper methods including perceived > "missing" traps, which may directly correlate to traps that would be > wanted to be added in the future. Thus, it seems to me that storing > anything in handlers besides existing traps is likely to cause future > headaches, and should be avoided. > This critique can be applied to any OO framework in general. Still, people happily subclass standard classes and add new methods to them without fear of the possible evolution problems (which do exist). You're right about potential backwards-compat. issues, but that didn't stop ES5 from adding new methods on Array.prototype, for example. Extra inheritance chain step when resolving traps: > > Just a minor observation that, if proxy instance state is stored in > the own properties of a handler, and traps are inherited (as with > Proxy.Handler created handlers), then all traps are at least one step > up the inheritance chain. With shared handlers, traps would be on > average about one less step up the inheritance chain. I realize > prototype chain walking is highly optimized, but proxies allow for > transparent non-optimized user defined inheritance. > Given the overall cost of trapping an operation on a proxy, my feeling is that this added inheritance step is a non-issue. Implementors should speak up if it's not. > Existing default handler useless to shared handlers: > ============================================================== > > The existing Proxy.Handler.prototype and all of its traps are useless > to a shared handler because they all have a reference to > "this.target". This could be resolved by replacing these references > with "this.getTarget(proxy)", but that again starts to pollute the > trap namespace. Conversely, with a share-able Proxy.Handler, > handler-per-instance use cases could be accommodated simply by > wrapping a singleton pattern around a handler. > True, the existing Proxy.Handler API was not designed to support shared handlers. I'm not sure whether that is a problem that needs to be addressed. Overall, your proposed API is consistent and does support shared handlers well. The question remains whether the added complexity (i.e. Proxy.Constructor) is worth it. In terms of object allocation, I don't see the benefit of shared handlers as opposed to the current design: - In typical uses of the current design, for N targets, one allocates N virtually-empty proxies, N handler objects that contain per-instance state (e.g. |target|), and 1 shared handler prototype that typically contains all the traps. - In the shared-handler design, for N targets, one allocates N proxies, each storing a reference to each of N state objects (the return values from your |create| trap) and 1 shared state-free handler object. Unless I'm missing something, the net result is the same in both cases. Cheers, Tom > > > Thanks, > Sean Eagan >
_______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss

