Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
To pick up this thread again, I gave a talk about the trade-offs in designing JS proxies at a meeting last week. The people following this thread may be interested in seeing the slides: http://soft.vub.ac.be/~tvcutsem/invokedynamic/presentations/Tradeoffs_WGLD2012_Austin.pdf (the talk tremendously simplifies the story to cram it in 25 minutes. I added some minimal notes to make the slides easier to follow. The talk is more or less a story about how we started out with non-direct proxies and ended up with direct-proxies. It doesn't provide any new insights or discuss the recent notification proxies or action-proxies. Cheers, Tom 2012/12/3 David Bruant bruan...@gmail.com Le 03/12/2012 16:38, Mark S. Miller a écrit : What eternal[1] invariant does this bypass? [1] https://mail.mozilla.org/**pipermail/es-discuss/2011-May/** 014150.htmlhttps://mail.mozilla.org/pipermail/es-discuss/2011-May/014150.html Apparently none... Yet, additionally to the last case I showed, there is also: var p = new Proxy({a:1}, { isExtensible: function(target, action){ var v = action(); Object.preventExtensions(**target); } }) // real life-like code? if(Object.isExtensible(target)**){ Object.defineProperty(target, 'b', {value: 37}); // throws } I agree it's not an eternal invariant, but it's quite surprising. [cc'ing Tom to make sure he reads this part] Arguably, the isExtensible, seal and freeze trap could have no action and just forward to the target. That's what the current invariant enforcement suggests anyway (Invariant check: check whether the boolean trap result is equal to isFrozen(target), isSealed(target) or isExtensible(target)). This applies to current proxies actually. Maybe their return value could just be ignored. Trap authors have all the information they need with the trap arguments to decide whether they want to throw. For the rest, the operation can just be forwarded to the target (which it has to for invariant check already). Proxies-with-action have this weird taste of one-move ahead; like if they could run what they're expected and then play a bit more of the game before actually showing their before-last move. It makes sense they do not violate eternal invariants Although not rigorously necessary when it comes to the very minimalistic eternal invariants, the current proxies provides some guarantees by design which are nice both for whoever writes handlers and whoever manipulates proxies. Unrelated, but I think that custom property descriptor attributes are lost with action-proxies. I'm not sure yet what is a good way to recover them. David On Mon, Dec 3, 2012 at 3:33 AM, David Bruant bruan...@gmail.com wrote: Le 03/12/2012 00:06, David Bruant a écrit : The call to action performs the original operation on target and remembers the result. After the trap returns, the proxy returns the remembered result of action. target = {a:1}; var p = new Proxy(target, { get: function(target, name, action){ var v = action(); target[name] = 2; } }) p.a; // ? If p.a is 1 because the call to action remembered the 1, then the target and the proxy look awkwardly desynchronized. To know what's being returned from the trap, one needs to remember when the action is being called which makes writing traps harder I feel. With the current design, looking at the return statements is enough. If p.a is 2, I don't understand the point of action. Or at least, I don't see how it's better than just calling Reflect[trap]. I've found much MUCH worse: target = {a:1}; var p = new Proxy(target, { get: function(target, name, action){ var v = action(); Object.defineProperty(target, name, {value:2, configurable: false, writable:false}) } }) p.a; // 1 ? In that case, the mechanism used to bypass invariant checking is a way to bypass invariants entirely. David __**_ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/**listinfo/es-discusshttps://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Le 03/12/2012 00:06, David Bruant a écrit : The call to action performs the original operation on target and remembers the result. After the trap returns, the proxy returns the remembered result of action. target = {a:1}; var p = new Proxy(target, { get: function(target, name, action){ var v = action(); target[name] = 2; } }) p.a; // ? If p.a is 1 because the call to action remembered the 1, then the target and the proxy look awkwardly desynchronized. To know what's being returned from the trap, one needs to remember when the action is being called which makes writing traps harder I feel. With the current design, looking at the return statements is enough. If p.a is 2, I don't understand the point of action. Or at least, I don't see how it's better than just calling Reflect[trap]. I've found much MUCH worse: target = {a:1}; var p = new Proxy(target, { get: function(target, name, action){ var v = action(); Object.defineProperty(target, name, {value:2, configurable: false, writable:false}) } }) p.a; // 1 ? In that case, the mechanism used to bypass invariant checking is a way to bypass invariants entirely. David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
What eternal[1] invariant does this bypass? [1] https://mail.mozilla.org/pipermail/es-discuss/2011-May/014150.html On Mon, Dec 3, 2012 at 3:33 AM, David Bruant bruan...@gmail.com wrote: Le 03/12/2012 00:06, David Bruant a écrit : The call to action performs the original operation on target and remembers the result. After the trap returns, the proxy returns the remembered result of action. target = {a:1}; var p = new Proxy(target, { get: function(target, name, action){ var v = action(); target[name] = 2; } }) p.a; // ? If p.a is 1 because the call to action remembered the 1, then the target and the proxy look awkwardly desynchronized. To know what's being returned from the trap, one needs to remember when the action is being called which makes writing traps harder I feel. With the current design, looking at the return statements is enough. If p.a is 2, I don't understand the point of action. Or at least, I don't see how it's better than just calling Reflect[trap]. I've found much MUCH worse: target = {a:1}; var p = new Proxy(target, { get: function(target, name, action){ var v = action(); Object.defineProperty(target, name, {value:2, configurable: false, writable:false}) } }) p.a; // 1 ? In that case, the mechanism used to bypass invariant checking is a way to bypass invariants entirely. David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss -- Cheers, --MarkM ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Le 03/12/2012 16:38, Mark S. Miller a écrit : What eternal[1] invariant does this bypass? [1] https://mail.mozilla.org/pipermail/es-discuss/2011-May/014150.html Apparently none... Yet, additionally to the last case I showed, there is also: var p = new Proxy({a:1}, { isExtensible: function(target, action){ var v = action(); Object.preventExtensions(target); } }) // real life-like code? if(Object.isExtensible(target)){ Object.defineProperty(target, 'b', {value: 37}); // throws } I agree it's not an eternal invariant, but it's quite surprising. [cc'ing Tom to make sure he reads this part] Arguably, the isExtensible, seal and freeze trap could have no action and just forward to the target. That's what the current invariant enforcement suggests anyway (Invariant check: check whether the boolean trap result is equal to isFrozen(target), isSealed(target) or isExtensible(target)). This applies to current proxies actually. Maybe their return value could just be ignored. Trap authors have all the information they need with the trap arguments to decide whether they want to throw. For the rest, the operation can just be forwarded to the target (which it has to for invariant check already). Proxies-with-action have this weird taste of one-move ahead; like if they could run what they're expected and then play a bit more of the game before actually showing their before-last move. It makes sense they do not violate eternal invariants Although not rigorously necessary when it comes to the very minimalistic eternal invariants, the current proxies provides some guarantees by design which are nice both for whoever writes handlers and whoever manipulates proxies. Unrelated, but I think that custom property descriptor attributes are lost with action-proxies. I'm not sure yet what is a good way to recover them. David On Mon, Dec 3, 2012 at 3:33 AM, David Bruant bruan...@gmail.com wrote: Le 03/12/2012 00:06, David Bruant a écrit : The call to action performs the original operation on target and remembers the result. After the trap returns, the proxy returns the remembered result of action. target = {a:1}; var p = new Proxy(target, { get: function(target, name, action){ var v = action(); target[name] = 2; } }) p.a; // ? If p.a is 1 because the call to action remembered the 1, then the target and the proxy look awkwardly desynchronized. To know what's being returned from the trap, one needs to remember when the action is being called which makes writing traps harder I feel. With the current design, looking at the return statements is enough. If p.a is 2, I don't understand the point of action. Or at least, I don't see how it's better than just calling Reflect[trap]. I've found much MUCH worse: target = {a:1}; var p = new Proxy(target, { get: function(target, name, action){ var v = action(); Object.defineProperty(target, name, {value:2, configurable: false, writable:false}) } }) p.a; // 1 ? In that case, the mechanism used to bypass invariant checking is a way to bypass invariants entirely. David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
2012/11/28 Brandon Benvie bran...@brandonbenvie.com Object.observe would fit under after correct? Yes, with two remarks: - it only traps updating operations - the traps are called in a separate turn (i.e. asynchronously), so they cannot change the result of the operation Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
2012/11/28 David Bruant bruan...@gmail.com I don't understand why a unique token per trap invocation would be necessary. I still don't understand this point. I've gone spelunking. I've found: * the wiki page [1] (reflecting July meeting) which mentions that returning undefined would express I don't know the private name, please forward * Confirmed on July 31st [2]. * Introduction of the idea of putting the verification of known names somewhere else than for each trap return [3]. Some discussion in between about this idea. Introduction of the idea of adding a third argument [4] after which I think stops all discussions about returning something in traps to prove knowledge of a private name or forwarding when not knowing. I don't remember the point about a token per trap invocation and I haven't been able to find it (but I haven't read everything). In any case, for throw ForwardToTarget, I don't see why it would be necessary. It seems it would work unambiguously with meta-handlers, with target-as-a-proxy or with manipulate-any-other-proxy-inside-a-trap (which target-as-a-proxy is an instance of). I think throwing a special token, as is done with StopIteration would probably work in practice (little risk of confusing it with a legitimately returned value). However, it does require every trap invocation to be wrapped in a try-catch block to potentially catch that error. Maybe I'm too influenced by the JVM, but my understanding is that wrapping every call to a trap with a try-catch block won't be free. Perhaps implementors can comment. Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
2012/11/29 Dean Tribble dtrib...@gmail.com As a general point, I encourage you to look for other inspiration than CLOS MOP for doign proxies (whose mother was really InterlispD). Meta-level access deeply impacts security,maintainability, reliability, understandability, etc. The tighter and more structured you can make your meta-level access, the easier it will be to to implement, use, and maintain (e.g., both coroutines and downward functions are more understandable, easier to implement, easier to secure, etc. than general continuations and call-cc). I agree (side note: the CLOS MOP didn't form a direct inspiration for JS proxies, although I'm probably influenced by having studied it). CLOS method combinations allow a composer to distinguish between before, after and around-style composition: - before-style wrapping gives you only the ability to get notified before an operation happens. You can abort, but not change, the result of the operation. This is what notification-proxies offer. You *can* change the result of the operation. You do so by modifying the state before the operation proceeds, of course. You could also extend the notification support to notify after so you could clenup (avoiding a callback hack). Indeed. I think notification proxies would benefit from both before + after notification so any cleanup of virtual properties can be done directly. That just leaves the thorny issue that for virtual object abstractions, having to set-up the target in a before-handler and clean-up the target in an after-handler is really a very indirect way of expressing the abstraction, especially if the proxy doesn't need to virtualize any invariants. - after-style wrapping allows you to get notified of an operation after-the-fact. Depending on the API, the after-wrapper may or may not get to see the outcome of the operation, and may or may not change the final outcome passed on to clients. - around-style wrapping is the most general and allows the composer to decide if and when to forward, and what result to return. It subsumes before/after wrapping. This is what direct proxies currently provide. It does not subsume before/after wrapping, because it loses the integrity of before/after (e.g., the wrapper can lie and cheat, where the before and after cannot). That may be worth it, but it is substantially different. You're right, around doesn't subsume before+after in that regard. Thanks for clarifying. Another variant is the differential version: the differential trap is like a notification, but it can also return virtual additions (or an iterator of additions). The proxy then invokes the primitive on the target, and appends (with de-dupping, etc.) the virtual additions. This allows the simple case to just use hte target, but also allows all of Allen's additional cases. As far as I can tell, virtual object abstractions like remote/persistent objects require around-style wrapping, because there's otherwise no meaningful target to automatically forward to. I thought the target in that case is an internal object to represent or reify the meta-state of the remote or persistent object. I think that still makes sense in both the persistent object and remote object cases. It does. It just feels awkward, after having been able to express these abstractions more directly with the current Proxy API for so long. Here's a list of use cases that I frequently have in mind when thinking about proxies, categorized according to whether the use case requires before/after/around wrapping: Virtual objects, hence around-style: - self-hosting exotic objects such as Date, Array (i.e. self-host an ES5/ES6 environment) - self-hosting DOM/WebIDL objects such as NodeList I should note that I'm not advocating a notification-only style for all your proxy needs; having get operations able to generate virtual results makes lots of sense. I primary suggest it for operations that are currently implemented by the system (i.e., user code cannot normally intervene) and that might be relied on for security-relevant behavior. wrapping return results of user operations in a proxy makes perfect sense to me. It's hard to force the two different use cases (wrapping vs virtual objects) into a single API. I don't have a good answer yet on how to resolve the trade-offs. Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Le 02/12/2012 17:27, Tom Van Cutsem a écrit : 2012/11/28 David Bruant bruan...@gmail.com mailto:bruan...@gmail.com I don't understand why a unique token per trap invocation would be necessary. I still don't understand this point. I've gone spelunking. I've found: * the wiki page [1] (reflecting July meeting) which mentions that returning undefined would express I don't know the private name, please forward * Confirmed on July 31st [2]. * Introduction of the idea of putting the verification of known names somewhere else than for each trap return [3]. Some discussion in between about this idea. Introduction of the idea of adding a third argument [4] after which I think stops all discussions about returning something in traps to prove knowledge of a private name or forwarding when not knowing. I don't remember the point about a token per trap invocation and I haven't been able to find it (but I haven't read everything). In any case, for throw ForwardToTarget, I don't see why it would be necessary. It seems it would work unambiguously with meta-handlers, with target-as-a-proxy or with manipulate-any-other-proxy-inside-a-trap (which target-as-a-proxy is an instance of). I think throwing a special token, as is done with StopIteration would probably work in practice (little risk of confusing it with a legitimately returned value). However, it does require every trap invocation to be wrapped in a try-catch block to potentially catch that error. Yes and no. The try-catch is inside the engine, very much like for StorIteration in for-of loops. In case current implementations had performance drawbacks, I feel they could special-case when they know they are calling a function in the context of a particular protocol (iterator or proxy trap for instance). Maybe I'm too influenced by the JVM, but my understanding is that wrapping every call to a trap with a try-catch block won't be free. The more interesting question is whether it would be significantly cheaper than 'returning a value+invariant checks' because that's the reason I suggested the addition of throw ForwardToTarget. Perhaps implementors can comment. Yep. David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Le 02/12/2012 17:43, David Bruant a écrit : Maybe I'm too influenced by the JVM, but my understanding is that wrapping every call to a trap with a try-catch block won't be free. The more interesting question is whether it would be significantly cheaper than 'returning a value+invariant checks' because that's the reason I suggested the addition of throw ForwardToTarget. Filed https://bugzilla.mozilla.org/show_bug.cgi?id=817401 David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
On Sun, Dec 2, 2012 at 8:40 AM, Tom Van Cutsem tomvc...@gmail.com wrote: - after-style wrapping allows you to get notified of an operation after-the-fact. Depending on the API, the after-wrapper may or may not get to see the outcome of the operation, and may or may not change the final outcome passed on to clients. - around-style wrapping is the most general and allows the composer to decide if and when to forward, and what result to return. It subsumes before/after wrapping. This is what direct proxies currently provide. It does not subsume before/after wrapping, because it loses the integrity of before/after (e.g., the wrapper can lie and cheat, where the before and after cannot). That may be worth it, but it is substantially different. You're right, around doesn't subsume before+after in that regard. Thanks for clarifying. I think we can rescue around wrapping. I'll call this approach opaque around wrapping. The idea is that the proxy passes into the handler trap a proxy-generated no-argument function (a thunk) called action. The normal case is that the trap does stuff before calling action, calls action with no arguments and ignoring action's results, does stuff after action, and returns nothing. The call to action performs the original operation on target and remembers the result. After the trap returns, the proxy returns the remembered result of action. Open questions that take us to different design possibilities: 1) what happens if the trap does not call action? 2) what happens if the trap calls action more than once? 3) do actions ever return anything, in case the trap wants to pay attention to it? 4) are there any return values from the trap that the proxy would pay attention to? 5) if the original operation performed by action throws, should the action still just return to the trap normally? 6) what happens if the trap throws rather that returning? Two attractive possibilities: A) A simple pure notification approach: 1) the proxy throws, indicating that the action was not performed 2) only the last successful action counts 3) a boolean, indicating whether the original operation succeeded on target 4) no 5) yes, but with false rather than true, as in #A.3 above. 6) the proxy throws, even though the action was performed. B) Notification with fallback to virtual properties with invariant checking: Like #A, except 1) if action is never called, then the trap return's invariants are checked, as now. 4) only if action was never called. I prefer #A to #B as it gains the full benefits of simplifying the overall spec and implementation. However, #B still seems better than current direct proxies, as the normal case gets target-maintenance and invariant checking for free. -- Cheers, --MarkM ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
2012/12/2 Mark S. Miller erig...@google.com I think we can rescue around wrapping. I'll call this approach opaque around wrapping. The idea is that the proxy passes into the handler trap a proxy-generated no-argument function (a thunk) called action. Interesting. I had thought of a similar approach, but hadn't pursued it because I thought allocating a thunk per trap invocation would be deemed too expensive. I'll go ahead and sketch the design, which I think corresponds closely with your approach B) First, from the point-of-view of the trap implementor, things would work as follows (I show the defineProperty trap only as an example) All traps take an extra thunk as last argument, which I will name forward, since calling that thunk has the effect of forwarding the operation to the target, returning the original result. defineProperty: function(target, name, desc, forward) { // before var result = forward(); // after return result; } Now, from the point-of-view of the proxy implementation, how is the above trap called? // in the proxy, intercepting Object.defineProperty(proxy, name, desc) // where proxy = Proxy(target, handler) var result = Uninitialized; var thunk = function() { if (result === Disabled) throw new Error(can only call forward() during trap invocation); if (result !== Uninitialized) throw new Error(forward() can be called only once); result = Reflect.defineProperty(target, name, desc); // forward to target return result; }; var trapResult = handler[defineProperty].call(handler, target, name, desc, thunk); if (result !== Uninitialized result === trapResult) return result; // no invariant checks when original result is returned if (result === Uninitialized || result !== trapResult) { /* do invariant checks */ ; return result; } result = Disabled; // ensures one cannot call forward() outside of dynamic extent of the invocation I'll answer your questions for the above design: Open questions that take us to different design possibilities: 1) what happens if the trap does not call action? Invariant checks are performed on the returned result. 2) what happens if the trap calls action more than once? An exception is thrown. 3) do actions ever return anything, in case the trap wants to pay attention to it? Yes, it returns the value of the operation, applied to the target. 4) are there any return values from the trap that the proxy would pay attention to? Yes. If the trap returns the original result, it doesn't do any extra checks. Otherwise it does. 5) if the original operation performed by action throws, should the action still just return to the trap normally? A thrown exception would escape and abort the entire operation, unless the trap wraps the call to forward() in a try-catch block. 6) what happens if the trap throws rather that returning? Thrown exceptions are always propagated to clients. I prefer #A to #B as it gains the full benefits of simplifying the overall spec and implementation. However, #B still seems better than current direct proxies, as the normal case gets target-maintenance and invariant checking for free. I agree that #B doesn't really simplify anything in the spec (the invariant checks are still there sometimes). I'm not sure if it is inherently better than the current design though: we gain performance in terms of avoiding invariant checks, but we lose performance by having to allocate a per-call thunk, + the API becomes more complex (an additional parameter to every trap) I do think that passing an action or forward() thunk to a trap as extra argument beats David's proposed throw ForwardToTarget trick in terms of elegance and usability (maybe not in terms of performance). Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
On Dec 2, 2012, at 11:40 AM, Tom Van Cutsem wrote: 2012/12/2 Mark S. Miller erig...@google.com I think we can rescue around wrapping. I'll call this approach opaque around wrapping. The idea is that the proxy passes into the handler trap a proxy-generated no-argument function (a thunk) called action. Interesting. I had thought of a similar approach, but hadn't pursued it because I thought allocating a thunk per trap invocation would be deemed too expensive. I'll go ahead and sketch the design, which I think corresponds closely with your approach B) This is my first reaction too. I want Proxies to be lightweight enough so they really can be used be used for things like self hosting built-ins and web API objects. The need to create an pass in a action thunk on each trap calls initially feels too expensive. But that is only a first reaction. Perhaps the expense really isn't that great. Or perhaps a lot of the cases where I have assumed the Proxies would be needed could be handled by lightweight mechanisms such such as the @elementGet/@elementSet hooks described in http://wiki.ecmascript.org/doku.php?id=strawman:object_model_reformation (see Implementing Built-inArray without Proxy and Rationalizing DOM HTMLCollections examples) Allen First, from the point-of-view of the trap implementor, things would work as follows (I show the defineProperty trap only as an example) All traps take an extra thunk as last argument, which I will name forward, since calling that thunk has the effect of forwarding the operation to the target, returning the original result. defineProperty: function(target, name, desc, forward) { // before var result = forward(); // after return result; } Now, from the point-of-view of the proxy implementation, how is the above trap called? // in the proxy, intercepting Object.defineProperty(proxy, name, desc) // where proxy = Proxy(target, handler) var result = Uninitialized; var thunk = function() { if (result === Disabled) throw new Error(can only call forward() during trap invocation); if (result !== Uninitialized) throw new Error(forward() can be called only once); result = Reflect.defineProperty(target, name, desc); // forward to target return result; }; var trapResult = handler[defineProperty].call(handler, target, name, desc, thunk); if (result !== Uninitialized result === trapResult) return result; // no invariant checks when original result is returned if (result === Uninitialized || result !== trapResult) { /* do invariant checks */ ; return result; } result = Disabled; // ensures one cannot call forward() outside of dynamic extent of the invocation I'll answer your questions for the above design: Open questions that take us to different design possibilities: 1) what happens if the trap does not call action? Invariant checks are performed on the returned result. 2) what happens if the trap calls action more than once? An exception is thrown. 3) do actions ever return anything, in case the trap wants to pay attention to it? Yes, it returns the value of the operation, applied to the target. 4) are there any return values from the trap that the proxy would pay attention to? Yes. If the trap returns the original result, it doesn't do any extra checks. Otherwise it does. 5) if the original operation performed by action throws, should the action still just return to the trap normally? A thrown exception would escape and abort the entire operation, unless the trap wraps the call to forward() in a try-catch block. 6) what happens if the trap throws rather that returning? Thrown exceptions are always propagated to clients. I prefer #A to #B as it gains the full benefits of simplifying the overall spec and implementation. However, #B still seems better than current direct proxies, as the normal case gets target-maintenance and invariant checking for free. I agree that #B doesn't really simplify anything in the spec (the invariant checks are still there sometimes). I'm not sure if it is inherently better than the current design though: we gain performance in terms of avoiding invariant checks, but we lose performance by having to allocate a per-call thunk, + the API becomes more complex (an additional parameter to every trap) I do think that passing an action or forward() thunk to a trap as extra argument beats David's proposed throw ForwardToTarget trick in terms of elegance and usability (maybe not in terms of performance). Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Le 02/12/2012 18:34, Mark S. Miller a écrit : On Sun, Dec 2, 2012 at 8:40 AM, Tom Van Cutsem tomvc...@gmail.com wrote: - after-style wrapping allows you to get notified of an operation after-the-fact. Depending on the API, the after-wrapper may or may not get to see the outcome of the operation, and may or may not change the final outcome passed on to clients. - around-style wrapping is the most general and allows the composer to decide if and when to forward, and what result to return. It subsumes before/after wrapping. This is what direct proxies currently provide. It does not subsume before/after wrapping, because it loses the integrity of before/after (e.g., the wrapper can lie and cheat, where the before and after cannot). That may be worth it, but it is substantially different. You're right, around doesn't subsume before+after in that regard. Thanks for clarifying. I think we can rescue around wrapping. I'll call this approach opaque around wrapping. The idea is that the proxy passes into the handler trap a proxy-generated no-argument function (a thunk) called action. The normal case is that the trap does stuff before calling action, calls action with no arguments and ignoring action's results, does stuff after action, and returns nothing. The point of around is to do something with the result in the middle of the trap, isn't it? For question 3, I'd answer that the action should return the result. The call to action performs the original operation on target and remembers the result. After the trap returns, the proxy returns the remembered result of action. target = {a:1}; var p = new Proxy(target, { get: function(target, name, action){ var v = action(); target[name] = 2; } }) p.a; // ? If p.a is 1 because the call to action remembered the 1, then the target and the proxy look awkwardly desynchronized. To know what's being returned from the trap, one needs to remember when the action is being called which makes writing traps harder I feel. With the current design, looking at the return statements is enough. If p.a is 2, I don't understand the point of action. Or at least, I don't see how it's better than just calling Reflect[trap]. 1) what happens if the trap does not call action? If calling action was made compulsory, then, it would have the same downsides than the notification proxies when it comes like virtual objects including the necessity of a physical backing. David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Le 02/12/2012 20:40, Tom Van Cutsem a écrit : 2012/12/2 Mark S. Miller erig...@google.com mailto:erig...@google.com I think we can rescue around wrapping. I'll call this approach opaque around wrapping. The idea is that the proxy passes into the handler trap a proxy-generated no-argument function (a thunk) called action. Interesting. I had thought of a similar approach, but hadn't pursued it because I thought allocating a thunk per trap invocation would be deemed too expensive. I'll go ahead and sketch the design, which I think corresponds closely with your approach B) I think that for most traps, action could be a deeply frozen wrapped version of Reflect.trap. It would reduce action functions to one per trap which would be much better than one per trap invocation. 4) are there any return values from the trap that the proxy would pay attention to? Yes. If the trap returns the original result, it doesn't do any extra checks. Otherwise it does. Said a bit differently, the extra check is reduced to the equality between the return value of action and trap return value. I prefer #A to #B as it gains the full benefits of simplifying the overall spec and implementation. However, #B still seems better than current direct proxies, as the normal case gets target-maintenance and invariant checking for free. I agree that #B doesn't really simplify anything in the spec (the invariant checks are still there sometimes). I'm not sure if it is inherently better than the current design though: we gain performance in terms of avoiding invariant checks, but we lose performance by having to allocate a per-call thunk, + the API becomes more complex (an additional parameter to every trap) I do think that passing an action or forward() thunk to a trap as extra argument beats David's proposed throw ForwardToTarget trick in terms of elegance and usability (maybe not in terms of performance). I agree it looks less hacky. But I'm not entirely convinced when it comes to usability. As mentioned in the other answer, it makes understanding the trap a bit harder because what comes out of the trap may be less clear: since there is no explicit return statement, one has to figure out, what the value of the action function specifically when it's called for the around case. David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
2012/11/26 Dean Tribble dtrib...@gmail.com On Mon, Nov 26, 2012 at 11:33 AM, Tom Van Cutsem tomvc...@gmail.comwrote: Thanks for spelling out these examples. While they still don't feel like actual important use cases to support, they give a good flavor of the kinds of compromises we'd need to make when turning to notification-only proxies. I agree. My usual expectation for proxies is to support remote and persistent objects. While supporting other scenarios is great, usually that's incidental. Is there a broader list of aspirations for proxies? or is this just a all else being equal it would be good if we can do this? Let's talk about aspirations for proxies. It will help us set priorities. First, some terminology (originating from CLOS, the mother of all MOPs ;-) CLOS method combinations allow a composer to distinguish between before, after and around-style composition: - before-style wrapping gives you only the ability to get notified before an operation happens. You can abort, but not change, the result of the operation. This is what notification-proxies offer. - after-style wrapping allows you to get notified of an operation after-the-fact. Depending on the API, the after-wrapper may or may not get to see the outcome of the operation, and may or may not change the final outcome passed on to clients. - around-style wrapping is the most general and allows the composer to decide if and when to forward, and what result to return. It subsumes before/after wrapping. This is what direct proxies currently provide. As far as I can tell, virtual object abstractions like remote/persistent objects require around-style wrapping, because there's otherwise no meaningful target to automatically forward to. Here's a list of use cases that I frequently have in mind when thinking about proxies, categorized according to whether the use case requires before/after/around wrapping: Virtual objects, hence around-style: - self-hosting exotic objects such as Date, Array (i.e. self-host an ES5/ES6 environment) - self-hosting DOM/WebIDL objects such as NodeList Around-style wrapping (need to be able to change the result of an operation): - membranes - higher-order contracts Before-style wrapping: - revocable references What else? Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Object.observe would fit under after correct? On Wed, Nov 28, 2012 at 4:09 PM, Tom Van Cutsem tomvc...@gmail.com wrote: 2012/11/26 Dean Tribble dtrib...@gmail.com On Mon, Nov 26, 2012 at 11:33 AM, Tom Van Cutsem tomvc...@gmail.comwrote: Thanks for spelling out these examples. While they still don't feel like actual important use cases to support, they give a good flavor of the kinds of compromises we'd need to make when turning to notification-only proxies. I agree. My usual expectation for proxies is to support remote and persistent objects. While supporting other scenarios is great, usually that's incidental. Is there a broader list of aspirations for proxies? or is this just a all else being equal it would be good if we can do this? Let's talk about aspirations for proxies. It will help us set priorities. First, some terminology (originating from CLOS, the mother of all MOPs ;-) CLOS method combinations allow a composer to distinguish between before, after and around-style composition: - before-style wrapping gives you only the ability to get notified before an operation happens. You can abort, but not change, the result of the operation. This is what notification-proxies offer. - after-style wrapping allows you to get notified of an operation after-the-fact. Depending on the API, the after-wrapper may or may not get to see the outcome of the operation, and may or may not change the final outcome passed on to clients. - around-style wrapping is the most general and allows the composer to decide if and when to forward, and what result to return. It subsumes before/after wrapping. This is what direct proxies currently provide. As far as I can tell, virtual object abstractions like remote/persistent objects require around-style wrapping, because there's otherwise no meaningful target to automatically forward to. Here's a list of use cases that I frequently have in mind when thinking about proxies, categorized according to whether the use case requires before/after/around wrapping: Virtual objects, hence around-style: - self-hosting exotic objects such as Date, Array (i.e. self-host an ES5/ES6 environment) - self-hosting DOM/WebIDL objects such as NodeList Around-style wrapping (need to be able to change the result of an operation): - membranes - higher-order contracts Before-style wrapping: - revocable references What else? Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Le 26/11/2012 21:39, David Bruant a écrit : Le 26/11/2012 20:59, Tom Van Cutsem a écrit : 2012/11/26 David Bruant bruan...@gmail.com mailto:bruan...@gmail.com We could define a symbolic value (like StopIteration for iterators) that would mean forward to target. By essence of what forwarding to the target means, there would be no need to perform the least invariant check. We can call it ForwardToTarget :-) I think we've previously entertained a similar proposal when a handler was encountering the .public property of a private property it didn't know, and then wanted to signal to the proxy I don't know this private name, please forward. True. I had the feeling the idea wasn't entirely knew, but I couldn't recall what was the inspiration for it. I recall one issue was that you'd really want a unique token per trap invocation, which costs. I don't understand why a unique token per trap invocation would be necessary. I still don't understand this point. I've gone spelunking. I've found: * the wiki page [1] (reflecting July meeting) which mentions that returning undefined would express I don't know the private name, please forward * Confirmed on July 31st [2]. * Introduction of the idea of putting the verification of known names somewhere else than for each trap return [3]. Some discussion in between about this idea. Introduction of the idea of adding a third argument [4] after which I think stops all discussions about returning something in traps to prove knowledge of a private name or forwarding when not knowing. I don't remember the point about a token per trap invocation and I haven't been able to find it (but I haven't read everything). In any case, for throw ForwardToTarget, I don't see why it would be necessary. It seems it would work unambiguously with meta-handlers, with target-as-a-proxy or with manipulate-any-other-proxy-inside-a-trap (which target-as-a-proxy is an instance of). David [1] http://wiki.ecmascript.org/doku.php?id=harmony:direct_proxies#discussed_during_tc39_july_2012_meeting_microsoft_redmond [2] https://mail.mozilla.org/pipermail/es-discuss/2012-July/024246.html [3] https://mail.mozilla.org/pipermail/es-discuss/2012-July/024256.html [4] https://mail.mozilla.org/pipermail/es-discuss/2012-August/024313.html ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
On Wed, Nov 28, 2012 at 1:09 PM, Tom Van Cutsem tomvc...@gmail.com wrote: 2012/11/26 Dean Tribble dtrib...@gmail.com ... I agree. My usual expectation for proxies is to support remote and persistent objects. While supporting other scenarios is great, usually that's incidental. Is there a broader list of aspirations for proxies? or is this just a all else being equal it would be good if we can do this? Let's talk about aspirations for proxies. It will help us set priorities. First, some terminology (originating from CLOS, the mother of all MOPs ;-) As a general point, I encourage you to look for other inspiration than CLOS MOP for doign proxies (whose mother was really InterlispD). Meta-level access deeply impacts security,maintainability, reliability, understandability, etc. The tighter and more structured you can make your meta-level access, the easier it will be to to implement, use, and maintain (e.g., both coroutines and downward functions are more understandable, easier to implement, easier to secure, etc. than general continuations and call-cc). CLOS method combinations allow a composer to distinguish between before, after and around-style composition: - before-style wrapping gives you only the ability to get notified before an operation happens. You can abort, but not change, the result of the operation. This is what notification-proxies offer. You *can* change the result of the operation. You do so by modifying the state before the operation proceeds, of course. You could also extend the notification support to notify after so you could clenup (avoiding a callback hack). - after-style wrapping allows you to get notified of an operation after-the-fact. Depending on the API, the after-wrapper may or may not get to see the outcome of the operation, and may or may not change the final outcome passed on to clients. - around-style wrapping is the most general and allows the composer to decide if and when to forward, and what result to return. It subsumes before/after wrapping. This is what direct proxies currently provide. It does not subsume before/after wrapping, because it loses the integrity of before/after (e.g., the wrapper can lie and cheat, where the before and after cannot). That may be worth it, but it is substantially different. Another variant is the differential version: the differential trap is like a notification, but it can also return virtual additions (or an iterator of additions). The proxy then invokes the primitive on the target, and appends (with de-dupping, etc.) the virtual additions. This allows the simple case to just use hte target, but also allows all of Allen's additional cases. As far as I can tell, virtual object abstractions like remote/persistent objects require around-style wrapping, because there's otherwise no meaningful target to automatically forward to. I thought the target in that case is an internal object to represent or reify the meta-state of the remote or persistent object. I think that still makes sense in both the persistent object and remote object cases. Here's a list of use cases that I frequently have in mind when thinking about proxies, categorized according to whether the use case requires before/after/around wrapping: Virtual objects, hence around-style: - self-hosting exotic objects such as Date, Array (i.e. self-host an ES5/ES6 environment) - self-hosting DOM/WebIDL objects such as NodeList I should note that I'm not advocating a notification-only style for all your proxy needs; having get operations able to generate virtual results makes lots of sense. I primary suggest it for operations that are currently implemented by the system (i.e., user code cannot normally intervene) and that might be relied on for security-relevant behavior. wrapping return results of user operations in a proxy makes perfect sense to me. Around-style wrapping (need to be able to change the result of an operation): - membranes - higher-order contracts Before-style wrapping: - revocable references You can validate arguments, the state of the destination object (e.g., if you were implementing a state machine), logging What else? There is the pattern derived from the meter pattern in KeyKOS: the handler is only invoked on exception (e.g., like a page fault). For example, a primitive stream gets read operations against. Normally they proceed as a primitive against an implementation-provided buffer so that next is really darned fast. When the buffer is exhausted, instead of throwing an error to the caller, the error is thrown to the handler (called a keeper) which goes through some user-defined effort to refill the buffer, then the read is retried. This allows most data transfer to such a stream to use fast, batch-oriented primitives, while supporting an arbitrary source of contents. ___ es-discuss mailing list es-discuss@mozilla.org
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
On Nov 28, 2012, at 1:09 PM, Tom Van Cutsem wrote: 2012/11/26 Dean Tribble dtrib...@gmail.com On Mon, Nov 26, 2012 at 11:33 AM, Tom Van Cutsem tomvc...@gmail.com wrote: Thanks for spelling out these examples. While they still don't feel like actual important use cases to support, they give a good flavor of the kinds of compromises we'd need to make when turning to notification-only proxies. I agree. My usual expectation for proxies is to support remote and persistent objects. While supporting other scenarios is great, usually that's incidental. Is there a broader list of aspirations for proxies? or is this just a all else being equal it would be good if we can do this? Let's talk about aspirations for proxies. It will help us set priorities. First, some terminology (originating from CLOS, the mother of all MOPs ;-) CLOS method combinations allow a composer to distinguish between before, after and around-style composition: - before-style wrapping gives you only the ability to get notified before an operation happens. You can abort, but not change, the result of the operation. This is what notification-proxies offer. - after-style wrapping allows you to get notified of an operation after-the-fact. Depending on the API, the after-wrapper may or may not get to see the outcome of the operation, and may or may not change the final outcome passed on to clients. - around-style wrapping is the most general and allows the composer to decide if and when to forward, and what result to return. It subsumes before/after wrapping. This is what direct proxies currently provide. As far as I can tell, virtual object abstractions like remote/persistent objects require around-style wrapping, because there's otherwise no meaningful target to automatically forward to. Here's a list of use cases that I frequently have in mind when thinking about proxies, categorized according to whether the use case requires before/after/around wrapping: Virtual objects, hence around-style: - self-hosting exotic objects such as Date, Array (i.e. self-host an ES5/ES6 environment) - self-hosting DOM/WebIDL objects such as NodeList -virtualizing a backing store as properties. -supported extended property attributes Around-style wrapping (need to be able to change the result of an operation): - membranes - higher-order contracts introducing new inheritance schemes, eg, multiple inheritance Before-style wrapping: - revocable references After-style a doesNotUnderstand wrapper -- run the operation, but if the result is undefined check if missing property and if so, call DNU handler What else? Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Le 25/11/2012 15:32, Axel Rauschmayer a écrit : If indeed both kinds of proxy are useful and direct proxies are more powerful, then why not only have a foundational direct proxy API and implement a tool type NotificationProxy that is based on that API. An interesting question I still haven't found a satisfying answer to is: is the additional power of current proxies useful? and worth the cost? Because the current freedom of proxies is the root cause of invariant checks that even good proxy citizens have to pay. David [[[Sent from a mobile device. Please forgive brevity and typos.]]] Dr. Axel Rauschmayer a...@rauschma.de mailto:a...@rauschma.de Home: http://rauschma.de Blog: http://2ality.com On 25.11.2012, at 12:44, Tom Van Cutsem tomvc...@gmail.com mailto:tomvc...@gmail.com wrote: Hi, I will refer to Dean's proposal as notification proxies (where traps essentially become notification callbacks), and will continue to use direct proxies for the current design where the trap can return a result (which is then verified). These notification proxies remind me a lot of how one must implement membranes with direct proxies. The general idea here is that the proxy must use a shadow target as the proxy target, and the handler must refer to the real wrapped target. Traps that have to do with invariants (e.g. freeze/isFrozen, or querying a non-configurable property descriptor) require the proxy to synchronize the state of the real and shadow targets, because the proxy will verify the trap result against the shadow target. For such membranes, let's consider how direct proxies and notification proxies trap an operation: Direct proxies: 1) the proxy calls a trap on the handler 2) if the operation involves strong invariants on the real target, the handler must synchronize real and shadow target 3) the trap returns a result 4) if the proxy detects that the operation involves strong invariants, the trap result is verified against the shadow target Notification proxies: 1) the proxy calls a trap on the handler (as a notification) 2) the handler must synchronize real and shadow target (regardless of whether invariants are involved) 3) the trap returns no result 4) the proxy performs the intercepted operation on the shadow and returns the result I agree that the big benefit of notification proxies is that they get rid of all the complex validation logic. However, some reservations: - if traps become mere notifications, perhaps their names should change to reflect this, e.g. notifyGetOwnPropertyNames instead of getOwnPropertyNames. This is to alert handler writers that the return value of these traps will be ignored. - I think we do lose some expressiveness in the case of pure virtual object abstractions that don't pretend to uphold any invariants. With notification proxies, the handler must always (even in the case of configurable properties) define concrete properties on the target. Any virtual object abstraction is thus forced to maintain a shadow which must eventually be represented as a plain Javascript object. In other words: *all* virtual object abstractions, whether they pretend to be frozen or not, have to make use of the shadow target technique, with the burden of synchronizing the shadow upon each operation. That burden currently doesn't exist for non-frozen virtual object abstractions (I'm using frozen vs non-frozen here as a shorthand for has non-configurable/non-extensible invariants vs has no such invariants). - Regarding the overhead of the getOwnPropertyNames trap having to create a defensive copy of the trap result: With notification proxies, upon trapping notifyGetOwnPropertyNames: 1) the trap must define all properties it wants to return on the target. If there are N properties, there is an O(N) physical storage cost involved. 2) the proxy applies the built-in Object.getOwnPropertyNames to the target. Assuming the target is a normal object, the primitive allocates a fresh array and returns the N properties. In the current design: 1) the trap returns an array of property names (requiring O(N) physical storage cost) 2) the proxy copies and verifies this array I think the storage costs are largely the same. However, with notification proxies, if the properties were virtual, those properties do linger as concrete properties on the target. Yes, the handler can delete them later, but when is later? Should the handler schedule clean-up actions using setTimeout(0)? This somehow does not feel right. I like the simplicity of notification proxies, but we should think carefully what operations we turn into notifications only. More generally, notification proxies are indeed even-more-direct-proxies. They make the wrapping use case (logging, profiling, contract checking, etc.) simpler, at the expense of virtual objects (remote objects, test mock-ups), which are forced to always concretize the virtual object's properties on a real
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Le 25/11/2012 12:44, Tom Van Cutsem a écrit : (...) I agree that the big benefit of notification proxies is that they get rid of all the complex validation logic. However, some reservations: (...) - I think we do lose some expressiveness in the case of pure virtual object abstractions that don't pretend to uphold any invariants. I don't think this is true. As Mark said: The trick is that placing a configurable property on the target doesn't commit the handler to anything, since the handler can remove or change this property freely as of the next trap. This is true for any trap I think. I however agree that this 2-trap consistency may make the exercise of writing traps harder (but it'd take user testing to be 100% sure it's significantly harder). I think there might be a loss in expressiveness if forwarding to the target throws an error. With current proxies, the trap can catch this exception; with notification proxies, the error is thrown after the trap, so it can't. Now, it would require an in-depth analysis, but I intuit that all cases where an internal operation throws have a corresponding invariant checks (which errors aren't caught by current proxies). If that was the case, it would mean that there is actually no loss (or a minor loss consisting in loosing the ability to catch an error and rethrow it yourself) Also, if you can only forward to target, there is no way you can define custom property descriptor attributes which would be a shame in my opinion. With notification proxies, the handler must always (even in the case of configurable properties) define concrete properties on the target. Any virtual object abstraction is thus forced to maintain a shadow which must eventually be represented as a plain Javascript object. I think that any virtual object abstraction which claims to maintain some sort of consistency has to store the necessary bits of this consistency somewhere. The target is as good as anywhere else. I have to note that if the target is forced to be used as a regular object, then things like ProxyMap [1] become pointless (because the map is just used as a regular object, not as a map). (...) However, with notification proxies, if the properties were virtual, those properties do linger as concrete properties on the target. Yes, the handler can delete them later, but when is later? Should the handler schedule clean-up actions using setTimeout(0)? This somehow does not feel right. According to Mark's comment, later is next trap. It's possible to maintain consistency, but writing the traps is a bit less easy. I like the simplicity of notification proxies, but we should think carefully what operations we turn into notifications only. More generally, notification proxies are indeed even-more-direct-proxies. They make the wrapping use case (logging, profiling, contract checking, etc.) simpler, at the expense of virtual objects (remote objects, test mock-ups), which are forced to always concretize the virtual object's properties on a real Javascript object. I have what I think to be a interesting middleground allowing to bypass invariant checks while keeping direct proxies as they are. My experience with writing traps with direct proxies is that traps (for non-virtual cases) almost all and always look like [2]: trap: function(...args){ // do something return Reflect.trap(...args); // or equivalent built-in/syntax } Let's take the Object.getOwnPropertyDescriptor trap as example. With current proxies, the trap would often end with Reflect.getOwnPropertyDescriptor(target, name) (or Object., whatev's), after the call on the target is finished, then, the engine checks the result against the target... but that's a bit dumb. We know we wanted to get the target result and we made the most straightforward thing to do that guarantees invariants, but the engine still has to check, because it can't know the result comes from calling the Reflect operation on the target. We could define a symbolic value (like StopIteration for iterators) that would mean forward to target. By essence of what forwarding to the target means, there would be no need to perform the least invariant check. We can call it ForwardToTarget :-) Then traps involved in proxies preserving some consistency would look like: trap: function(...args){ // do something return ForwardToTarget; } The rule would become: if you want to do something simple, ForwardToTarget at the end of your trap and the invariant check cost is gone. If you want to use direct proxies freedom (virtual objects), you have to pay the cost of invariant checks to make sure you don't go too wild with your freedom Best of both worlds for the cost of a symbol. As a followup to my above comment about custom property descriptor attributes, it would be nice to be able to return something like ForwardToTargetAndCombineWith({custom1: val1,
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
On Mon, Nov 26, 2012 at 3:36 AM, David Bruant bruan...@gmail.com wrote: Le 25/11/2012 15:32, Axel Rauschmayer a écrit : If indeed both kinds of proxy are useful and direct proxies are more powerful, then why not only have a foundational direct proxy API and implement a tool type NotificationProxy that is based on that API. An interesting question I still haven't found a satisfying answer to is: is the additional power of current proxies useful? and worth the cost? Because the current freedom of proxies is the root cause of invariant checks that even good proxy citizens have to pay. At a minimum, the direct proxies allow remote objects and other entirely virtual objects to be implemented with no allocation overhead. In contrast, notification proxies require allocation proportional to the size of the remote object being proxied. In Racket, whose chaperone system has some of the flavor of notification proxies, this makes remote objects more costly and difficult to implement. This is less of a problem in Racket, since the primary use case for chaperones is the implementation of contracts, and virtual objects would usually be handled differently. -- sam th sa...@ccs.neu.edu ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
On Nov 26, 2012, at 12:36 AM, David Bruant wrote: Le 25/11/2012 15:32, Axel Rauschmayer a écrit : If indeed both kinds of proxy are useful and direct proxies are more powerful, then why not only have a foundational direct proxy API and implement a tool type NotificationProxy that is based on that API. An interesting question I still haven't found a satisfying answer to is: is the additional power of current proxies useful? and worth the cost? Because the current freedom of proxies is the root cause of invariant checks that even good proxy citizens have to pay. One of the motivating use cases for Proxies is self-hosting exotic built-ins and host objects such as current Web API objects. If the standard built-in Proxy abstraction isn't expressive enough for that job (and also efficient enough) then we haven't achieve the goal of supporting that use case. If that happens what I suspect will happen is that some implementations will provide a non-standard, less restrictive, more expressive alternative to the standard Proxy. That seems quite doable because it would just be another alternative exotic object MOP binding for the engine to support. I'd prefer that we provide a standard, interoperable abstraction rather than seeing non-interooperable solutions to this use case. Allen David [[[Sent from a mobile device. Please forgive brevity and typos.]]] Dr. Axel Rauschmayer a...@rauschma.de Home: http://rauschma.de Blog: http://2ality.com On 25.11.2012, at 12:44, Tom Van Cutsem tomvc...@gmail.com wrote: Hi, I will refer to Dean's proposal as notification proxies (where traps essentially become notification callbacks), and will continue to use direct proxies for the current design where the trap can return a result (which is then verified). These notification proxies remind me a lot of how one must implement membranes with direct proxies. The general idea here is that the proxy must use a shadow target as the proxy target, and the handler must refer to the real wrapped target. Traps that have to do with invariants (e.g. freeze/isFrozen, or querying a non-configurable property descriptor) require the proxy to synchronize the state of the real and shadow targets, because the proxy will verify the trap result against the shadow target. For such membranes, let's consider how direct proxies and notification proxies trap an operation: Direct proxies: 1) the proxy calls a trap on the handler 2) if the operation involves strong invariants on the real target, the handler must synchronize real and shadow target 3) the trap returns a result 4) if the proxy detects that the operation involves strong invariants, the trap result is verified against the shadow target Notification proxies: 1) the proxy calls a trap on the handler (as a notification) 2) the handler must synchronize real and shadow target (regardless of whether invariants are involved) 3) the trap returns no result 4) the proxy performs the intercepted operation on the shadow and returns the result I agree that the big benefit of notification proxies is that they get rid of all the complex validation logic. However, some reservations: - if traps become mere notifications, perhaps their names should change to reflect this, e.g. notifyGetOwnPropertyNames instead of getOwnPropertyNames. This is to alert handler writers that the return value of these traps will be ignored. - I think we do lose some expressiveness in the case of pure virtual object abstractions that don't pretend to uphold any invariants. With notification proxies, the handler must always (even in the case of configurable properties) define concrete properties on the target. Any virtual object abstraction is thus forced to maintain a shadow which must eventually be represented as a plain Javascript object. In other words: *all* virtual object abstractions, whether they pretend to be frozen or not, have to make use of the shadow target technique, with the burden of synchronizing the shadow upon each operation. That burden currently doesn't exist for non-frozen virtual object abstractions (I'm using frozen vs non-frozen here as a shorthand for has non-configurable/non-extensible invariants vs has no such invariants). - Regarding the overhead of the getOwnPropertyNames trap having to create a defensive copy of the trap result: With notification proxies, upon trapping notifyGetOwnPropertyNames: 1) the trap must define all properties it wants to return on the target. If there are N properties, there is an O(N) physical storage cost involved. 2) the proxy applies the built-in Object.getOwnPropertyNames to the target. Assuming the target is a normal object, the primitive allocates a fresh array and returns the N properties. In the current design: 1) the trap returns an array of property names (requiring O(N) physical storage cost) 2) the proxy
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Le 26/11/2012 19:58, Allen Wirfs-Brock a écrit : On Nov 26, 2012, at 12:36 AM, David Bruant wrote: Le 25/11/2012 15:32, Axel Rauschmayer a écrit : If indeed both kinds of proxy are useful and direct proxies are more powerful, then why not only have a foundational direct proxy API and implement a tool type NotificationProxy that is based on that API. An interesting question I still haven't found a satisfying answer to is: is the additional power of current proxies useful? and worth the cost? Because the current freedom of proxies is the root cause of invariant checks that even good proxy citizens have to pay. One of the motivating use cases for Proxies is self-hosting exotic built-ins and host objects such as current Web API objects. If the standard built-in Proxy abstraction isn't expressive enough for that job (and also efficient enough) then we haven't achieve the goal of supporting that use case. I agree, but I haven't read evidence on the thread that notification proxies would have insufficient power to do so. In my other answer, I pointed to a couple of expressiveness (minor) losses, but nothing that would prevent self-hosting Web APIs. Do you have particular examples in mind? David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
2012/11/25 Allen Wirfs-Brock al...@wirfs-brock.com I have a couple virtual object use cases in mind where I don't think I would want to make all properties concrete on the target. Thanks for spelling out these examples. While they still don't feel like actual important use cases to support, they give a good flavor of the kinds of compromises we'd need to make when turning to notification-only proxies. 1) A bit vector abstraction where individual bits are accessible as numerically indexed properties. Assume I have a bit string of fairly large size (as little as 128 bits) and I would like to abstract it as an array of single bit numbers where the indexes correspond to bit positions in the bit string. Using Proxies I want be able to use Get and Put traps to direct such indexed access to a binary data backing store I maintain. I believe that having to reify on the target each bit that is actually accessed would be too expensive in both time and space to justify using this approach. Yes. As another example, consider a self-hosted sparse Array implementation. The paradox here is that it's precisely those abstractions that seek to store/retrieve properties in a more compact/efficient way than allowed by the standard JS object model would turn to proxies, yet having to reify each accessed property precisely voids the more compact/efficient storage of properties. BTW, this is a scenario where I might not even brother trying to make sure that Object.getOwnPropertyNames listed all of the bit indexes. I could, include them in an array of own property names, but would anybody really care if I didn't? Well, yes and no. Yes, in the sense that your object abstraction will break when used with some tools and libraries. For instance, consider a debugger that uses [[GetOwnPropertyNames]] to populate its inspector view, or a library that contains generic algorithms that operate on arbitrary objects (say copying an object, or serializing it, by using Object.getOwnPropertyNames). No, in the sense that even if you would implement getOwnPropertyNames consistently, copying or serializing your bit vector abstraction would not lead to the desired result anyway (the copy or deserialized version would be a normal object without the optimized bit representation) (although the result might still be usable!) More generally, notification proxies are indeed even-more-direct-proxies. They make the wrapping use case (logging, profiling, contract checking, etc.) simpler, at the expense of virtual objects (remote objects, test mock-ups), which are forced to always concretize the virtual object's properties on a real Javascript object. Yes, I also like the simplicity of notification proxies but don't want to give up the power of virtual objects. Maybe having both would be a reasonable alternative. Brandon beat me to it, but indeed, having two kinds of proxies for the two different use cases makes sense. Except that there's a complexity budget we need to take into account. If we can avoid the cost of two APIs, we should. Brandon's proposal tries to reduce the API bloat by keeping the exact same API for both direct proxies and notification proxies, and changing the rules dynamically based on the presence/absence of invariants. One issue I have with that is that it will make it very hard for people writing proxies to understand when the trap return value is ignored, and when it is not. Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
There's a few goals I have in mind when thinking about proxies: * Ability to forward to target at minimal cost. * Minimal cost for invariant enforcement, preferably automatic since the result is predetermined * Near zero cost for invariant enforcement in the majority of cases where it's not needed * Ability to virtualize properties with minimal forced overhead (if someone eagerly deep clones a membrane then that's their cost, not the API's) When something becomes invariant, then the runtime essentially takes ownership of it and it no longer belongs to the proxy. The proxy's only remaining power is being the first to know whenever something is about to happen. As the proxy creator, I don't really want to spend resources being responsible for something I no longer have any control over. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
I started to respond to Allan's message, but I'll combine them to here. Note the additional proposal in the middle of the message. On Mon, Nov 26, 2012 at 11:33 AM, Tom Van Cutsem tomvc...@gmail.com wrote: 2012/11/25 Allen Wirfs-Brock al...@wirfs-brock.com I have a couple virtual object use cases in mind where I don't think I would want to make all properties concrete on the target. Thanks for spelling out these examples. While they still don't feel like actual important use cases to support, they give a good flavor of the kinds of compromises we'd need to make when turning to notification-only proxies. I agree. My usual expectation for proxies is to support remote and persistent objects. While supporting other scenarios is great, usually that's incidental. Is there a broader list of aspirations for proxies? or is this just a all else being equal it would be good if we can do this? 1) A bit vector abstraction where individual bits are accessible as numerically indexed properties. Assume I have a bit string of fairly large size (as little as 128 bits) and I would like to abstract it as an array of single bit numbers where the indexes correspond to bit positions in the bit string. Using Proxies I want be able to use Get and Put traps to direct such indexed access to a binary data backing store I maintain. I believe that having to reify on the target each bit that is actually accessed would be too expensive in both time and space to justify using this approach. Yes. As another example, consider a self-hosted sparse Array implementation. The paradox here is that it's precisely those abstractions that seek to store/retrieve properties in a more compact/efficient way than allowed by the standard JS object model would turn to proxies, yet having to reify each accessed property precisely voids the more compact/efficient storage of properties. I don't have a good sense of how often and for what purpose clients call getOwnPropertyNames and the like. That frankly seems like a terrible operation for any client to be calling; it's architecturally necessarily inefficient; especially since it currently demands a fresh array. Worst case, I'd like to see it have a frozen result or be deprecated in favor of an operation that is more architecturally efficient (e.g., return an iterator of names so they need never all be reified). If the operation is typically only called for debugging and inspection, or once per type or some such, then the performance questions are less important. If libraries constantly call it for web services, then having an improved API might be a big win. BTW, this is a scenario where I might not even brother trying to make sure that Object.getOwnPropertyNames listed all of the bit indexes. I could, include them in an array of own property names, but would anybody really care if I didn't? So for this example, you might want to suppress the integer properties from getOwnPropertyNames *regardless *of the proxy approach. Otherwise you are indeed doing O(N) work for all your otherwise efficiently-implemented bit fields. Such a hack would work poorly with meta-driven tools (e.g., something that maps fields to a display table for object inspection), but that's not because of the proxy support. (It is conceivable to me that integer-indexed fields deserve explicit support in a meta-protocol anyway, since their usage patterns are typically so different from that of named fields.) Well, yes and no. Yes, in the sense that your object abstraction will break when used with some tools and libraries. For instance, consider a debugger that uses [[GetOwnPropertyNames]] to populate its inspector view, or a library that contains generic algorithms that operate on arbitrary objects (say copying an object, or serializing it, by using Object.getOwnPropertyNames). No, in the sense that even if you would implement getOwnPropertyNames consistently, copying or serializing your bit vector abstraction would not lead to the desired result anyway (the copy or deserialized version would be a normal object without the optimized bit representation) (although the result might still be usable!) More generally, notification proxies are indeed even-more-direct-proxies. They make the wrapping use case (logging, profiling, contract checking, etc.) simpler, at the expense of virtual objects (remote objects, test mock-ups), which are forced to always concretize the virtual object's properties on a real Javascript object. Yes, I also like the simplicity of notification proxies but don't want to give up the power of virtual objects. Maybe having both would be a reasonable alternative. Brandon beat me to it, but indeed, having two kinds of proxies for the two different use cases makes sense. Except that there's a complexity budget we need to take into account. If we can avoid the cost of two APIs, we should. I too would like to avoid two kinds of proxies. And
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Le 26/11/2012 20:59, Tom Van Cutsem a écrit : 2012/11/26 David Bruant bruan...@gmail.com mailto:bruan...@gmail.com We could define a symbolic value (like StopIteration for iterators) that would mean forward to target. By essence of what forwarding to the target means, there would be no need to perform the least invariant check. We can call it ForwardToTarget :-) I think we've previously entertained a similar proposal when a handler was encountering the .public property of a private property it didn't know, and then wanted to signal to the proxy I don't know this private name, please forward. True. I had the feeling the idea wasn't entirely knew, but I couldn't recall what was the inspiration for it. I recall one issue was that you'd really want a unique token per trap invocation, which costs. I don't understand why a unique token per trap invocation would be necessary. By the way, very much like for iterators, it would have to be throw ForwardToTarget instead of return ForwardToTarget because the symbol could be a value people would want to return while it would be a bad practice to expect a specific value to be thrown in a normal execution. David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
Hi, I will refer to Dean's proposal as notification proxies (where traps essentially become notification callbacks), and will continue to use direct proxies for the current design where the trap can return a result (which is then verified). These notification proxies remind me a lot of how one must implement membranes with direct proxies. The general idea here is that the proxy must use a shadow target as the proxy target, and the handler must refer to the real wrapped target. Traps that have to do with invariants (e.g. freeze/isFrozen, or querying a non-configurable property descriptor) require the proxy to synchronize the state of the real and shadow targets, because the proxy will verify the trap result against the shadow target. For such membranes, let's consider how direct proxies and notification proxies trap an operation: Direct proxies: 1) the proxy calls a trap on the handler 2) if the operation involves strong invariants on the real target, the handler must synchronize real and shadow target 3) the trap returns a result 4) if the proxy detects that the operation involves strong invariants, the trap result is verified against the shadow target Notification proxies: 1) the proxy calls a trap on the handler (as a notification) 2) the handler must synchronize real and shadow target (regardless of whether invariants are involved) 3) the trap returns no result 4) the proxy performs the intercepted operation on the shadow and returns the result I agree that the big benefit of notification proxies is that they get rid of all the complex validation logic. However, some reservations: - if traps become mere notifications, perhaps their names should change to reflect this, e.g. notifyGetOwnPropertyNames instead of getOwnPropertyNames. This is to alert handler writers that the return value of these traps will be ignored. - I think we do lose some expressiveness in the case of pure virtual object abstractions that don't pretend to uphold any invariants. With notification proxies, the handler must always (even in the case of configurable properties) define concrete properties on the target. Any virtual object abstraction is thus forced to maintain a shadow which must eventually be represented as a plain Javascript object. In other words: *all* virtual object abstractions, whether they pretend to be frozen or not, have to make use of the shadow target technique, with the burden of synchronizing the shadow upon each operation. That burden currently doesn't exist for non-frozen virtual object abstractions (I'm using frozen vs non-frozen here as a shorthand for has non-configurable/non-extensible invariants vs has no such invariants). - Regarding the overhead of the getOwnPropertyNames trap having to create a defensive copy of the trap result: With notification proxies, upon trapping notifyGetOwnPropertyNames: 1) the trap must define all properties it wants to return on the target. If there are N properties, there is an O(N) physical storage cost involved. 2) the proxy applies the built-in Object.getOwnPropertyNames to the target. Assuming the target is a normal object, the primitive allocates a fresh array and returns the N properties. In the current design: 1) the trap returns an array of property names (requiring O(N) physical storage cost) 2) the proxy copies and verifies this array I think the storage costs are largely the same. However, with notification proxies, if the properties were virtual, those properties do linger as concrete properties on the target. Yes, the handler can delete them later, but when is later? Should the handler schedule clean-up actions using setTimeout(0)? This somehow does not feel right. I like the simplicity of notification proxies, but we should think carefully what operations we turn into notifications only. More generally, notification proxies are indeed even-more-direct-proxies. They make the wrapping use case (logging, profiling, contract checking, etc.) simpler, at the expense of virtual objects (remote objects, test mock-ups), which are forced to always concretize the virtual object's properties on a real Javascript object. Cheers, Tom 2012/11/25 Mark S. Miller erig...@google.com +1. I think this is a really effective extension of the direct proxy approach, and I don't know why we didn't see it earlier. It's weird that this preserves all the flexibility of fully virtual configurable properties even though it insists that even these be made into real properties on the target. The trick is that placing a configurable property on the target doesn't commit the handler to anything, since the handler can remove or change this property freely as of the next trap. Apologies again for not yet having the time to do more than skim the thread at this point. But IIRC someone already suggested a similar change to some of the imperative traps -- perhaps freeze, seal, and preventExtensions. The handler would not perform these operations on the target, only to
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
I think it could be argued that the two categories of proxy uses have different enough fundamentals that they would be better suited broken into two separate types of proxies. They use a common protocol for dispatching notifications/requests, but the result is incompatible. For a Notification Proxy the trap is notified and then action proceeds against the ProxyTarget as it would normally. This is the simple case since it merely adds a pre-notification to the front of all trapped operations and nothing else changes. For a Virtual Proxy the invariant-sensitive attributes of the virtual target act like those of a Notification Proxy. Any time something becomes invariant, whether it be a property or the whole object, the value is reified from being virtual to being an actual property. From that point on the property or the whole Proxy changes behavior from Virtual to Notification. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
If indeed both kinds of proxy are useful and direct proxies are more powerful, then why not only have a foundational direct proxy API and implement a tool type NotificationProxy that is based on that API. [[[Sent from a mobile device. Please forgive brevity and typos.]]] Dr. Axel Rauschmayer a...@rauschma.de Home: http://rauschma.de Blog: http://2ality.com On 25.11.2012, at 12:44, Tom Van Cutsem tomvc...@gmail.com wrote: Hi, I will refer to Dean's proposal as notification proxies (where traps essentially become notification callbacks), and will continue to use direct proxies for the current design where the trap can return a result (which is then verified). These notification proxies remind me a lot of how one must implement membranes with direct proxies. The general idea here is that the proxy must use a shadow target as the proxy target, and the handler must refer to the real wrapped target. Traps that have to do with invariants (e.g. freeze/isFrozen, or querying a non-configurable property descriptor) require the proxy to synchronize the state of the real and shadow targets, because the proxy will verify the trap result against the shadow target. For such membranes, let's consider how direct proxies and notification proxies trap an operation: Direct proxies: 1) the proxy calls a trap on the handler 2) if the operation involves strong invariants on the real target, the handler must synchronize real and shadow target 3) the trap returns a result 4) if the proxy detects that the operation involves strong invariants, the trap result is verified against the shadow target Notification proxies: 1) the proxy calls a trap on the handler (as a notification) 2) the handler must synchronize real and shadow target (regardless of whether invariants are involved) 3) the trap returns no result 4) the proxy performs the intercepted operation on the shadow and returns the result I agree that the big benefit of notification proxies is that they get rid of all the complex validation logic. However, some reservations: - if traps become mere notifications, perhaps their names should change to reflect this, e.g. notifyGetOwnPropertyNames instead of getOwnPropertyNames. This is to alert handler writers that the return value of these traps will be ignored. - I think we do lose some expressiveness in the case of pure virtual object abstractions that don't pretend to uphold any invariants. With notification proxies, the handler must always (even in the case of configurable properties) define concrete properties on the target. Any virtual object abstraction is thus forced to maintain a shadow which must eventually be represented as a plain Javascript object. In other words: *all* virtual object abstractions, whether they pretend to be frozen or not, have to make use of the shadow target technique, with the burden of synchronizing the shadow upon each operation. That burden currently doesn't exist for non-frozen virtual object abstractions (I'm using frozen vs non-frozen here as a shorthand for has non-configurable/non-extensible invariants vs has no such invariants). - Regarding the overhead of the getOwnPropertyNames trap having to create a defensive copy of the trap result: With notification proxies, upon trapping notifyGetOwnPropertyNames: 1) the trap must define all properties it wants to return on the target. If there are N properties, there is an O(N) physical storage cost involved. 2) the proxy applies the built-in Object.getOwnPropertyNames to the target. Assuming the target is a normal object, the primitive allocates a fresh array and returns the N properties. In the current design: 1) the trap returns an array of property names (requiring O(N) physical storage cost) 2) the proxy copies and verifies this array I think the storage costs are largely the same. However, with notification proxies, if the properties were virtual, those properties do linger as concrete properties on the target. Yes, the handler can delete them later, but when is later? Should the handler schedule clean-up actions using setTimeout(0)? This somehow does not feel right. I like the simplicity of notification proxies, but we should think carefully what operations we turn into notifications only. More generally, notification proxies are indeed even-more-direct-proxies. They make the wrapping use case (logging, profiling, contract checking, etc.) simpler, at the expense of virtual objects (remote objects, test mock-ups), which are forced to always concretize the virtual object's properties on a real Javascript object. Cheers, Tom 2012/11/25 Mark S. Miller erig...@google.com +1. I think this is a really effective extension of the direct proxy approach, and I don't know why we didn't see it earlier. It's weird that this preserves all the flexibility of fully virtual configurable properties even
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
On Nov 25, 2012, at 4:15 AM, Brandon Benvie wrote: I think it could be argued that the two categories of proxy uses have different enough fundamentals that they would be better suited broken into two separate types of proxies. They use a common protocol for dispatching notifications/requests, but the result is incompatible. For a Notification Proxy the trap is notified and then action proceeds against the ProxyTarget as it would normally. This is the simple case since it merely adds a pre-notification to the front of all trapped operations and nothing else changes. For a Virtual Proxy the invariant-sensitive attributes of the virtual target act like those of a Notification Proxy. Any time something becomes invariant, whether it be a property or the whole object, the value is reified from being virtual to being an actual property. From that point on the property or the whole Proxy changes behavior from Virtual to Notification. And using the new specification MOP this would be quite easy to specify. I even suspect that it would be fairly easy to implement, assuming that engines actually dynamically dispatch internal method calls at approximately the granularity of those specified by the MOP. I actually had the thought as I was working through the MOP and the Proxy internal methods, that I'd wager that somebody is going to come up with other proxy like abstractions that are optimized for different scenarios and that they'd eventually get implemented. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
On Nov 25, 2012, at 3:44 AM, Tom Van Cutsem wrote: Hi, I will refer to Dean's proposal as notification proxies (where traps essentially become notification callbacks), and will continue to use direct proxies for the current design where the trap can return a result (which is then verified). I agree with Mark that Dean has a quite clever proposal... ... However, some reservations: me too...although I think notification proxies are optimized for a real use case and might be worth supporting. - if traps become mere notifications, perhaps their names should change to reflect this, e.g. notifyGetOwnPropertyNames instead of getOwnPropertyNames. This is to alert handler writers that the return value of these traps will be ignored. - I think we do lose some expressiveness in the case of pure virtual object abstractions that don't pretend to uphold any invariants. With notification proxies, the handler must always (even in the case of configurable properties) define concrete properties on the target. Any virtual object abstraction is thus forced to maintain a shadow which must eventually be represented as a plain Javascript object. In other words: *all* virtual object abstractions, whether they pretend to be frozen or not, have to make use of the shadow target technique, with the burden of synchronizing the shadow upon each operation. That burden currently doesn't exist for non-frozen virtual object abstractions (I'm using frozen vs non-frozen here as a shorthand for has non-configurable/non-extensible invariants vs has no such invariants). I have a couple virtual object use cases in mind where I don't think I would want to make all properties concrete on the target. 1) A bit vector abstraction where individual bits are accessible as numerically indexed properties. Assume I have a bit string of fairly large size (as little as 128 bits) and I would like to abstract it as an array of single bit numbers where the indexes correspond to bit positions in the bit string. Using Proxies I want be able to use Get and Put traps to direct such indexed access to a binary data backing store I maintain. I believe that having to reify on the target each bit that is actually accessed would be too expensive in both time and space to justify using this approach. BTW, this is a scenario where I might not even brother trying to make sure that Object.getOwnPropertyNames listed all of the bit indexes. I could, include them in an array of own property names, but would anybody really care if I didn't? 2) Multiple Inheritance I'm playing with what it takes to support self-like multiple inheritance using proxies. One approach that looks promising is to use a Proxy-based object as the immediate [[Prototype]] of leaf objects that have multiple logical inheritance parents. That lets put/gets of own property operate at native speed and the Put/Get handlers only get invoked for inherited properties. The MI parent proxy keeps track (using its own private state) of the multiple parents and doesn't really use it own [[Prototype]] ( actually it's target object's [[Prototype]]) as a lookup path for proto climbing. It encapsulates this entire mechanism such that from the perspective of the leaf object, all of its inherited properties look like own properties of the MI parent proxy. I would hate to have to copy down every accessed inherited property. There are situations were I might want to copy down some of them, but probably not all. ... I think the storage costs are largely the same. However, with notification proxies, if the properties were virtual, those properties do linger as concrete properties on the target. Yes, the handler can delete them later, but when is later? Should the handler schedule clean-up actions using setTimeout(0)? This somehow does not feel right. Yes indeed. Storage cost may be comparable for getOwnProperyNames that that is presumably a rare operation. If all traps had that characteristic I think many virtual object use cases would be too expensive to make the practical. I like the simplicity of notification proxies, but we should think carefully what operations we turn into notifications only. More generally, notification proxies are indeed even-more-direct-proxies. They make the wrapping use case (logging, profiling, contract checking, etc.) simpler, at the expense of virtual objects (remote objects, test mock-ups), which are forced to always concretize the virtual object's properties on a real Javascript object. Yes, I also like the simplicity of notification proxies but don't want to give up the power of virtual objects. Maybe having both would be a reasonable alternative. Allen Cheers, Tom 2012/11/25 Mark S. Miller erig...@google.com +1. I think this is a really effective extension of the direct proxy approach, and I don't know why we didn't see it earlier. It's weird
Re: Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)
On Nov 25, 2012, at 6:32 AM, Axel Rauschmayer wrote: If indeed both kinds of proxy are useful and direct proxies are more powerful, then why not only have a foundational direct proxy API and implement a tool type NotificationProxy that is based on that API. That might work. In that case you would probably do no invariant enforcement in the direct proxy internal methods and the notification proxy APIs would supply an intermediate layer of traps that called notification traps and then delegated to the target to get the actual results... Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
I am looking forward to proxies in JavaScript, and had a thought on the issues below. You could extend the the direct proxy approach for this. When the Proxy receives getOwnPropertyNames, it 1) notifies the handler that property names are being requested 2) the handler adds/removes any properties (configurable or otherwise subject to the normal constraints) on the target 3) upon return, the proxy invokes getOwnPropertyNames directly on the target (e..g, invoking the *normal *system primitive) This approach appears to have consistent behavior for configurability and extensibility. For example, the trap operation above could add configurable properties to an extensible target, and remove them later. It could add non-configurable properties, but they are permanent once added, etc. Thus there's no loss of generality. In addition to optionally setting up properties on the target, the handler trap above would need to indicate to the proxy (via exception or boolean result) that the getOwnPropertyNames operation should proceed ahead or fail. This extension of the direct proxy approach applies to all query operations, eliminates the copying and validation overhead discussed below, simplifies the implementation, retains full backwards compatibility, and enables most if not all the expressiveness we might expect for proxies. Dean From: Allen Wirfs-Brock al...@wirfs-brock.com Date: Tue, Nov 20, 2012 at 2:18 PM Subject: Fwd: possible excessive proxy invariants for Object.keys/etc?? To: es-discuss discussion es-discuss@mozilla.org Tom Van Custem have been having some email discussion while I work on integrating Proxys into the ES6 spec. He and I agree that some broader input would be useful so I'm going to forward some of the message here to es-discuss and carry the discussion forward here. Here is the first message with other to follow: Begin forwarded message: From: Allen Wirfs-Brock al...@wirfs-brock.com Date: November 18, 2012 1:26:14 PM PST To: Tom Van Cutsem tomvc...@gmail.com, Mark S. Miller erig...@google.com Cc: Jason Orendorff jorendo...@mozilla.com Subject: possible excessive proxy invariants for Object.keys/etc?? I'm wondering if the wiki spec. for these functions aren't doing invariant checking that goes beyond what is required for the integrity purposes you have stated. In general, proxies traps check to ensure that the invariants of a sealed/frozen target object aren't violated. Generally, only minimal processing needs to be done if the target is extensible and has no non-configurable properties. In fact the Virtual Object proposal says As long as the proxy does not expose non-configurable properties or becomes non-extensible, the target object is fully ignored (except to acquire internal properties such as [[Class]]). . The proxy spec.for Object.getOwnPropertyNames/kets/etc. seem to be doing quite a bit more than this. They 1) always copy the array returned from the trap? Why is this necessary? Sure the author of a trap should probably always return a fresh object but not doing so doesn't violate the integrity of the frozen/sealed invariants? In most cases they will provide a fresh object and copying adds unnecessary work that is proportional to the number of names to every such call. 2) ensuring that the list of property keys contains no duplicates. Why is this essential? Again, I don't see what it has to do with the integrity of the frozen/sealed invariants. It is extra and probably unnecessary work that is at least proportional to the number of names). 3) Every name in the list returned by the trap code is looked up on the target to determine whether or not it exists, even if the target is extensible. Each of those lookup is observable (the target might itself be a proxy) so, according to the algorithm they all must be performed. 4) Every own property of the target, is observably looked up (possibly a second time) even if the object is extensible and has no non-configurable properties. It isn't clear to me if any of this work is really necessary to ensure integrity. After all, what can you do with any of these names other than use them as the property key argument to some other trap/internal method such as [[SetP]], [[DefineOwnProperty]], etc. Called on a proxy, those fundamental operations are going to enforce the integrity invariants of the actual properties involved so the get name checks doesn't really seem to be adding anything essential. Perhaps we can just get rid of all the above checking. It seems like a good idea to me. Alternatively, it suggests that a [[GetNonConfigurablePropertyNames]] internal method/trap would be a useful call to have as the integrity invariants only care about non-configurable properties. That would significantly limit the work in the case where there are none and limit the observable trap calls to only the non-configurable properties. Allen
Re: possible excessive proxy invariants for Object.keys/etc??
+1. I think this is a really effective extension of the direct proxy approach, and I don't know why we didn't see it earlier. It's weird that this preserves all the flexibility of fully virtual configurable properties even though it insists that even these be made into real properties on the target. The trick is that placing a configurable property on the target doesn't commit the handler to anything, since the handler can remove or change this property freely as of the next trap. Apologies again for not yet having the time to do more than skim the thread at this point. But IIRC someone already suggested a similar change to some of the imperative traps -- perhaps freeze, seal, and preventExtensions. The handler would not perform these operations on the target, only to have the proxy check it. Rather, if the trap indicates that the operation should succeed, the proxy then simply performs the operation -- or else throws. I wonder if this philosophy could be extended to some of the other imperative operations as well? What expressiveness does this even-more-direct proxy approach lose? AFAICT, not much. At the same time, it should result in a *much* simpler implementation and much greater confidence that invariants of the non-proxy sublanguage are preserved by the introduction of proxies. In fact, I think it's much stronger on invariant preservation that the current direct proxies, while being much simpler. On Sat, Nov 24, 2012 at 6:49 PM, Dean Tribble dtrib...@gmail.com wrote: I am looking forward to proxies in JavaScript, and had a thought on the issues below. You could extend the the direct proxy approach for this. When the Proxy receives getOwnPropertyNames, it 1) notifies the handler that property names are being requested 2) the handler adds/removes any properties (configurable or otherwise subject to the normal constraints) on the target 3) upon return, the proxy invokes getOwnPropertyNames directly on the target (e..g, invoking the normal system primitive) This approach appears to have consistent behavior for configurability and extensibility. For example, the trap operation above could add configurable properties to an extensible target, and remove them later. It could add non-configurable properties, but they are permanent once added, etc. Thus there's no loss of generality. In addition to optionally setting up properties on the target, the handler trap above would need to indicate to the proxy (via exception or boolean result) that the getOwnPropertyNames operation should proceed ahead or fail. This extension of the direct proxy approach applies to all query operations, eliminates the copying and validation overhead discussed below, simplifies the implementation, retains full backwards compatibility, and enables most if not all the expressiveness we might expect for proxies. Dean From: Allen Wirfs-Brock al...@wirfs-brock.com Date: Tue, Nov 20, 2012 at 2:18 PM Subject: Fwd: possible excessive proxy invariants for Object.keys/etc?? To: es-discuss discussion es-discuss@mozilla.org Tom Van Custem have been having some email discussion while I work on integrating Proxys into the ES6 spec. He and I agree that some broader input would be useful so I'm going to forward some of the message here to es-discuss and carry the discussion forward here. Here is the first message with other to follow: Begin forwarded message: From: Allen Wirfs-Brock al...@wirfs-brock.com Date: November 18, 2012 1:26:14 PM PST To: Tom Van Cutsem tomvc...@gmail.com, Mark S. Miller erig...@google.com Cc: Jason Orendorff jorendo...@mozilla.com Subject: possible excessive proxy invariants for Object.keys/etc?? I'm wondering if the wiki spec. for these functions aren't doing invariant checking that goes beyond what is required for the integrity purposes you have stated. In general, proxies traps check to ensure that the invariants of a sealed/frozen target object aren't violated. Generally, only minimal processing needs to be done if the target is extensible and has no non-configurable properties. In fact the Virtual Object proposal says As long as the proxy does not expose non-configurable properties or becomes non-extensible, the target object is fully ignored (except to acquire internal properties such as [[Class]]). . The proxy spec.for Object.getOwnPropertyNames/kets/etc. seem to be doing quite a bit more than this. They 1) always copy the array returned from the trap? Why is this necessary? Sure the author of a trap should probably always return a fresh object but not doing so doesn't violate the integrity of the frozen/sealed invariants? In most cases they will provide a fresh object and copying adds unnecessary work that is proportional to the number of names to every such call. 2) ensuring that the list of property keys contains no duplicates. Why is this essential? Again, I don't see what it has to do with the integrity of the frozen
Re: possible excessive proxy invariants for Object.keys/etc??
2012/11/21 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 21, 2012, at 12:09 PM, Tom Van Cutsem wrote: Let's first discuss whether there is an issue with just letting (unique) symbols show up in Object.{keys,getOwnPropertyNames}. It's still a change in return type (from Array[String] to Array[String|Symbol]), but a far less innocuous one than changing Array[String] into Any. Wow, I actually think that the Array[String|Symbol] change is far more likely to have actual compatibility impact. I can imagine various scenarios where is program or library is doing string processing on the elements returned from keys/getOPN. That seems far more likely, then that they have dependencies upon the returned collection being Array.isArray rather than just Array-like. If we don't do any invariant checks, the trap isn't even obliged to return an array-like. Code that expects an array-like is likely to immediately crash if this happens though, so I'm not sure if that's a real issue. c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). Yes, premature type coercion, in my opinion. Also, a classic performance mistakes made by dynamic language programmers: unnecessary coercion of values that are never going to be access or redundant coercion checks of values that are already of the property type. Why is it important to do such checks on values that are are just passing through these traps. When and if somebody actually gets arounds to using one of the elements that are returned as a property key they will be automatically coerced to a valid value. Why is it important that it happens any sooner than that. I don't know if you are familiar with the work of Felleisen et al. on higher-order contracts. In that work, they use the notion of blame between different components/modules. Basically: if some module A receives some data from a module B, and B provides wrong data, then B should be assigned blame. You don't want to end up in a situation where A receives the blame at the point where it passes that wrong data into another module C. Yes, but ES is rampant with this sort of potentially misplaced blame. We can debate whether such proper blame assignment is important or not, but I do believe this sort of very low level MOP interface is a situation where you want to absolutely minimize none essential work. I'd sooner have it be a little bit more difficult to track now Proxy based bugs then to impact the performance of every correctly implemented proxy in every correct program. BTW this is a general statement about the entire proxy MOP and not just about these particularly property key access traps. It's true that most proxies will probably be benign and thus most dynamic invariant checks will succeed. But we should not remove checks just because they succeed 99.9% of the time. By analogy, most arguments passed to the + operator are valid operands, but that doesn't obviate the runtime type-check it must make over and over again (barring optimization). In the case of the + operator a forgotten type-check would lead to a crash or memory-unsafe behavior. In the case of a forgotten invariant check for proxies, the consequences are far less severe, but may lead to inconsistent behavior none the less. There is a big difference between essential checks that are needed for memory safety and checks that are needed to maintain some of these proxy invariants. As you say, one will cause system level crashes or unsafe memory accesses. The other just introduces application level bugs. Essential runtime checks are always should be designed to be as inexpensive as possible and for that reason are always very simple. Complex relationship validation belongs at the application level. You might do such validation in your sandbox implementation (for example providing your own Object.getOwnPropertyNames that does a copy to a fresh array) but it shouldn't be forced upon applications that don't need it. The early type coercions at the exit of the traps can be thought of in these terms: the invariant checks will always blame the proxy handler for producing wrong data, at the point where the wrong data is produced and before it's exposed to client code. Clients are expecting ES5/6 objects to obey an implicit contract. We are defining that contract right now. It would be a mistake to over specify it in ways that may unnecessarily impact performance. getOwnPropertyDescriptor is also on my list to talk to you about for similar reasons. I don't see why the property descriptor needs to be normalized and regenerated (including converting accessor properties on the descriptor into data properties and requiring the
Re: possible excessive proxy invariants for Object.keys/etc??
On Nov 22, 2012, at 5:36 AM, Tom Van Cutsem wrote: 2012/11/21 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 21, 2012, at 12:09 PM, Tom Van Cutsem wrote: Let's first discuss whether there is an issue with just letting (unique) symbols show up in Object.{keys,getOwnPropertyNames}. It's still a change in return type (from Array[String] to Array[String|Symbol]), but a far less innocuous one than changing Array[String] into Any. Wow, I actually think that the Array[String|Symbol] change is far more likely to have actual compatibility impact. I can imagine various scenarios where is program or library is doing string processing on the elements returned from keys/getOPN. That seems far more likely, then that they have dependencies upon the returned collection being Array.isArray rather than just Array-like. If we don't do any invariant checks, the trap isn't even obliged to return an array-like. Code that expects an array-like is likely to immediately crash if this happens though, so I'm not sure if that's a real issue. Just like all the generic array function. Essentially all objects are array-like, you don't even need to have a length property. Such array-likes generally act as if they had a length of 0. Alternatively, what's the real difference to an application whether it crashes (eg, throws TypeError) within the Proxy implementation of [[Keys]] or crashes when it touches the object returned from Object.keys? ... A concrete example: say I'm implementing a membrane that wants to allow frozen records to pass through the membrane unwrapped. By record, I mean an object with only data properties bound to primitives (strings, numbers, ...). Since individual strings and numbers can pass through unwrapped, it's not entirely unreasonable to allow compound values composed of only primitives to be passed through unwrapped as well. To pass such frozen records, the membrane needs to perform a check to see whether the frozen object is indeed a record. To do so, it must be able to iterate over all the properties of the object and test whether they are indeed data properties bound to primitives. If the frozen record is a proxy that can lie about its own properties, it can not report a particular foo property that is bound to a mutable object, thus opening a communications channel piercing the membrane. ... Hence my above question, is getOPN really one of these basic primitives? Without at least 1 operation that reliably lists an object's properties, you can't reliably introspect a frozen object. ... It still appears to me that for Sealed/Frozen objects, which I believe are the ones you actually care about, you are enforcing that the list returned is exactly the own properties of the ultimate ordinary target object (perhaps with multiple levels of proxy indirection). In otherwords, the result cannot be meaningfully changed. In that case it doesn't need to be trap. You're right that the result cannot be meaningfully changed for frozen objects. But it still needs to be a trap for membranes. The issue is again that the membrane proxy needs to be notified of the operation occurring, so that it can update its target object before the operation proceeds. Like Brandon mentioned earlier in this thread, the trap is effectively just a notification callback at that point. Then I think the thing to do is provide a [[GetNonConfigurablePropertyKeys]] internal method that, for Proxy objects, only calls the trap as a notification and always returns the [[GetNonConfigurablePropertyKeys]] of the target object (which would eventually bottom out in an ordinary object. Because your use cases require sealed/frozen objects the list returned is equivalent for those situations to the the own property list. I'd also eliminate all of the integrity checking from [Keys]]/[[GetOwnPropertyNames]] because for your use cases they simply wouldn't be used. But they still have utility for people implementing pure open virtual objects. Eliminating the unnecessary integrity checks greatly simplifies them and should make them more efficient. Finally, because sealed/frozen is so key to your use cases, I'd move forward with my suggestion for formalizing the sealed/frozen as object states. Rather that something that has to be deduced from [[Extensible]] and the [[Configurable]]/[[Writable]] attributes. It may not be essential (implementation might internally optimize it that way, anyway) but it seems important enough that it would be better for the spec. to be explicit about this rather than leaving in to implementation to figure out that such an optimization is important. Also, making the state explicit eliminates all the individually observable [[GetOwnProperty]] calls that are otherwise needed to deduce frozen/sealed (and which can't be optimized away because they are observable via traps). BTW, the ordering of those
Re: possible excessive proxy invariants for Object.keys/etc??
Le 21/11/2012 01:06, Allen Wirfs-Brock a écrit : b) that all the elements of the result are Strings And presumably Symbols. We have to also accommodate Symbols, at least for getOwnPropetyNames. Regardless, why is this important? More below... Same argument as above. I recall there was some concern about symbols showing up in existing reflection methods like Object.getOwnPropertyNames. Not sure how that got resolved. It's basically touching upon the same issue of whether we can change the return type of existing methods. I'm assuming we are excluding symbols from [[Enumerate]] and [[Keys]] Why so? I would assume unique symbols to show up for these. so it is only [[GetOwnPropertyNames]] that have this concern. and of course non-enumerable unique symbols for this operation. For both case, I think returning unique symbols work as long as ES6 doesn't consider that objects have built-in unique-symboled properties (because that could probably break existing code). One way or another we have to be able to reflectively get a list of symbol keyed properties. We can either: 1) include them in the Object.getOwnPropertyNames result. 1bis) Make all built-in symbols (like @iterable) private 2) Consider getOwnPropertyNames depreciated (it actually lives forever) and replace it with a new Object.getOwnPropertyKeys that includes non-private Symbol keys 3) Keep getOwnPropertyNames as is and add Object.getOwnPropertySymbols that only returns the non-private-Symbol keys. 1) has the greatest backward compat concerns, but is the simplest to provide and for ES programmers to deal with going forward. Since the use of Object.getOwnPropertyNames may not be widespread, maybe that making non-enumerable unique symbol properties could do the trick (as it has with new {Object, Array, etc.}.prototype additions) 1bis) If all standard built-in names are private, they're not enumerated, neither in for-in, Object.keys, Object.getOwnPropertyNames and you can have a unique name in any of these enumeration operation if you've added them manually, so, no backward compat (or close enough that it's acceptable in my opinion) 2) Eliminates the compat risk but creates perpetual potential for a: used gOPNames when I should have used gOPK hazard. 3) Eliminates the compat risk but means everybody who wants to deal with all own properties have to deal with two lists of keys. They also loose relative insertion order info relating key/symbol property keys. It's a bit unfortunate to make all built-in symbols private (because they ought to be unique), but it feels like something acceptable. It may induce a bit of boilerplate for proxy whitelists (because all built-in names like @iterable would need to be added), but a built-in constructor helper that would generate sets with all built-in names in it could solve that issue easily. David [1] http://wiki.ecmascript.org/doku.php?id=strawman:enumeration ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
On 21 November 2012 01:06, Allen Wirfs-Brock al...@wirfs-brock.com wrote: Tom Van Cutsem tomvc...@gmail.com wrote: Allen Wirfs-Brock al...@wirfs-brock.com wrote: Tom Van Cutsem tomvc...@gmail.com wrote: c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). Yes, premature type coercion, in my opinion. Also, a classic performance mistakes made by dynamic language programmers: unnecessary coercion of values that are never going to be access or redundant coercion checks of values that are already of the property type. Why is it important to do such checks on values that are are just passing through these traps. When and if somebody actually gets arounds to using one of the elements that are returned as a property key they will be automatically coerced to a valid value. Why is it important that it happens any sooner than that. I don't know if you are familiar with the work of Felleisen et al. on higher-order contracts. In that work, they use the notion of blame between different components/modules. Basically: if some module A receives some data from a module B, and B provides wrong data, then B should be assigned blame. You don't want to end up in a situation where A receives the blame at the point where it passes that wrong data into another module C. Yes, but ES is rampant with this sort of potentially misplaced blame. We can debate whether such proper blame assignment is important or not, but I do believe this sort of very low level MOP interface is a situation where you want to absolutely minimize none essential work. I'd sooner have it be a little bit more difficult to track now Proxy based bugs then to impact the performance of every correctly implemented proxy in every correct program. BTW this is a general statement about the entire proxy MOP and not just about these particularly property key access traps. I'm strongly in favour of guaranteeing the contract Tom is mentioning. However, there is an alternative to copying: we could require the array (or array-like object) returned by the trap to be frozen. (We could also freeze it ourselves, but that might be more problematic.) (The fact that ES is already full of mistakes should not be an excuse for reiterating them for new features.) /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
On Nov 21, 2012, at 1:55 AM, David Bruant wrote: Le 21/11/2012 01:06, Allen Wirfs-Brock a écrit : b) that all the elements of the result are Strings And presumably Symbols. We have to also accommodate Symbols, at least for getOwnPropetyNames. Regardless, why is this important? More below... Same argument as above. I recall there was some concern about symbols showing up in existing reflection methods like Object.getOwnPropertyNames. Not sure how that got resolved. It's basically touching upon the same issue of whether we can change the return type of existing methods. I'm assuming we are excluding symbols from [[Enumerate]] and [[Keys]] Why so? I would assume unique symbols to show up for these. I believe that the POR is that Symbol keys are never enumerated (even if associated with a the property attribute that has [[Enumerable]]: true). I suppose a proxy could violate that invariant (I wouldn't want to actively enforce it) but would be a buggy proxy. so it is only [[GetOwnPropertyNames]] that have this concern. and of course non-enumerable unique symbols for this operation. ie, private Symbols (private Symbols, not only are always non-enumerable. They are never reflected by primitive operation unless the symbol value is explicitly presented as an argument. You can getOwnPropertyDescriptor using a private symbol, but getOwnPropertyNames never includes them) For both case, I think returning unique symbols work as long as ES6 doesn't consider that objects have built-in unique-symboled properties (because that could probably break existing code). I assume by unique-symbol you mean the samething as private symbol. The above will hold, assuming we make @@interator, @@toStringTag, @@hasInstance, etc. private symbols. While they need to be well-known, I don't see any reason why they shouldn't also be private. One way or another we have to be able to reflectively get a list of symbol keyed properties. We can either: 1) include them in the Object.getOwnPropertyNames result. 1bis) Make all built-in symbols (like @iterable) private 2) Consider getOwnPropertyNames depreciated (it actually lives forever) and replace it with a new Object.getOwnPropertyKeys that includes non-private Symbol keys 3) Keep getOwnPropertyNames as is and add Object.getOwnPropertySymbols that only returns the non-private-Symbol keys. 1) has the greatest backward compat concerns, but is the simplest to provide and for ES programmers to deal with going forward. Since the use of Object.getOwnPropertyNames may not be widespread, maybe that making non-enumerable unique symbol properties could do the trick (as it has with new {Object, Array, etc.}.prototype additions) 1bis) If all standard built-in names are private, they're not enumerated, neither in for-in, Object.keys, Object.getOwnPropertyNames and you can have a unique name in any of these enumeration operation if you've added them manually, so, no backward compat (or close enough that it's acceptable in my opinion) 2) Eliminates the compat risk but creates perpetual potential for a: used gOPNames when I should have used gOPK hazard. 3) Eliminates the compat risk but means everybody who wants to deal with all own properties have to deal with two lists of keys. They also loose relative insertion order info relating key/symbol property keys. It's a bit unfortunate to make all built-in symbols private (because they ought to be unique), but it feels like something acceptable. It may induce a bit of boilerplate for proxy whitelists (because all built-in names like @iterable would need to be added), but a built-in constructor helper that would generate sets with all built-in names in it could solve that issue easily. Good point about the white lists, that changes my mind about making the built-in symbols private. I think I'd prefer to take the risks of alternative 1 rather than forcing proxy writers to deal with them in white lists. I think there is probably a greater risk of people messing up whitelisting than that there will be campat. issues with gOPN. Note that even in legacy code, if a gOPN symbol valued element is used in any context that requires a property key, things will still work fine. It is only explicitly doing string manipulation on a gOPN element that would cause trouble. For example, trying to string prefix every element a list of own property names. My second choice would be adding getOwnPropertyKeys. I would also only want to have a getOwnPropertyKeys internal method/trap and would defined getOwnPropertyNames as filtering that list. Allen David [1] http://wiki.ecmascript.org/doku.php?id=strawman:enumeration ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
On Nov 21, 2012, at 3:54 AM, Andreas Rossberg wrote: On 21 November 2012 01:06, Allen Wirfs-Brock al...@wirfs-brock.com wrote: Tom Van Cutsem tomvc...@gmail.com wrote: Allen Wirfs-Brock al...@wirfs-brock.com wrote: Tom Van Cutsem tomvc...@gmail.com wrote: c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). Yes, premature type coercion, in my opinion. Also, a classic performance mistakes made by dynamic language programmers: unnecessary coercion of values that are never going to be access or redundant coercion checks of values that are already of the property type. Why is it important to do such checks on values that are are just passing through these traps. When and if somebody actually gets arounds to using one of the elements that are returned as a property key they will be automatically coerced to a valid value. Why is it important that it happens any sooner than that. I don't know if you are familiar with the work of Felleisen et al. on higher-order contracts. In that work, they use the notion of blame between different components/modules. Basically: if some module A receives some data from a module B, and B provides wrong data, then B should be assigned blame. You don't want to end up in a situation where A receives the blame at the point where it passes that wrong data into another module C. Yes, but ES is rampant with this sort of potentially misplaced blame. We can debate whether such proper blame assignment is important or not, but I do believe this sort of very low level MOP interface is a situation where you want to absolutely minimize none essential work. I'd sooner have it be a little bit more difficult to track now Proxy based bugs then to impact the performance of every correctly implemented proxy in every correct program. BTW this is a general statement about the entire proxy MOP and not just about these particularly property key access traps. I'm strongly in favour of guaranteeing the contract Tom is mentioning. However, there is an alternative to copying: we could require the array (or array-like object) returned by the trap to be frozen. (We could also freeze it ourselves, but that might be more problematic.) I'd be more favorably inclined towards freezing than I am towards copying. But, as you know, ES5 does not currently produce frozen objects in these situations. I feel uncomfortable about enforcing a frozen invariant for traps where that invariant is not provided by the corresponding ordinary object behavior. Perhaps I could get over that or perhaps incompatibility applying that requirement to ordinary objects wouldn't break anything. Regardless, freezing and testing for frozen is, itself, not a cheap operation. It requires iterating over all the property descriptors of an object. If we are going to build in a lot of checks of for frozen objects perhaps we should just make frozen (and possibly) sealed object level states rather than a dynamic check of all properties. Essentially we could internally turn the [[Extensible]] internal property into a four state value: open,non-extensible,sealed,frozen. It would make both freezing and checking for frozen much cheaper. (The fact that ES is already full of mistakes should not be an excuse for reiterating them for new features.) I think it is usually a mistake to perform complex invariant check at low levels of a language engine. Those often become performance barriers. Checking complex relationships belongs at higher abstraction layers. Allen /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
On 21 November 2012 17:55, Allen Wirfs-Brock al...@wirfs-brock.com wrote: I'd be more favorably inclined towards freezing than I am towards copying. But, as you know, ES5 does not currently produce frozen objects in these situations. I feel uncomfortable about enforcing a frozen invariant for traps where that invariant is not provided by the corresponding ordinary object behavior. Perhaps I could get over that or perhaps incompatibility applying that requirement to ordinary objects wouldn't break anything. Regardless, freezing and testing for frozen is, itself, not a cheap operation. It requires iterating over all the property descriptors of an object. If we are going to build in a lot of checks of for frozen objects perhaps we should just make frozen (and possibly) sealed object level states rather than a dynamic check of all properties. Essentially we could internally turn the [[Extensible]] internal property into a four state value: open,non-extensible,sealed,frozen. It would make both freezing and checking for frozen much cheaper. That doesn't seem necessary, because it is just as easy to optimise the current check for the normal case where the object has been frozen or sealed with the respective operation. I think it is usually a mistake to perform complex invariant check at low levels of a language engine. Those often become performance barriers. Checking complex relationships belongs at higher abstraction layers. Well, the root of the problem arguably lies with the whole idea of proxies hooking arbitrary code into low-level operations. Perhaps such power has to come with a cost in terms of checks and balances. There are no higher abstraction layers in this case. /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
On 21 November 2012 18:35, Allen Wirfs-Brock al...@wirfs-brock.com wrote: If you are writing any sort of generic algorithm that does a freeze check on an arbitrary object you have to explicitly perform all of the internal method calls because you don't know whether the object is a proxy (where every such internal method call turns into an observable trap) or even some other sort of exotic object implementation that can observe actual internal method calls. If there is explicit internal state is designate an object as frozen, then we wouldn't have all of those potentially observable calls. Yes, but the fast path in the VM would merely check whether you have an ordinary object with the 'frozen' flag set. Only if that fails, or for (most) proxies and other exotics, you have to fall back to do something more complicated. Presumably, most practical use cases would never hit that. /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
Le 21/11/2012 17:37, Allen Wirfs-Brock a écrit : On Nov 21, 2012, at 1:55 AM, David Bruant wrote: Le 21/11/2012 01:06, Allen Wirfs-Brock a écrit : b) that all the elements of the result are Strings And presumably Symbols. We have to also accommodate Symbols, at least for getOwnPropetyNames. Regardless, why is this important? More below... Same argument as above. I recall there was some concern about symbols showing up in existing reflection methods like Object.getOwnPropertyNames. Not sure how that got resolved. It's basically touching upon the same issue of whether we can change the return type of existing methods. I'm assuming we are excluding symbols from [[Enumerate]] and [[Keys]] Why so? I would assume unique symbols to show up for these. I believe that the POR is that Symbol keys are never enumerated (even if associated with a the property attribute that has [[Enumerable]]: true). I suppose a proxy could violate that invariant (I wouldn't want to actively enforce it) but would be a buggy proxy. What does POR mean? so it is only [[GetOwnPropertyNames]] that have this concern. and of course non-enumerable unique symbols for this operation. ie, private Symbols (private Symbols, not only are always non-enumerable. They are never reflected by primitive operation unless the symbol value is explicitly presented as an argument. You can getOwnPropertyDescriptor using a private symbol, but getOwnPropertyNames never includes them) For both case, I think returning unique symbols work as long as ES6 doesn't consider that objects have built-in unique-symboled properties (because that could probably break existing code). I assume by unique-symbol you mean the samething as private symbol. Indeed, I meant private here, sorry for the mistake. The above will hold, assuming we make @@interator, @@toStringTag, @@hasInstance, etc. private symbols. While they need to be well-known, I don't see any reason why they shouldn't also be private. +1 Good point about the white lists, that changes my mind about making the built-in symbols private. I think I'd prefer to take the risks of alternative 1 rather than forcing proxy writers to deal with them in white lists. I think there is probably a greater risk of people messing up whitelisting than that there will be campat. issues with gOPN. If the proxy module provides a constructor like BuiltInPrivateNamesWeakSet, things could be fine: var whitelist = new BuiltInPrivateNamesWeakSet(); whitelist.add(...) // adding user generated private names if applicable var p = new Proxy(target, handler, whitelist); The name I'm proposing is a bit long, but that would solve the built-in private name micro management problem elegantly enough as far as I'm concerned. It's also future-proof in the sense that if new built-in private names are added, the weakset will do the right thing in updated environments. If people want to build the weakset manually, they would be free to do so, but at least, the helper would do the annoying part on behalf of the user. We can consider that if the third argument is omitted, all built-in private names pass. That sounds like a good useful default. David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
2012/11/19 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 19, 2012, at 10:04 AM, Tom Van Cutsem wrote: It's all a matter of developer expectations and how much leeway we have in changing the return type of existing built-ins. In theory, it's a backwards-incompatible change. In practice, it may not matter. It can't break existing code that doesn't use Proxies. It's very likely that existing ES5 code (which doesn't use or know about proxies) will come to interact with ES6 proxies. Consider a sandboxed JS environment that provides a wrapped DOM (implemented using proxies). The ES5 code loaded into the sandbox doesn't actively use proxies; it will interact with proxies unknowingly. That's the kind of scenarios for which we should be careful with backwards-incompatible changes. b) that all the elements of the result are Strings And presumably Symbols. We have to also accommodate Symbols, at least for getOwnPropetyNames. Regardless, why is this important? More below... Same argument as above. I recall there was some concern about symbols showing up in existing reflection methods like Object.getOwnPropertyNames. Not sure how that got resolved. It's basically touching upon the same issue of whether we can change the return type of existing methods. I'm assuming we are excluding symbols from [[Enumerate]] and [[Keys]] so it is only [[GetOwnPropertyNames]] that have this concern. One way or another we have to be able to reflectively get a list of symbol keyed properties. We can either: 1) include them in the Object.getOwnPropertyNames result. 2) Consider getOwnPropertyNames depreciated (it actually lives forever) and replace it with a new Object.getOwnPropertyKeys that includes non-private Symbol keys 3) Keep getOwnPropertyNames as is and add Object.getOwnPropertySymbols that only returns the non-private-Symbol keys. 1) has the greatest backward compat concerns, but is the simplest to provide and for ES programmers to deal with going forward. 2) Eliminates the compat risk but creates perpetual potential for a: used gOPNames when I should have used gOPK hazard. 3) Eliminates the compat risk but means everybody who wants to deal with all own properties have to deal with two lists of keys. They also loose relative insertion order info relating key/symbol property keys. Either 2 or 3 could require widening the MOP which increases the possibility for incomplete, inconsistent, or otherwise buggy proxies. Even if we expose multiple public functions like either 2 or 3 I would still prefer to only have a single getOwnProperyyKeys internal method/trap that can be filtered to provide just strings and/or symbols. Overall, I'm more concerned about keeping the internal method/trap MOP width as narrow as possible than I am about the size of the public Object.* API Let's first discuss whether there is an issue with just letting (unique) symbols show up in Object.{keys,getOwnPropertyNames}. It's still a change in return type (from Array[String] to Array[String|Symbol]), but a far less innocuous one than changing Array[String] into Any. c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). Yes, premature type coercion, in my opinion. Also, a classic performance mistakes made by dynamic language programmers: unnecessary coercion of values that are never going to be access or redundant coercion checks of values that are already of the property type. Why is it important to do such checks on values that are are just passing through these traps. When and if somebody actually gets arounds to using one of the elements that are returned as a property key they will be automatically coerced to a valid value. Why is it important that it happens any sooner than that. I don't know if you are familiar with the work of Felleisen et al. on higher-order contracts. In that work, they use the notion of blame between different components/modules. Basically: if some module A receives some data from a module B, and B provides wrong data, then B should be assigned blame. You don't want to end up in a situation where A receives the blame at the point where it passes that wrong data into another module C. Yes, but ES is rampant with this sort of potentially misplaced blame. We can debate whether such proper blame assignment is important or not, but I do believe this sort of very low level MOP interface is a situation where you want to absolutely minimize none essential work. I'd sooner have it be a little bit more difficult to track now Proxy based bugs then to impact the performance of every correctly implemented proxy in every correct program. BTW this is a general statement about the entire proxy MOP
Re: possible excessive proxy invariants for Object.keys/etc??
2012/11/21 David Bruant bruan...@gmail.com Since the use of Object.getOwnPropertyNames may not be widespread, maybe that making non-enumerable unique symbol properties could do the trick (as it has with new {Object, Array, etc.}.prototype additions) 1bis) If all standard built-in names are private, they're not enumerated, neither in for-in, Object.keys, Object.getOwnPropertyNames and you can have a unique name in any of these enumeration operation if you've added them manually, so, no backward compat (or close enough that it's acceptable in my opinion) Even disregarding the proxy whitelist hassle, let's not go there. Making all the well-known unique symbols, like @iterator, private just so they don't appear in Object.getOwnPropertyNames feels silly to me. We have the distinction between unique and private symbols precisely because we've identified a need for symbols that need to be unique but not necessarily private! Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
2012/11/21 Allen Wirfs-Brock al...@wirfs-brock.com I'd be more favorably inclined towards freezing than I am towards copying. But, as you know, ES5 does not currently produce frozen objects in these situations. I feel uncomfortable about enforcing a frozen invariant for traps where that invariant is not provided by the corresponding ordinary object behavior. Perhaps I could get over that or perhaps incompatibility applying that requirement to ordinary objects wouldn't break anything. While the arrays produced by Object.keys etc. aren't frozen, there's an implicit guarantee that those arrays will not further be modified implicitly by any other operation in the program. Of course a program can choose to explicitly mutate it. But there's no magical action-at-a-distance. Currently, we provide the same guarantee in the face of proxies by defensively copying the trap result into a fresh array. Requiring the trap result to be frozen feels OK to me, except that it doesn't play well with the current default of automatically forwarding: var p = Proxy(target, { }) Object.keys(p) // error: keys trap did not return a frozen array var p = Proxy(target, { keys: function(t) { return Object.freeze(Object.keys(t)); }) Object.keys(p) // now it works One way to get rid of this irregularity is indeed to make Object.keys always return frozen arrays. I don't think it would hurt performance, as implementations already aren't allowed to reuse arrays returned from Object.keys (of course they can always cheat as long as they don't get caught). Regardless, freezing and testing for frozen is, itself, not a cheap operation. It requires iterating over all the property descriptors of an object. If we are going to build in a lot of checks of for frozen objects perhaps we should just make frozen (and possibly) sealed object level states rather than a dynamic check of all properties. Essentially we could internally turn the [[Extensible]] internal property into a four state value: open,non-extensible,sealed,frozen. It would make both freezing and checking for frozen much cheaper. As Andreas mentioned, for normal objects, isFrozen checks can be optimized to O(1). For proxies, if we leave in the derived isFrozen trap, the check could also be O(1). Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
2012/11/21 Mark S. Miller erig...@google.com On Wed, Nov 21, 2012 at 8:55 AM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: [...] Essentially we could internally turn the [[Extensible]] internal property into a four state value: open,non-extensible,sealed,frozen. [...] First, my apologies for not yet finding the time to catch up on this thread. But I did want to encourage this particular idea. We may not be able to make this work out, but it would have many benefits if we could. For example, the isFrozen check in the Object.observe API would make more sense if this were a simple state rather than a pattern check. More later... A lot of the complexity of the current invariant checks derives from the fact that Javascript objects have (too) fine-grained invariants (i.e. non-configurability at the level of individual properties, non-extensibility at the level of the object, and all possible combinations in between). This means we have to account for weird cases such as a non-extensible object with all but 1 property being non-configurable (an almost-frozen object). If JS objects could be in only one of four states, things would be a lot simpler to reason about. That said, I don't see how we can get there without radically breaking with ES5's view of object invariants. Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
On Nov 21, 2012, at 11:13 AM, David Bruant wrote: Le 21/11/2012 17:37, Allen Wirfs-Brock a écrit : On Nov 21, 2012, at 1:55 AM, David Bruant wrote: Le 21/11/2012 01:06, Allen Wirfs-Brock a écrit : b) that all the elements of the result are Strings And presumably Symbols. We have to also accommodate Symbols, at least for getOwnPropetyNames. Regardless, why is this important? More below... Same argument as above. I recall there was some concern about symbols showing up in existing reflection methods like Object.getOwnPropertyNames. Not sure how that got resolved. It's basically touching upon the same issue of whether we can change the return type of existing methods. I'm assuming we are excluding symbols from [[Enumerate]] and [[Keys]] Why so? I would assume unique symbols to show up for these. I believe that the POR is that Symbol keys are never enumerated (even if associated with a the property attribute that has [[Enumerable]]: true). I suppose a proxy could violate that invariant (I wouldn't want to actively enforce it) but would be a buggy proxy. What does POR mean? Plan of Record ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
On Wed, Nov 21, 2012 at 12:42 PM, Tom Van Cutsem tomvc...@gmail.com wrote: 2012/11/21 Mark S. Miller erig...@google.com On Wed, Nov 21, 2012 at 8:55 AM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: [...] Essentially we could internally turn the [[Extensible]] internal property into a four state value: open,non-extensible,sealed,frozen. [...] [...] If JS objects could be in only one of four states, things would be a lot simpler to reason about. That said, I don't see how we can get there without radically breaking with ES5's view of object invariants. Why can't an implementation cache the knowledge that an object is frozen (after Object.freeze or after a full pattern-check) exactly as if the fourth state exists, and get the efficiency benefit (but not the observability-of-the-test-on-a-proxy benefit) without changing the ES5 model? ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
Le 21/11/2012 21:13, Tom Van Cutsem a écrit : 2012/11/21 David Bruant bruan...@gmail.com mailto:bruan...@gmail.com Since the use of Object.getOwnPropertyNames may not be widespread, maybe that making non-enumerable unique symbol properties could do the trick (as it has with new {Object, Array, etc.}.prototype additions) 1bis) If all standard built-in names are private, they're not enumerated, neither in for-in, Object.keys, Object.getOwnPropertyNames and you can have a unique name in any of these enumeration operation if you've added them manually, so, no backward compat (or close enough that it's acceptable in my opinion) Even disregarding the proxy whitelist hassle, let's not go there. Making all the well-known unique symbols, like @iterator, private just so they don't appear in Object.getOwnPropertyNames feels silly to me. We have the distinction between unique and private symbols precisely because we've identified a need for symbols that need to be unique but not necessarily private! Rest assured that it doesn't make me happy to propose this idea, but it seems worthwhile to explore. Allen's latest point about backward compat makes me feel that it may not be that big of a problem (Note that even in legacy code, if a gOPN symbol valued element is used in any context that requires a property key, things will still work fine. It is only explicitly doing string manipulation on a gOPN element that would cause trouble. For example, trying to string prefix every element a list of own property names.) I guess only user testing could tell. If absolutely necessary, it may not be absurd to have the following: string + unique name = new unique name. David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
On Nov 21, 2012, at 12:09 PM, Tom Van Cutsem wrote: 2012/11/19 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 19, 2012, at 10:04 AM, Tom Van Cutsem wrote: It's all a matter of developer expectations and how much leeway we have in changing the return type of existing built-ins. In theory, it's a backwards-incompatible change. In practice, it may not matter. It can't break existing code that doesn't use Proxies. It's very likely that existing ES5 code (which doesn't use or know about proxies) will come to interact with ES6 proxies. Consider a sandboxed JS environment that provides a wrapped DOM (implemented using proxies). The ES5 code loaded into the sandbox doesn't actively use proxies; it will interact with proxies unknowingly. That's the kind of scenarios for which we should be careful with backwards-incompatible changes. Presumably in this scenario it is the sandbox that is providing the Proxy so it can make to always return a built-in array if it is concerned about this. The trade-off is always doing an extra copy of an object, just because of the unlikely possibility that a trap implementor doesn't use an actual built-in array. On the other hand, it seems very unlikely that realistic consumers of the existing Object.keys/Object.getOwnPropertyNames are actually doing an Array.isArray checks on the value produced from them. What would be the point? b) that all the elements of the result are Strings And presumably Symbols. We have to also accommodate Symbols, at least for getOwnPropetyNames. Regardless, why is this important? More below... Same argument as above. I recall there was some concern about symbols showing up in existing reflection methods like Object.getOwnPropertyNames. Not sure how that got resolved. It's basically touching upon the same issue of whether we can change the return type of existing methods. I'm assuming we are excluding symbols from [[Enumerate]] and [[Keys]] so it is only [[GetOwnPropertyNames]] that have this concern. One way or another we have to be able to reflectively get a list of symbol keyed properties. We can either: 1) include them in the Object.getOwnPropertyNames result. 2) Consider getOwnPropertyNames depreciated (it actually lives forever) and replace it with a new Object.getOwnPropertyKeys that includes non-private Symbol keys 3) Keep getOwnPropertyNames as is and add Object.getOwnPropertySymbols that only returns the non-private-Symbol keys. 1) has the greatest backward compat concerns, but is the simplest to provide and for ES programmers to deal with going forward. 2) Eliminates the compat risk but creates perpetual potential for a: used gOPNames when I should have used gOPK hazard. 3) Eliminates the compat risk but means everybody who wants to deal with all own properties have to deal with two lists of keys. They also loose relative insertion order info relating key/symbol property keys. Either 2 or 3 could require widening the MOP which increases the possibility for incomplete, inconsistent, or otherwise buggy proxies. Even if we expose multiple public functions like either 2 or 3 I would still prefer to only have a single getOwnProperyyKeys internal method/trap that can be filtered to provide just strings and/or symbols. Overall, I'm more concerned about keeping the internal method/trap MOP width as narrow as possible than I am about the size of the public Object.* API Let's first discuss whether there is an issue with just letting (unique) symbols show up in Object.{keys,getOwnPropertyNames}. It's still a change in return type (from Array[String] to Array[String|Symbol]), but a far less innocuous one than changing Array[String] into Any. Wow, I actually think that the Array[String|Symbol] change is far more likely to have actual compatibility impact. I can imagine various scenarios where is program or library is doing string processing on the elements returned from keys/getOPN. That seems far more likely, then that they have dependencies upon the returned collection being Array.isArray rather than just Array-like. c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). Yes, premature type coercion, in my opinion. Also, a classic performance mistakes made by dynamic language programmers: unnecessary coercion of values that are never going to be access or redundant coercion checks of values that are already of the property type. Why is it important to do such checks on values that are are just passing through these traps. When and if somebody actually gets arounds to using one of the elements that are returned as a property key they will be
Re: possible excessive proxy invariants for Object.keys/etc??
On Nov 21, 2012, at 12:27 PM, Tom Van Cutsem wrote: 2012/11/21 Allen Wirfs-Brock al...@wirfs-brock.com I'd be more favorably inclined towards freezing than I am towards copying. But, as you know, ES5 does not currently produce frozen objects in these situations. I feel uncomfortable about enforcing a frozen invariant for traps where that invariant is not provided by the corresponding ordinary object behavior. Perhaps I could get over that or perhaps incompatibility applying that requirement to ordinary objects wouldn't break anything. While the arrays produced by Object.keys etc. aren't frozen, there's an implicit guarantee that those arrays will not further be modified implicitly by any other operation in the program. Of course a program can choose to explicitly mutate it. But there's no magical action-at-a-distance. Currently, we provide the same guarantee in the face of proxies by defensively copying the trap result into a fresh array. Requiring the trap result to be frozen feels OK to me, except that it doesn't play well with the current default of automatically forwarding: var p = Proxy(target, { }) Object.keys(p) // error: keys trap did not return a frozen array var p = Proxy(target, { keys: function(t) { return Object.freeze(Object.keys(t)); }) Object.keys(p) // now it works Wait, Object.keys as now just implemented by calling the [[Keys]] internal method and it doesn't have any idea about what kind of object that internal method is being called upon. So it must apply the same rules to both the result of ordinary object [[Keys]] and proxy [[Keys]]. Object.keys could always copy the result or always freeze its result or accept either frozen/non-frozen but all [[Keys]] invocation need to be treated equally. One way to get rid of this irregularity is indeed to make Object.keys always return frozen arrays. I don't think it would hurt performance, as implementations already aren't allowed to reuse arrays returned from Object.keys (of course they can always cheat as long as they don't get caught). But client of Object.keys are currently permitted to modify its result array. Freezing it would break such clients. Regardless, freezing and testing for frozen is, itself, not a cheap operation. It requires iterating over all the property descriptors of an object. If we are going to build in a lot of checks of for frozen objects perhaps we should just make frozen (and possibly) sealed object level states rather than a dynamic check of all properties. Essentially we could internally turn the [[Extensible]] internal property into a four state value: open,non-extensible,sealed,frozen. It would make both freezing and checking for frozen much cheaper. As Andreas mentioned, for normal objects, isFrozen checks can be optimized to O(1). For proxies, if we leave in the derived isFrozen trap, the check could also be O(1). Are you sure about that last point? What if the target is a Proxy or some other exotic object that is observing internal method calls on itself. This is one of my overall concerns about some of the more complex invariant checking algorithms. Our interoperability rules require strict ordering of all observable operations and internal method/trap calls are potentially observable. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
On Nov 21, 2012, at 12:42 PM, Tom Van Cutsem wrote: 2012/11/21 Mark S. Miller erig...@google.com On Wed, Nov 21, 2012 at 8:55 AM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: [...] Essentially we could internally turn the [[Extensible]] internal property into a four state value: open,non-extensible,sealed,frozen. [...] First, my apologies for not yet finding the time to catch up on this thread. But I did want to encourage this particular idea. We may not be able to make this work out, but it would have many benefits if we could. For example, the isFrozen check in the Object.observe API would make more sense if this were a simple state rather than a pattern check. More later... A lot of the complexity of the current invariant checks derives from the fact that Javascript objects have (too) fine-grained invariants (i.e. non-configurability at the level of individual properties, non-extensibility at the level of the object, and all possible combinations in between). This means we have to account for weird cases such as a non-extensible object with all but 1 property being non-configurable (an almost-frozen object). If JS objects could be in only one of four states, things would be a lot simpler to reason about. That said, I don't see how we can get there without radically breaking with ES5's view of object invariants. Of course, I don't necessarily agree that ES5 has such a view of object invariants... ;-) However, everything we currently have in ES5 maps to one of those fours states. Do you actually care very much, form a reasoning perspective, about a open object with some non-configurable properties. Or an non-extensible object with with some configurable properties? Overall, I think having the four states makes the conceptual model clearer and might guide us in cleaning up the MOP. Allen Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Fwd: possible excessive proxy invariants for Object.keys/etc??
Tom Van Custem have been having some email discussion while I work on integrating Proxys into the ES6 spec. He and I agree that some broader input would be useful so I'm going to forward some of the message here to es-discuss and carry the discussion forward here. Here is the first message with other to follow: Begin forwarded message: From: Allen Wirfs-Brock al...@wirfs-brock.com Date: November 18, 2012 1:26:14 PM PST To: Tom Van Cutsem tomvc...@gmail.com, Mark S. Miller erig...@google.com Cc: Jason Orendorff jorendo...@mozilla.com Subject: possible excessive proxy invariants for Object.keys/etc?? I'm wondering if the wiki spec. for these functions aren't doing invariant checking that goes beyond what is required for the integrity purposes you have stated. In general, proxies traps check to ensure that the invariants of a sealed/frozen target object aren't violated. Generally, only minimal processing needs to be done if the target is extensible and has no non-configurable properties. In fact the Virtual Object proposal says As long as the proxy does not expose non-configurable properties or becomes non-extensible, the target object is fully ignored (except to acquire internal properties such as [[Class]]). . The proxy spec.for Object.getOwnPropertyNames/kets/etc. seem to be doing quite a bit more than this. They 1) always copy the array returned from the trap? Why is this necessary? Sure the author of a trap should probably always return a fresh object but not doing so doesn't violate the integrity of the frozen/sealed invariants? In most cases they will provide a fresh object and copying adds unnecessary work that is proportional to the number of names to every such call. 2) ensuring that the list of property keys contains no duplicates. Why is this essential? Again, I don't see what it has to do with the integrity of the frozen/sealed invariants. It is extra and probably unnecessary work that is at least proportional to the number of names). 3) Every name in the list returned by the trap code is looked up on the target to determine whether or not it exists, even if the target is extensible. Each of those lookup is observable (the target might itself be a proxy) so, according to the algorithm they all must be performed. 4) Every own property of the target, is observably looked up (possibly a second time) even if the object is extensible and has no non-configurable properties. It isn't clear to me if any of this work is really necessary to ensure integrity. After all, what can you do with any of these names other than use them as the property key argument to some other trap/internal method such as [[SetP]], [[DefineOwnProperty]], etc. Called on a proxy, those fundamental operations are going to enforce the integrity invariants of the actual properties involved so the get name checks doesn't really seem to be adding anything essential. Perhaps we can just get rid of all the above checking. It seems like a good idea to me. Alternatively, it suggests that a [[GetNonConfigurablePropertyNames]] internal method/trap would be a useful call to have as the integrity invariants only care about non-configurable properties. That would significantly limit the work in the case where there are none and limit the observable trap calls to only the non-configurable properties. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
Hi Allen, 2012/11/18 Allen Wirfs-Brock al...@wirfs-brock.com The proxy spec.for Object.getOwnPropertyNames/kets/etc. seem to be doing quite a bit more than this. They 1) always copy the array returned from the trap? Why is this necessary? Sure the author of a trap should probably always return a fresh object but not doing so doesn't violate the integrity of the frozen/sealed invariants? In most cases they will provide a fresh object and copying adds unnecessary work that is proportional to the number of names to every such call. The copying is to ensure: a) that the result is an Array b) that all the elements of the result are Strings c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). c) on the other hand is crucial for the non-configurability/non-extensibility checks mentioned below. It's no use checking some invariants on a data structure if that data structure can later still be mutated. If we don't care about any of a, b and c, then the result array wouldn't need to be copied. 2) ensuring that the list of property keys contains no duplicates. Why is this essential? Again, I don't see what it has to do with the integrity of the frozen/sealed invariants. It is extra and probably unnecessary work that is at least proportional to the number of names). We've been going back and forth over whether or not we wanted to prevent duplicates. I remember Andreas Gal being concerned about these kinds of issues (that was when he was doing the first Firefox prototype for old proxies, in the context of the enumerate() trap, which was called during a live for-in loop. IIRC, Firefox already did de-dupe checks, as properties already enumerated in a child object should not be re-visited when visiting a parent object) More recently, at the last TC39 meeting in Boston, we decided to change the return type of the enumerate() trap from Array[String] to Iterator, and in doing so waiving the duplicate properties check. Quoting from the Open issues section of http://wiki.ecmascript.org/doku.php?id=harmony:direct_proxies#open_issues: Enumerate trap signature: consider making the enumerate() trap return an iterator rather than an array of strings. To retain the benefits of an iterator (no need to store collection in memory), we might need to waive the duplicate properties check. Resolution: accepted (duplicate properties check is waived in favor of iterator return type) I guess if duplicate properties are not crucial for enumeration, they're also not crucial for Object.getOwnPropertyNames and Object.keys and can be dropped. Mark, can you comment? 3) Every name in the list returned by the trap code is looked up on the target to determine whether or not it exists, even if the target is extensible. Each of those lookup is observable (the target might itself be a proxy) so, according to the algorithm they all must be performed. This is where we get into actual checks required to enforce non-configurability/non-extensibility. Granted, the ES5 spec is not clear about the invariants on getOwnPropertyNames and keys. The currently specified invariants are a common-sense extrapolation of the existing invariants to cover these operations. In practice, it determines the degree of confidence that a programmer can have in Object.getOwnPropertyNames and friends when dealing with a frozen object. If we waive these invariant checks, then the result of those operations can never be trusted on to reliably introspect on a frozen object's list of property names: Object.isFrozen(proxy) // true Object.getOwnPropertyNames(proxy) // ['foo'] Object.getOwnPropertyNames(proxy) // [ ] Here, the 'foo' property apparently disappeared on a frozen object. If neither the for-in loop nor Object.getOwnPropertyNames nor Object.keys can reliably report an object's own properties, then we've made it impossible to reliably traverse and inspect a presumably deep-frozen object graph. 4) Every own property of the target, is observably looked up (possibly a second time) even if the object is extensible and has no non-configurable properties. We may be able to remove the redundancy of two lookups by restructuring the algorithm. There previously was some redundancy in other checks as well. It isn't clear to me if any of this work is really necessary to ensure integrity. After all, what can you do with any of these names other than use them as the property key argument to some other trap/internal method such as [[SetP]], [[DefineOwnProperty]], etc. Called on a proxy, those fundamental operations are going to enforce the integrity invariants of the actual properties involved so the get name checks doesn't really seem to be adding anything
Re: possible excessive proxy invariants for Object.keys/etc??
On Nov 19, 2012, at 10:04 AM, Tom Van Cutsem wrote: Hi Allen, 2012/11/18 Allen Wirfs-Brock al...@wirfs-brock.com The proxy spec.for Object.getOwnPropertyNames/kets/etc. seem to be doing quite a bit more than this. They 1) always copy the array returned from the trap? Why is this necessary? Sure the author of a trap should probably always return a fresh object but not doing so doesn't violate the integrity of the frozen/sealed invariants? In most cases they will provide a fresh object and copying adds unnecessary work that is proportional to the number of names to every such call. The copying is to ensure: a) that the result is an Array Why is Array-ness essential? It was what ES5 specified, but ES5 had a fixed implementations of Object.getOwnPropertyNames/keys so that requirement was simply a reflection of what the spec's algorithms actually did. Most places in the specification that consume an array only require array-like-ness, not an actual array. Now that we're make the implementation of the getOPN/keys extensible, it would be natural to relax the requirement that they produce a [[Class]]===Array object. b) that all the elements of the result are Strings And presumably Symbols. We have to also accommodate Symbols, at least for getOwnPropetyNames. Regardless, why is this important? More below... c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). Yes, premature type coercion, in my opinion. Also, a classic performance mistakes made by dynamic language programmers: unnecessary coercion of values that are never going to be access or redundant coercion checks of values that are already of the property type. Why is it important to do such checks on values that are are just passing through these traps. When and if somebody actually gets arounds to using one of the elements that are returned as a property key they will be automatically coerced to a valid value. Why is it important that it happens any sooner than that. getOwnPropertyDescriptor is also on my list to talk to you about for similar reasons. I don't see why the property descriptor needs to be normalized and regenerated (including converting accessor properties on the descriptor into data properties and requiring the [[Prototype]] to be object. I have an alternative for [[GetOwnProperty]]/TrapGetOwnPropertyDescriptor that preserves the original object returned from the trap (while normalizing to a descriptor record for any internal calls on ordinary objects). This preserves any exotic property descriptor object properties as is. This seems like exactly the right things. Such exotic property descriptor properties can only be meaningfully used by other proxy traps or application/library code that knows about them. I don't see any need to normalize them into data properties or otherwise muck with what the actually implementor of the getOwnPropertyDescrptor trap choose to return. I this alternative implementation is, again, much simpler (and efficient) with fewer externally observable calls in its algorithm steps. I sure you will want to review it so I will include it in the spec. draft. c) on the other hand is crucial for the non-configurability/non-extensibility checks mentioned below. It's no use checking some invariants on a data structure if that data structure can later still be mutated. I don't see how an extra copy of the the result array makes any difference in this regard (also see below). The array returned from Object.getOwnPropertyNames is still mutable even after you make a copy. It's only as stable as any other non-frozen object you are likely to encounter. If we don't care about any of a, b and c, then the result array wouldn't need to be copied. yes, I believe that should be the case. 2) ensuring that the list of property keys contains no duplicates. Why is this essential? Again, I don't see what it has to do with the integrity of the frozen/sealed invariants. It is extra and probably unnecessary work that is at least proportional to the number of names). We've been going back and forth over whether or not we wanted to prevent duplicates. I remember Andreas Gal being concerned about these kinds of issues (that was when he was doing the first Firefox prototype for old proxies, in the context of the enumerate() trap, which was called during a live for-in loop. IIRC, Firefox already did de-dupe checks, as properties already enumerated in a child object should not be re-visited when visiting a parent object) More recently, at the last TC39 meeting in Boston, we decided to change the return type of the enumerate() trap from Array[String] to
Re: possible excessive proxy invariants for Object.keys/etc??
2012/11/19 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 19, 2012, at 10:04 AM, Tom Van Cutsem wrote: The copying is to ensure: a) that the result is an Array Why is Array-ness essential? It was what ES5 specified, but ES5 had a fixed implementations of Object.getOwnPropertyNames/keys so that requirement was simply a reflection of what the spec's algorithms actually did. Most places in the specification that consume an array only require array-like-ness, not an actual array. Now that we're make the implementation of the getOPN/keys extensible, it would be natural to relax the requirement that they produce a [[Class]]===Array object. It's all a matter of developer expectations and how much leeway we have in changing the return type of existing built-ins. In theory, it's a backwards-incompatible change. In practice, it may not matter. b) that all the elements of the result are Strings And presumably Symbols. We have to also accommodate Symbols, at least for getOwnPropetyNames. Regardless, why is this important? More below... Same argument as above. I recall there was some concern about symbols showing up in existing reflection methods like Object.getOwnPropertyNames. Not sure how that got resolved. It's basically touching upon the same issue of whether we can change the return type of existing methods. c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). Yes, premature type coercion, in my opinion. Also, a classic performance mistakes made by dynamic language programmers: unnecessary coercion of values that are never going to be access or redundant coercion checks of values that are already of the property type. Why is it important to do such checks on values that are are just passing through these traps. When and if somebody actually gets arounds to using one of the elements that are returned as a property key they will be automatically coerced to a valid value. Why is it important that it happens any sooner than that. I don't know if you are familiar with the work of Felleisen et al. on higher-order contracts. In that work, they use the notion of blame between different components/modules. Basically: if some module A receives some data from a module B, and B provides wrong data, then B should be assigned blame. You don't want to end up in a situation where A receives the blame at the point where it passes that wrong data into another module C. The early type coercions at the exit of the traps can be thought of in these terms: the invariant checks will always blame the proxy handler for producing wrong data, at the point where the wrong data is produced and before it's exposed to client code. Clients are expecting ES5/6 objects to obey an implicit contract. getOwnPropertyDescriptor is also on my list to talk to you about for similar reasons. I don't see why the property descriptor needs to be normalized and regenerated (including converting accessor properties on the descriptor into data properties and requiring the [[Prototype]] to be object. I have an alternative for [[GetOwnProperty]]/TrapGetOwnPropertyDescriptor that preserves the original object returned from the trap (while normalizing to a descriptor record for any internal calls on ordinary objects). This preserves any exotic property descriptor object properties as is. This seems like exactly the right things. Such exotic property descriptor properties can only be meaningfully used by other proxy traps or application/library code that knows about them. I don't see any need to normalize them into data properties or otherwise muck with what the actually implementor of the getOwnPropertyDescrptor trap choose to return. I this alternative implementation is, again, much simpler (and efficient) with fewer externally observable calls in its algorithm steps. I sure you will want to review it so I will include it in the spec. draft. It's all a matter of how much invariants we care to give up. I think it's risky to allow descriptors to be returned whose attributes may be accessors, thus potentially fooling much existing ES5 code that often does simple tests like: if (!desc.writable !desc.configurable) { var val = desc.value; // we can assume the property is immutable, so we can cache its value ... } You can no longer rely on the correctness of such code if desc can't be guaranteed to be a plain old object containing only data properties. c) on the other hand is crucial for the non-configurability/non-extensibility checks mentioned below. It's no use checking some invariants on a data structure if that data structure can later still be mutated. I don't see how an extra copy of the the result array makes any
Fwd: possible excessive proxy invariants for Object.keys/etc??
(for some reason the followup message didn't seem to make it to es-discuss the first time I redirected them. so here goes using an alternative technique. Sorry in advance with we end up with duplicate messages) Begin forwarded message: From: Tom Van Cutsem tomvc...@gmail.com Date: November 19, 2012 10:04:56 AM PST To: Allen Wirfs-Brock al...@wirfs-brock.com Cc: Mark S. Miller erig...@google.com, Jason Orendorff jorendo...@mozilla.com Subject: Re: possible excessive proxy invariants for Object.keys/etc?? Hi Allen, 2012/11/18 Allen Wirfs-Brock al...@wirfs-brock.com The proxy spec.for Object.getOwnPropertyNames/kets/etc. seem to be doing quite a bit more than this. They 1) always copy the array returned from the trap? Why is this necessary? Sure the author of a trap should probably always return a fresh object but not doing so doesn't violate the integrity of the frozen/sealed invariants? In most cases they will provide a fresh object and copying adds unnecessary work that is proportional to the number of names to every such call. The copying is to ensure: a) that the result is an Array b) that all the elements of the result are Strings c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). c) on the other hand is crucial for the non-configurability/non-extensibility checks mentioned below. It's no use checking some invariants on a data structure if that data structure can later still be mutated. If we don't care about any of a, b and c, then the result array wouldn't need to be copied. 2) ensuring that the list of property keys contains no duplicates. Why is this essential? Again, I don't see what it has to do with the integrity of the frozen/sealed invariants. It is extra and probably unnecessary work that is at least proportional to the number of names). We've been going back and forth over whether or not we wanted to prevent duplicates. I remember Andreas Gal being concerned about these kinds of issues (that was when he was doing the first Firefox prototype for old proxies, in the context of the enumerate() trap, which was called during a live for-in loop. IIRC, Firefox already did de-dupe checks, as properties already enumerated in a child object should not be re-visited when visiting a parent object) More recently, at the last TC39 meeting in Boston, we decided to change the return type of the enumerate() trap from Array[String] to Iterator, and in doing so waiving the duplicate properties check. Quoting from the Open issues section of http://wiki.ecmascript.org/doku.php?id=harmony:direct_proxies#open_issues: Enumerate trap signature: consider making the enumerate() trap return an iterator rather than an array of strings. To retain the benefits of an iterator (no need to store collection in memory), we might need to waive the duplicate properties check. Resolution: accepted (duplicate properties check is waived in favor of iterator return type) I guess if duplicate properties are not crucial for enumeration, they're also not crucial for Object.getOwnPropertyNames and Object.keys and can be dropped. Mark, can you comment? 3) Every name in the list returned by the trap code is looked up on the target to determine whether or not it exists, even if the target is extensible. Each of those lookup is observable (the target might itself be a proxy) so, according to the algorithm they all must be performed. This is where we get into actual checks required to enforce non-configurability/non-extensibility. Granted, the ES5 spec is not clear about the invariants on getOwnPropertyNames and keys. The currently specified invariants are a common-sense extrapolation of the existing invariants to cover these operations. In practice, it determines the degree of confidence that a programmer can have in Object.getOwnPropertyNames and friends when dealing with a frozen object. If we waive these invariant checks, then the result of those operations can never be trusted on to reliably introspect on a frozen object's list of property names: Object.isFrozen(proxy) // true Object.getOwnPropertyNames(proxy) // ['foo'] Object.getOwnPropertyNames(proxy) // [ ] Here, the 'foo' property apparently disappeared on a frozen object. If neither the for-in loop nor Object.getOwnPropertyNames nor Object.keys can reliably report an object's own properties, then we've made it impossible to reliably traverse and inspect a presumably deep-frozen object graph. 4) Every own property of the target, is observably looked up (possibly a second time) even if the object is extensible and has no non-configurable properties. We may
Fwd: possible excessive proxy invariants for Object.keys/etc??
Begin forwarded message: From: Allen Wirfs-Brock al...@wirfs-brock.com Date: November 19, 2012 2:11:52 PM PST To: Tom Van Cutsem tomvc...@gmail.com Cc: Mark S. Miller erig...@google.com, Jason Orendorff jorendo...@mozilla.com Subject: Re: possible excessive proxy invariants for Object.keys/etc?? On Nov 19, 2012, at 10:04 AM, Tom Van Cutsem wrote: Hi Allen, 2012/11/18 Allen Wirfs-Brock al...@wirfs-brock.com The proxy spec.for Object.getOwnPropertyNames/kets/etc. seem to be doing quite a bit more than this. They 1) always copy the array returned from the trap? Why is this necessary? Sure the author of a trap should probably always return a fresh object but not doing so doesn't violate the integrity of the frozen/sealed invariants? In most cases they will provide a fresh object and copying adds unnecessary work that is proportional to the number of names to every such call. The copying is to ensure: a) that the result is an Array Why is Array-ness essential? It was what ES5 specified, but ES5 had a fixed implementations of Object.getOwnPropertyNames/keys so that requirement was simply a reflection of what the spec's algorithms actually did. Most places in the specification that consume an array only require array-like-ness, not an actual array. Now that we're make the implementation of the getOPN/keys extensible, it would be natural to relax the requirement that they produce a [[Class]]===Array object. b) that all the elements of the result are Strings And presumably Symbols. We have to also accommodate Symbols, at least for getOwnPropetyNames. Regardless, why is this important? More below... c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). Yes, premature type coercion, in my opinion. Also, a classic performance mistakes made by dynamic language programmers: unnecessary coercion of values that are never going to be access or redundant coercion checks of values that are already of the property type. Why is it important to do such checks on values that are are just passing through these traps. When and if somebody actually gets arounds to using one of the elements that are returned as a property key they will be automatically coerced to a valid value. Why is it important that it happens any sooner than that. getOwnPropertyDescriptor is also on my list to talk to you about for similar reasons. I don't see why the property descriptor needs to be normalized and regenerated (including converting accessor properties on the descriptor into data properties and requiring the [[Prototype]] to be object. I have an alternative for [[GetOwnProperty]]/TrapGetOwnPropertyDescriptor that preserves the original object returned from the trap (while normalizing to a descriptor record for any internal calls on ordinary objects). This preserves any exotic property descriptor object properties as is. This seems like exactly the right things. Such exotic property descriptor properties can only be meaningfully used by other proxy traps or application/library code that knows about them. I don't see any need to normalize them into data properties or otherwise muck with what the actually implementor of the getOwnPropertyDescrptor trap choose to return. I this alternative implementation is, again, much simpler (and efficient) with fewer externally observable calls in its algorithm steps. I sure you will want to review it so I will include it in the spec. draft. c) on the other hand is crucial for the non-configurability/non-extensibility checks mentioned below. It's no use checking some invariants on a data structure if that data structure can later still be mutated. I don't see how an extra copy of the the result array makes any difference in this regard (also see below). The array returned from Object.getOwnPropertyNames is still mutable even after you make a copy. It's only as stable as any other non-frozen object you are likely to encounter. If we don't care about any of a, b and c, then the result array wouldn't need to be copied. yes, I believe that should be the case. 2) ensuring that the list of property keys contains no duplicates. Why is this essential? Again, I don't see what it has to do with the integrity of the frozen/sealed invariants. It is extra and probably unnecessary work that is at least proportional to the number of names). We've been going back and forth over whether or not we wanted to prevent duplicates. I remember Andreas Gal being concerned about these kinds of issues (that was when he was doing the first Firefox prototype for old proxies, in the context
Fwd: possible excessive proxy invariants for Object.keys/etc??
Begin forwarded message: From: Tom Van Cutsem tomvc...@gmail.com Date: November 20, 2012 11:36:24 AM PST To: Allen Wirfs-Brock al...@wirfs-brock.com Cc: Mark S. Miller erig...@google.com, Jason Orendorff jorendo...@mozilla.com Subject: Re: possible excessive proxy invariants for Object.keys/etc?? 2012/11/19 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 19, 2012, at 10:04 AM, Tom Van Cutsem wrote: The copying is to ensure: a) that the result is an Array Why is Array-ness essential? It was what ES5 specified, but ES5 had a fixed implementations of Object.getOwnPropertyNames/keys so that requirement was simply a reflection of what the spec's algorithms actually did. Most places in the specification that consume an array only require array-like-ness, not an actual array. Now that we're make the implementation of the getOPN/keys extensible, it would be natural to relax the requirement that they produce a [[Class]]===Array object. It's all a matter of developer expectations and how much leeway we have in changing the return type of existing built-ins. In theory, it's a backwards-incompatible change. In practice, it may not matter. b) that all the elements of the result are Strings And presumably Symbols. We have to also accommodate Symbols, at least for getOwnPropetyNames. Regardless, why is this important? More below... Same argument as above. I recall there was some concern about symbols showing up in existing reflection methods like Object.getOwnPropertyNames. Not sure how that got resolved. It's basically touching upon the same issue of whether we can change the return type of existing methods. c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). Yes, premature type coercion, in my opinion. Also, a classic performance mistakes made by dynamic language programmers: unnecessary coercion of values that are never going to be access or redundant coercion checks of values that are already of the property type. Why is it important to do such checks on values that are are just passing through these traps. When and if somebody actually gets arounds to using one of the elements that are returned as a property key they will be automatically coerced to a valid value. Why is it important that it happens any sooner than that. I don't know if you are familiar with the work of Felleisen et al. on higher-order contracts. In that work, they use the notion of blame between different components/modules. Basically: if some module A receives some data from a module B, and B provides wrong data, then B should be assigned blame. You don't want to end up in a situation where A receives the blame at the point where it passes that wrong data into another module C. The early type coercions at the exit of the traps can be thought of in these terms: the invariant checks will always blame the proxy handler for producing wrong data, at the point where the wrong data is produced and before it's exposed to client code. Clients are expecting ES5/6 objects to obey an implicit contract. getOwnPropertyDescriptor is also on my list to talk to you about for similar reasons. I don't see why the property descriptor needs to be normalized and regenerated (including converting accessor properties on the descriptor into data properties and requiring the [[Prototype]] to be object. I have an alternative for [[GetOwnProperty]]/TrapGetOwnPropertyDescriptor that preserves the original object returned from the trap (while normalizing to a descriptor record for any internal calls on ordinary objects). This preserves any exotic property descriptor object properties as is. This seems like exactly the right things. Such exotic property descriptor properties can only be meaningfully used by other proxy traps or application/library code that knows about them. I don't see any need to normalize them into data properties or otherwise muck with what the actually implementor of the getOwnPropertyDescrptor trap choose to return. I this alternative implementation is, again, much simpler (and efficient) with fewer externally observable calls in its algorithm steps. I sure you will want to review it so I will include it in the spec. draft. It's all a matter of how much invariants we care to give up. I think it's risky to allow descriptors to be returned whose attributes may be accessors, thus potentially fooling much existing ES5 code that often does simple tests like: if (!desc.writable !desc.configurable) { var val = desc.value; // we can assume the property is immutable, so we can cache its value ... } You can no longer rely
Re: possible excessive proxy invariants for Object.keys/etc??
In regards to the non-configurable/extensible issue, the issue is that the proxy still needs to be *notified* of what's happening, but it's not really allowed to *trap* because the result is predetermined. Currently this is handled by treating it like a normal trap activation with extra limitations on the return result. Skipping the trap entirely and returning the result is undesirable because the notification is still important even when the result can't be influenced. The ideal result would be for the the trap to be called normally but as a notification rather than as a request for something to happen/a return value. That way a handler that needs side effects to happen can still make sure they do, but there's no need to conjure up descriptors or reflect actions where the result is predetermined. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: possible excessive proxy invariants for Object.keys/etc??
and my first reply, direct to es-discuss... On Nov 20, 2012, at 2:32 PM, Allen Wirfs-Brock wrote: Begin forwarded message: From: Tom Van Cutsem tomvc...@gmail.com Date: November 20, 2012 11:36:24 AM PST To: Allen Wirfs-Brock al...@wirfs-brock.com Cc: Mark S. Miller erig...@google.com, Jason Orendorff jorendo...@mozilla.com Subject: Re: possible excessive proxy invariants for Object.keys/etc?? 2012/11/19 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 19, 2012, at 10:04 AM, Tom Van Cutsem wrote: The copying is to ensure: a) that the result is an Array Why is Array-ness essential? It was what ES5 specified, but ES5 had a fixed implementations of Object.getOwnPropertyNames/keys so that requirement was simply a reflection of what the spec's algorithms actually did. Most places in the specification that consume an array only require array-like-ness, not an actual array. Now that we're make the implementation of the getOPN/keys extensible, it would be natural to relax the requirement that they produce a [[Class]]===Array object. It's all a matter of developer expectations and how much leeway we have in changing the return type of existing built-ins. In theory, it's a backwards-incompatible change. In practice, it may not matter. It can't break existing code that doesn't use Proxies. Plus, the default behavior is still to return a built-in Array instance. For existing code to break it would have to: 1) be used in an environment where proxies existed and were being used 2) it would have to actually be reflecting upon such proxies 3) The proxy would have to return something other than a built-in array instance from the getOwnPropertyNames/keys trap. 4) The code would actually have to be dependent upon the built-in array-ness (Array.isArray check, dependency upon the length invariant, etc.) of such such a returned object. It all seems quite unlikely to cause any compat issues for existing and probably worth the risk. Particularly if the alternative is to add an otherwise unnecessary array copying to every such proxy trap call. b) that all the elements of the result are Strings And presumably Symbols. We have to also accommodate Symbols, at least for getOwnPropetyNames. Regardless, why is this important? More below... Same argument as above. I recall there was some concern about symbols showing up in existing reflection methods like Object.getOwnPropertyNames. Not sure how that got resolved. It's basically touching upon the same issue of whether we can change the return type of existing methods. I'm assuming we are excluding symbols from [[Enumerate]] and [[Keys]] so it is only [[GetOwnPropertyNames]] that have this concern. One way or another we have to be able to reflectively get a list of symbol keyed properties. We can either: 1) include them in the Object.getOwnPropertyNames result. 2) Consider getOwnPropertyNames depreciated (it actually lives forever) and replace it with a new Object.getOwnPropertyKeys that includes non-private Symbol keys 3) Keep getOwnPropertyNames as is and add Object.getOwnPropertySymbols that only returns the non-private-Symbol keys. 1) has the greatest backward compat concerns, but is the simplest to provide and for ES programmers to deal with going forward. 2) Eliminates the compat risk but creates perpetual potential for a: used gOPNames when I should have used gOPK hazard. 3) Eliminates the compat risk but means everybody who wants to deal with all own properties have to deal with two lists of keys. They also loose relative insertion order info relating key/symbol property keys. Either 2 or 3 could require widening the MOP which increases the possibility for incomplete, inconsistent, or otherwise buggy proxies. Even if we expose multiple public functions like either 2 or 3 I would still prefer to only have a single getOwnProperyyKeys internal method/trap that can be filtered to provide just strings and/or symbols. Overall, I'm more concerned about keeping the internal method/trap MOP width as narrow as possible than I am about the size of the public Object.* API c) to ensure the stability of the result. You can think of a + b as implementing a type coercion of the trap result to Array of String. This coercion is not too dissimilar from what the getOwnPropertyDescriptor has to do (normalization of the returned property descriptor by creating a fresh copy). Yes, premature type coercion, in my opinion. Also, a classic performance mistakes made by dynamic language programmers: unnecessary coercion of values that are never going to be access or redundant coercion checks of values that are already of the property type. Why is it important to do such checks on values that are are just passing through these traps. When and if somebody actually gets arounds to using one of the elements that are returned as a property key they will be automatically
Re: possible excessive proxy invariants for Object.keys/etc??
On Nov 20, 2012, at 3:16 PM, Brandon Benvie wrote: In regards to the non-configurable/extensible issue, the issue is that the proxy still needs to be *notified* of what's happening, but it's not really allowed to *trap* because the result is predetermined. Currently this is handled by treating it like a normal trap activation with extra limitations on the return result. Skipping the trap entirely and returning the result is undesirable because the notification is still important even when the result can't be influenced. The ideal result would be for the the trap to be called normally but as a notification rather than as a request for something to happen/a return value. That way a handler that needs side effects to happen can still make sure they do, but there's no need to conjure up descriptors or reflect actions where the result is predetermined. right, I just made what I think is an equivalent suggestion in a new replay to Tom's last message. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss