[ES Harmony Proxies] Feedback on implementing arrays with proxies
This sounds like it is going to be a useful experiment. It's closely related to why I didn't the analysis that is available at www.wirfs-brock.com/allen/things/es5-technial-notes-and-resources. The ES5 array spec. is written based upon the ubderstanding that [[DefineOwnProperty]] is a "fundamental" operation that all other "internal methods" and operations use to actually store property values. Since these internal methods provide the linkage between the surface syntax/semantics of the language and the internal object semantics it is going to be important that proxy traps provide an identical mapping.
I think in the end we will probably have to refactor both the current "internal methods" and the current traps but I think we can arrive at a design works.
2011/1/28 David Bruant <bruant at enseirb-matmeca.fr>
Hi,
I have decided to work on trying to (re-)implement native arrays with proxies. All the code is here: DavidBruant/ProxyArrayand I'd like to provide some feedback.
- The approach The basic idea was to:
- use a forwarding proxy
- change the defineProperty trap into an adapted version of ES5 15.4.5.1 ([[DefineOwnProperty internal method of native array objects]])
- reimplement the constructor.
- First issue: the 'set' trap (and all derived traps) The very first issue I've encounter was that setting a property on the proxy was (obviously) forwarded on the target using the 'set' trap. Consequently, my defineProperty was just bypassed. It got me thinking of the forwarding proxy example (which is on its way to being standardized: harmony:proxy_defaulthandler). Would it make sense to define a default forwarding proxy as a proxy "in sync" with an object? This definition would deserve to be more formalized, but the idea is that if you write: var h = new Proxy.Handler(obj); var p = Proxy.create(h, Object.getPrototypeOf(obj)); then your program cannot tell the difference between obj and p besides:
- if you do preventExtension/seal/freeze on the proxy (which fix it proxy and break the inner link between obj and p).
- obj !== p
I think this is correct, except for the detail that if 'obj' is a function object, you'd need to wrap it in a function proxy to make sure typeof, call and construct are consistent for p and obj.
Based on that definition, we could have a stronger definition of derived traps: Derived trap are defined as ES code using fundamental traps in order to respect the forwarding proxy definition. Before going any further, I'd like to say that it's already the case :-) In my opinion, that was the rational behind Mark Miller's "coherent behavior to fall back to" idea of what should be derived or fundamental. Coherent with what? With what we expect from native objects. To solve my problem with proxyArrays, I got rid of all derived traps definitions in the forwarding proxy code and it worked perfectly. It could make sense to do exactly the same with the default proxy forwarding handler, otherwise, people who just want to implement a fundamental trap (like I did) will have to reimplement all derived traps depending on the fundamental and it is very likely that their reimplementation will be the derived trap default definition.
Very interesting observation. So the issue is that the derived traps of the "default forwarding handler" have two possible default implementations: a) forward the derived operation to the 'target' (that is how they are specified now) b) implement the "default" semantics in terms of the fundamental forwarding traps (or equivalently, the default forwarding handler has no derived traps)
The problem with a) is that child objects of the default forwarding handler that override a fundamental trap must actually also override all derived traps that depend on it. In some cases, the trap implementor will want to do this anyway since the derived traps may allow a more efficient implementation, but if not, then indeed the programmer must either reimplement the default semantics, or delete the inherited derived trap.
The problem with b) is that if the "target" object to which the proxy forwards is itself a proxy p2, then p2's derived traps will never be called. Instead, only p2's fundamental traps will be called. Things won't break, but it is suboptimal if p2 has ad hoc (presumably more efficient) implementations for its derived traps. Presumably, even if "target" is a native object, option a) is more efficient than defaulting to the fundamental operations.
I have no good suggestion yet for how to address both issues. One way out would be to provide two default forwarding handlers (e.g. a FundamentalHandler that only defines the fundamental traps (and hence has b) semantics for its derived traps), and a DerivedHandler that inherits from the FundamentalHandler that adds the derived traps with a) semantics). Depending on the required semantics, the programmer can make his/her custom handler inherit from either one. But maybe this is just too complex a solution.
- getPropertyDescriptor Since this function isn't implemented in FF4 and that there is no access to the proxy object, it was impossible to reach the prototype. "Fortunately", I knew that it had to be Array.prototype. I have hardcoded the prototype chain walk. We have already discussed this issue in previous e-mails.
I hope this feedback will be helpful.
Yes, very helpful!
Le 30/01/2011 16:58, Tom Van Cutsem a écrit :
2011/1/28 David Bruant <bruant at enseirb-matmeca.fr <mailto:bruant at enseirb-matmeca.fr>>
Based on that definition, we could have a stronger definition of derived traps: Derived trap are defined as ES code using fundamental traps in order to respect the forwarding proxy definition. Before going any further, I'd like to say that it's already the case :-) In my opinion, that was the rational behind Mark Miller's "coherent behavior to fall back to" idea of what should be derived or fundamental. Coherent with what? With what we expect from native objects. To solve my problem with proxyArrays, I got rid of all derived traps definitions in the forwarding proxy code and it worked perfectly. It could make sense to do exactly the same with the default proxy forwarding handler, otherwise, people who just want to implement a fundamental trap (like I did) will have to reimplement all derived traps depending on the fundamental and it is very likely that their reimplementation will be the derived trap default definition.
Very interesting observation. So the issue is that the derived traps of the "default forwarding handler" have two possible default implementations: a) forward the derived operation to the 'target' (that is how they are specified now) b) implement the "default" semantics in terms of the fundamental forwarding traps (or equivalently, the default forwarding handler has no derived traps)
The problem with a) is that child objects of the default forwarding handler that override a fundamental trap must actually also override all derived traps that depend on it. In some cases, the trap implementor will want to do this anyway since the derived traps may allow a more efficient implementation, but if not, then indeed the programmer must either reimplement the default semantics, or delete the inherited derived trap.
The problem with b) is that if the "target" object to which the proxy forwards is itself a proxy p2, then p2's derived traps will never be called. Instead, only p2's fundamental traps will be called. Things won't break, but it is suboptimal if p2 has ad hoc (presumably more efficient) implementations for its derived traps. Presumably, even if "target" is a native object, option a) is more efficient than defaulting to the fundamental operations.
I have no good suggestion yet for how to address both issues. One way out would be to provide two default forwarding handlers (e.g. a FundamentalHandler that only defines the fundamental traps (and hence has b) semantics for its derived traps), and a DerivedHandler that inherits from the FundamentalHandler that adds the derived traps with a) semantics). Depending on the required semantics, the programmer can make his/her custom handler inherit from either one. But maybe this is just too complex a solution.
I do not have a strong opinion on the complexity of the solution, but i find it quite elegant. It solves both problems and keeps being consistent with the handler model by using inheritance.
If I just had one thing to say it would only be about the naming. "DerivedHandler" could confuse programmers who could think that a DerivedHandler only defines derived traps while the derived traps are only the own properties. I think that "Handler" could be kept as the name, only defining derived traps as own properties and inheriting fundamental from FundamentalHandler.
I have decided to work on trying to (re-)implement native arrays with proxies. All the code is here: DavidBruant/ProxyArray and I'd like to provide some feedback.
Based on that definition, we could have a stronger definition of derived traps: Derived trap are defined as ES code using fundamental traps in order to respect the forwarding proxy definition. Before going any further, I'd like to say that it's already the case :-) In my opinion, that was the rational behind Mark Miller's "coherent behavior to fall back to" idea of what should be derived or fundamental. Coherent with what? With what we expect from native objects. To solve my problem with proxyArrays, I got rid of all derived traps definitions in the forwarding proxy code and it worked perfectly. It could make sense to do exactly the same with the default proxy forwarding handler, otherwise, people who just want to implement a fundamental trap (like I did) will have to reimplement all derived traps depending on the fundamental and it is very likely that their reimplementation will be the derived trap default definition.
I hope this feedback will be helpful.
Thanks for reading!