Re: [Proxies] Refactoring prototype climbing in the spec
2011/11/10 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 10, 2011, at 9:03 AM, Tom Van Cutsem wrote: Regarding property deletion: if an object is implemented as a proxy, and you would want to delete a property from that object, I'm not sure why you would want to circumvent triggering the delete trap? In http://wiki.ecmascript.org/doku.php?id=strawman:object_model_reformation I'm exploring explicitly distinguishing the semants of obj.propName and obj[expression] such that an object can explicitly define its obj[expression] behaviors without using a Proxy (note that currently by the time a Proxy is called, the distinction is already lost so even if you wanted to use a Proxy for this purpose they may not be adequate). In many cases (including the default), the object-specific handler for obj[expression] will delegate back to Object.getProperty/Object.setProperty (either directly or via a super call). But note that it can do so via direct [ ] syntax, as that would loop. This can actually all be modeled with a new Reference variant within the spec. that distinguishes . references from [ ] references. However, I also need to account for the delete operator and distinguish delete obj.propName from delete obj[expression] . This can be taken care of by the same reference extension (essentially References needs a delete method). A handler for deleting obj[expression]] may also want to delegate back to normal property deletion and for the same reason as for obj[exper] get/set handlers it can't directly use the syntactic form. Instead it needs to call an Object.deleteProperty function. Understood. BTW, rather than adding these methods to Object, I'm beginning to think it might be better to import them from a built-in reflection module. Indeed. Perhaps that built-in reflection module could be the same module that defines the default forwarding handler. Your note that we need an Object.deleteProperty method apart from the built-in syntax reminded me that the default forwarding handler for proxies is really all about providing method-based alternatives to things that can currently only be achieved through built-in syntax. In the direct proxies proposal, I proposed defining a Proxy.forward object that enables forwarding all operations interceptable by proxies to another object. A prototype implementation of it exists here: http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/DirectProxies.js#1250 If you observe closely, you'll note that the default forwarding API is really the dual of the Proxy API. Proxies uniformly turn all sorts of operations on objects (whether triggered through syntax or built-in methods) into trap invocations, for instance: Object.getOwnPropertyDescriptor(proxy, name) = handler.getOwnPropertyDescriptor(name, target) name in proxy = handler.has(name, target) etc. The default forwarding API turns these trap invocations back into the original operations: Proxy.forward.getOwnPropertyDescriptor(name, target) = Object.getOwnPropertyDescriptor(target, name); Proxy.forward.has(name, target) = name in target etc. That's why the forwarding API is crucial for double lifting to work: if a handler is itself a proxy, then to intercept handler[trapName], we need to be able to generically forward the operation, which is made possible by writing Proxy.forward[trapName]. I proposed the name Proxy.forward since that looks very natural when writing handler code that simply wants to augment the default behavior: funtion makeChangeLogger(target) { return Proxy.for(target, { set: function(receiver, name, value, target) { log('property '+name+' on '+target+' set to '+value); return Proxy.forward.set(receiver, name, value, target); } });} However, it now seems to me that the methods encapsulated by Proxy.forward can be more generally useful for arbitrary meta-programming, not just for forwarding in the case of proxies. I see an opportunity here to kill two birds with one stone: we can introduce a reflection module (assume it's called Reflect for now) that contains the Proxy.forward methods. That would: a) obviate the need for Object.deleteProperty (now named Reflect.delete), Object.getProperty (Reflect.get) and Object.setProperty (Reflect.set). b) obviate the need for Proxy.forward (the above code snippet would call Reflect.set instead, which is even shorter) The design guideline for this Reflect module would be that the methods it exposes have _exactly_ the same name + parameter list as the Proxy API trap names. Not only does this retain consistency, it also enables easy double lifting (or perhaps other design patterns that want to treat operations on objects / Proxy traps generically). Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
2011/11/9 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 9, 2011, at 3:53 AM, Tom Van Cutsem wrote: - lots of methods on Array.prototype (map, forEach, filter, some, reduce, reduceRight, reverse, splice, indexOf, lastIndexOf, every, shift, slice, sort, unshift): the only way in which these trigger proxy code is when |this| is a proxy, i.e. when they are called as Array.prototype.theMethod.call(aProxy). (calling aProxy.theMethod triggers the proxy's get trap) - Array.prototype.concat: [[HasProperty]] is called on a built-in Array only. the array methods are my primary concern as the HasProperty/Get combination is used to preserve holes as the algorithms iterate over arrays. On large arrays, there can be lots of these calls. However, I'm not sure I understand your comments about theMethod.call(aProxy) vs. aProxy.theMethod(). In either case when theMethod is actually executed the this value must be aProxy and the internal calls to [[HasProperty]] and [[Get]] need to trap. You're right, I forgot the get trap just traps property access, not the full method invocation. The get trap could return a bound method, binding theMethod's |this| to the wrapped array, but that would break inheritance and would forego intercepting the individual array elements accessed by the bound function. - 10.2.1.2 Object environment records uses [[HasProperty]] in a number of places. Isn't this needed only for |with|, so only triggered by |with (aProxy) { ... }|? Since ES.next builds on ES5/strict, this doesn't seem to carry much weight. And for global object bindings. It still has to work, consider for example, a proxy based DOM object that is accessed from non-Harmony code. The global object, it self, might be a Proxy. I see. So IIUC, in such a runtime environment, dereferencing a global variable name would then first trigger the global object's has trap, potentially followed by the get trap. Are you sure you want to kill [[HasProperty]] entirely? If we replace it by a conditional [[Get]], wouldn't that mean that the expression |name in obj| might unnecessarily trigger an accessor in obj? Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
2011/11/9 Allen Wirfs-Brock al...@wirfs-brock.com [[Get]] seems to do nothing but redispatch to [[GetP]] so its definition could be replaced with the body of [[GetP]]. [[Puit] does a redispatch to [[SetP]] followed by a mode based conditional throw depending upon the result of the [[SetP]]. I din't think the the throwing behavior needs (or even should) be over-ride able by a proxy (or even by an alternative internal implementation). I would factor the conditional throw code out of [[Put]] and make it the responsibility of the caller. In practice, this just means that I would define a new Abstraction Operation that is used in place of direct calls to [[Put]]. The abstraction operation wold essentially have the definition you provide for [[Put]] except that it would call [[Put]] instead of [[SetP]] and the definition of [[Put]] would be replaced with the body of [[SetP]] Agreed. So if I understood all of that correctly, we would have: - Abstract [[GetProperty]] and [[SetProperty]] operations (the methods I called [[GetP]] and [[SetP]]) - Object.getProperty and Object.setProperty built-ins that call [[GetProperty]] and [[SetProperty]] - [[Get]] and [[Set]] methods on Objects that call [[GetProperty]] and [[SetProperty]] - [[Get]] and [[Set]] methods on Proxies that trigger get and set traps - An abstract operation [[Put]](O, P, V, Throw) that calls O.[[Set]](O,P,V) and performs the strict mode reject behavior. Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Internal and external object APIs (was: [Proxies] Refactoring prototype climbing in the spec)
Le 10/11/2011 12:28, David Bruant a écrit : (...) but i think [traps] should be a right balance between the internal and external API. Internal object API: * getPropertyDescriptor(propName) * setPropertyDescriptor(propName, desc) (more or less equivalent to the current [[DefineOwnProperty]]) * delete(name) * getPropertyNames() * makeNonExtensible() * getPrototype() * getDefaultValue(hint) * getClass() I would claim that anything done in the ES5.1 spec with objects (functions aside for now) could be expressed as a combinaison of this minimal internal object API. Some choices are intentional (makeNonExtensible does not have a makeExtensible counterpart, get{Prototype|Class} not having a set counterpart...). Also, I do not write getOwnPropertyNames, because as far as an ECMAScript object is concerned, there is no reason to do something else than adding an own property. Oh yes, there is this other object you can have a reference to by calling getPrototype, but there is no reason to act on this one directly internally. I'd like to emphasis that this minimal API defines objects as a mapping of name - property descriptors. External object API: ** Current * Object.getOwnPropertyDescriptor(o, name) * Object.getOwnPropertyNames(o) * Object.defineProperty(o, name, pd) * delete o.name * Object.freeze(o) * Object.seal(o) * Object.preventExtensions(o) * name in o * ({}).hasOwnProperty.call(o, name) * o.name * o.name = val * for (name in o) * Object.keys(o) ** Upcoming * for(v of o) * (I'm probably forgetting some) Allen wrote: More generally, I think there should be a 1::1 correspondence between the internal methods in listed in ES5 Table 8 and fundamental proxy traps. If by fundamental, you were refering to something like the minimal internal API I described above, once again, I disagree, but for different reasons that before. First, I think that there is an agreement to not trap some things like getPrototype, getClass or getDefaultValue. Then, I think that if some traps (like set or get) exist, it's because it's a bit more convenient to reason on object interaction from the syntax perspective than the fundamental internal API. Actually, proxies initial design rather makes a correspondance with syntax [1]. Regarding ES5 invariants, these were expressed in terms of internal API opening the door to host object to do whatever they want in their [[Get]] and [[Put]] internal methods. I'm glad to see invariants being defined more closely to the syntax view of objects in the direct proxies proposal [2] David [1] http://wiki.ecmascript.org/doku.php?id=harmony:proxies#api [2] http://wiki.ecmascript.org/doku.php?id=strawman:direct_proxies#invariant_enforcement ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
On Nov 10, 2011, at 1:36 AM, Tom Van Cutsem wrote: 2011/11/9 Allen Wirfs-Brock al...@wirfs-brock.com [[Get]] seems to do nothing but redispatch to [[GetP]] so its definition could be replaced with the body of [[GetP]]. [[Puit] does a redispatch to [[SetP]] followed by a mode based conditional throw depending upon the result of the [[SetP]]. I din't think the the throwing behavior needs (or even should) be over-ride able by a proxy (or even by an alternative internal implementation). I would factor the conditional throw code out of [[Put]] and make it the responsibility of the caller. In practice, this just means that I would define a new Abstraction Operation that is used in place of direct calls to [[Put]]. The abstraction operation wold essentially have the definition you provide for [[Put]] except that it would call [[Put]] instead of [[SetP]] and the definition of [[Put]] would be replaced with the body of [[SetP]] Agreed. So if I understood all of that correctly, we would have: - Abstract [[GetProperty]] and [[SetProperty]] operations (the methods I called [[GetP]] and [[SetP]]) Yes, current spec. conventions would just call these GetProperty and SetProperty abstract operations. - Object.getProperty and Object.setProperty built-ins that call [[GetProperty]] and [[SetProperty]] Also, to fully reify property manipulation I think we also need to factor [[Delete]] in a similar manner and provide an Object.deleteProperty built-in that calls the DeleteProperty abstraction operations. I touch upon this in http://wiki.ecmascript.org/doku.php?id=strawman:object_model_reformation#a_side_note_on_reflective_property_access (note that this stawman is still under construction) the basic reason is that just like with Get/Put a handler may want to invoke the primitive behavior of Delete upon the target object without going through the delete operator (which could retrigger a trap). - [[Get]] and [[Set]] methods on Objects that call [[GetProperty]] and [[SetProperty]] - [[Get]] and [[Set]] methods on Proxies that trigger get and set traps Yes, although I prefer to think of [[Get]] and [[Set]] consistently for both Proxy and normal objects. They are polymorphic traps that dispatch to a handler that is determined by the nature of the object. For native objects [[Get]] and [[Set]] dispatch to handlers that just call GetProperty or SetProperty - An abstract operation [[Put]](O, P, V, Throw) that calls O.[[Set]](O,P,V) and performs the strict mode reject behavior. Yes, call it Put. Allen___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
On Nov 10, 2011, at 1:26 AM, Tom Van Cutsem wrote: Are you sure you want to kill [[HasProperty]] entirely? If we replace it by a conditional [[Get]], wouldn't that mean that the expression |name in obj| might unnecessarily trigger an accessor in obj? Good point, I didn't think about the fact that [[Get]] always triggers accessor calls. We probably do need to continue to have a side-effect free [[HasProperty]]. I still think it is desirable to have a conditional [[Get]] available for use in places like the array algorithms. Whether it is an over-roading of the [[Get]] api or an additional trap is debatable. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
2011/11/10 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 10, 2011, at 1:36 AM, Tom Van Cutsem wrote: - Object.getProperty and Object.setProperty built-ins that call [[GetProperty]] and [[SetProperty]] Also, to fully reify property manipulation I think we also need to factor [[Delete]] in a similar manner and provide an Object.deleteProperty built-in that calls the DeleteProperty abstraction operations. I touch upon this in http://wiki.ecmascript.org/doku.php?id=strawman:object_model_reformation#a_side_note_on_reflective_property_access (note that this stawman is still under construction) the basic reason is that just like with Get/Put a handler may want to invoke the primitive behavior of Delete upon the target object without going through the delete operator (which could retrigger a trap). Actually I was mistaken: the above quoted line should read: - Object.getProperty and Object.setProperty built-ins that call the [[Get]] and [[Set]] methods In other words: it's important that Object.getProperty(aProxy, name) triggers that proxy's get trap rather than performing the Object algorithm and calling the getOwnPropertyDescriptor trap. Otherwise, proxies would not compose well (think of a proxy p1 wrapping another proxy p2: if p1 forwards a property access to its target (p2), p2's get trap should be triggered). (+ we could end up with duplicated getOwnPropertyDescriptor calls, as pointed out by David earlier) If the goal is to intentionally avoid proxy traps, one can write up the algorithm in Javascript itself (as I have done on the strawman page). But I think it's important that Object.getProperty does trigger proxy traps (if only for consistency: Object.getOwnPropertyDescriptor et al. also trigger proxy traps). Regarding property deletion: if an object is implemented as a proxy, and you would want to delete a property from that object, I'm not sure why you would want to circumvent triggering the delete trap? - [[Get]] and [[Set]] methods on Objects that call [[GetProperty]] and [[SetProperty]] - [[Get]] and [[Set]] methods on Proxies that trigger get and set traps Yes, although I prefer to think of [[Get]] and [[Set]] consistently for both Proxy and normal objects. They are polymorphic traps that dispatch to a handler that is determined by the nature of the object. For native objects [[Get]] and [[Set]] dispatch to handlers that just call GetProperty or SetProperty Do you mean that the direction you would like to go is to look at normal ES5 objects as proxies with a special, built-in handler? Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
On Nov 10, 2011, at 3:28 AM, David Bruant wrote: Le 09/11/2011 12:17, Tom Van Cutsem a écrit : 2011/11/8 Allen Wirfs-Brock al...@wirfs-brock.com More generally, I think there should be a 1::1 correspondence between the internal methods in listed in ES5 Table 8 and fundamental proxy traps. I absolutely agree we should strive to achieve this. I disagree. First of all, Table 8 is incomplete: * It lacks a function for property enumeration and proxies need a trap for that (for-in loops, Object.getOwnPropertyNames, Object.keys). * There is no method for passing [[Extensible]] from true to false, though this behavior is used and needs a (fundamental) trap as well. The reason for this is certainly that this action can be described in one spec algorithm step, so there was no need from a specification perspective to consider this as an internal method, but it's still a fundamental operation you can perform on an object. (I may be missing others) I agree that Table 8 is incomplete (and has unnecessary items in it). The point is that Table 8 should define the fundamental Object semantic API that is used by all clients, including the specifications of language feature semantics, the specification of built-in functions, and the semantics that are reified via the Proxy and Object reflection API. One consistent set of object semantics operations, used by everybody. A second point I disagree on is the idea of fundamental trap. This notion existed on the original proxy proposal. I think it was more for the purpose of writing traps more easily. With the direct proxies proposal, the notion of fundamental and derived traps disappears (and hopefully, direct proxies will be promoted during the next TC-39 meeting). I was using fundamental to mean what i just stated above. I agree that only these fundamental object operations need to be reflected in the Proxy API. Regardless of Table 8 being incomplete, I think that proxy traps have another factor to take into account which is the relationship with surface syntax. When someone writes let a = o.a;, the intention is to call the get operation regardless of whether the a property is on o or somewhere on its prototype chain. This is another thing that I like in the [[Get]] refactoring: when the prototype is the proxy, its get trap gets called and not its getOwnPropertyDescriptor trap. It makes proxy traps more in line with the object clients requests. The surface syntax perspective is exactly how I conceptualize the Table 8 level of the specification. It is the interface between the runtime object model and the surface features of the language. However, there were some practical engineering considerations that went into the ES5 factoring of the internal methods. For example, [[Put]] calls [[DefineOwnProperty]] to set a property's value because it made it easier to specify Array behavior. Creating an array indexed property (possibly via [[Put]]) and letting the value of the length property both have side-effects. There are multiple ways that such values can be set, including using Object.defineProperty. By making the default implementation of [[Put]] delegate to [[DefineOwnProperty]] I only needed to over-ride [[DefineOwnProperty]] for array instances to specify their semantics. If I hand't done this, I would have had to consistently over-ridden both [[DefineOwnProperty]] and [[Put]]. As long, as the internal methods were just specification devices, this sort of refactoring could be done as a convenience. But as soon as the reify them via traps we have to thing about the broader and permanent impacts of such refactoring. Allen___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
On Nov 10, 2011, at 9:03 AM, Tom Van Cutsem wrote: 2011/11/10 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 10, 2011, at 1:36 AM, Tom Van Cutsem wrote: - Object.getProperty and Object.setProperty built-ins that call [[GetProperty]] and [[SetProperty]] Also, to fully reify property manipulation I think we also need to factor [[Delete]] in a similar manner and provide an Object.deleteProperty built-in that calls the DeleteProperty abstraction operations. I touch upon this in http://wiki.ecmascript.org/doku.php?id=strawman:object_model_reformation#a_side_note_on_reflective_property_access (note that this stawman is still under construction) the basic reason is that just like with Get/Put a handler may want to invoke the primitive behavior of Delete upon the target object without going through the delete operator (which could retrigger a trap). Actually I was mistaken: the above quoted line should read: - Object.getProperty and Object.setProperty built-ins that call the [[Get]] and [[Set]] methods In other words: it's important that Object.getProperty(aProxy, name) triggers that proxy's get trap rather than performing the Object algorithm and calling the getOwnPropertyDescriptor trap. Otherwise, proxies would not compose well (think of a proxy p1 wrapping another proxy p2: if p1 forwards a property access to its target (p2), p2's get trap should be triggered). (+ we could end up with duplicated getOwnPropertyDescriptor calls, as pointed out by David earlier) If the goal is to intentionally avoid proxy traps, one can write up the algorithm in Javascript itself (as I have done on the strawman page). But I think it's important that Object.getProperty does trigger proxy traps (if only for consistency: Object.getOwnPropertyDescriptor et al. also trigger proxy traps). Yes, on further thought, I agree that Object getProperty should trigger the trap. Regarding property deletion: if an object is implemented as a proxy, and you would want to delete a property from that object, I'm not sure why you would want to circumvent triggering the delete trap? In http://wiki.ecmascript.org/doku.php?id=strawman:object_model_reformation I'm exploring explicitly distinguishing the semants of obj.propName and obj[expression] such that an object can explicitly define its obj[expression] behaviors without using a Proxy (note that currently by the time a Proxy is called, the distinction is already lost so even if you wanted to use a Proxy for this purpose they may not be adequate). In many cases (including the default), the object-specific handler for obj[expression] will delegate back to Object.getProperty/Object.setProperty (either directly or via a super call). But note that it can do so via direct [ ] syntax, as that would loop. This can actually all be modeled with a new Reference variant within the spec. that distinguishes . references from [ ] references. However, I also need to account for the delete operator and distinguish delete obj.propName from delete obj[expression] . This can be taken care of by the same reference extension (essentially References needs a delete method). A handler for deleting obj[expression]] may also want to delegate back to normal property deletion and for the same reason as for obj[exper] get/set handlers it can't directly use the syntactic form. Instead it needs to call an Object.deleteProperty function. BTW, rather than adding these methods to Object, I'm beginning to think it might be better to import them from a built-in reflection module. - [[Get]] and [[Set]] methods on Objects that call [[GetProperty]] and [[SetProperty]] - [[Get]] and [[Set]] methods on Proxies that trigger get and set traps Yes, although I prefer to think of [[Get]] and [[Set]] consistently for both Proxy and normal objects. They are polymorphic traps that dispatch to a handler that is determined by the nature of the object. For native objects [[Get]] and [[Set]] dispatch to handlers that just call GetProperty or SetProperty Do you mean that the direction you would like to go is to look at normal ES5 objects as proxies with a special, built-in handler? Yes, exactly. BTW, I think this was probably the intent of the originally internal methods and native+host objects design. allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
2011/11/8 Allen Wirfs-Brock al...@wirfs-brock.com I don't think that [[GetP]] and [[PutP]] need to be internal methods In spec'ing this I think I would make them be abstract operations. Internal methods are extensions points that can be over-ridden on a per object basis. That is what [[Get]] and [[Put]] provides. GetP and SetP define fixed operations. I proposed [[GetP]] and [[SetP]] as internal methods because Proxies would inherit the built-in behavior for [[Get]] and [[Put]], but override the [[GetP]] and [[SetP]] operations. If [[GetP]] and [[SetP]] become abstract operations, won't they need to explicitly dispatch? Proxies could override [[Get]] and [[Put]], but that would require them to duplicate the Object [[Get]] and [[Put]] code. More generally, I think there should be a 1::1 correspondence between the internal methods in listed in ES5 Table 8 and fundamental proxy traps. I absolutely agree we should strive to achieve this. Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
2011/11/8 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 8, 2011, at 7:33 AM, Andreas Rossberg wrote: But I have a follow-up request. :) Regarding redundant trap calls with proxies there is another, more pervasive problem with the current spec: in lots of places it first calls [[HasProperty]] and then [[Get]]. With proxies, this always implies two trap calls, which seems wasteful. Would it be possible to refactor that, too? Seems more difficult, because we would need to enable [[Get]] (and hence the get trap) to signal lookup failure. (Too bad that we cannot reuse `undefined' for it.) But I think the current situation isn't satisfactory. I agree, the current specification style is fine when we are dealing with side-effectly free internal operations but as soon as they are reified they become problematic and a performance issue. Is the situation really that problematic? Skimming the ES5.1 spec for [[HasProperty]], I encounter calls in the following places: - ToPropertyDescriptor conversion (only triggers proxy code via Object.defineProperty(obj, name, aProxyAsDescriptor) and Object.defineProperties). - lots of methods on Array.prototype (map, forEach, filter, some, reduce, reduceRight, reverse, splice, indexOf, lastIndexOf, every, shift, slice, sort, unshift): the only way in which these trigger proxy code is when |this| is a proxy, i.e. when they are called as Array.prototype.theMethod.call(aProxy). (calling aProxy.theMethod triggers the proxy's get trap) - Array.prototype.concat: [[HasProperty]] is called on a built-in Array only. - 10.2.1.2 Object environment records uses [[HasProperty]] in a number of places. Isn't this needed only for |with|, so only triggered by |with (aProxy) { ... }|? Since ES.next builds on ES5/strict, this doesn't seem to carry much weight. - The in operator: only calls [[HasProperty]], not followed by a call to [[Get]] (so it's not an instance of the conditional [[Get]] pattern). None of the conditional [[Get]] patterns on proxies seem particularly common operations, even if proxies would be widely used. Are they worth the complexity of changing one of the most critical and common operations in Javascript? Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
On Nov 9, 2011, at 3:53 AM, Tom Van Cutsem wrote: 2011/11/8 Allen Wirfs-Brock al...@wirfs-brock.com On Nov 8, 2011, at 7:33 AM, Andreas Rossberg wrote: But I have a follow-up request. :) Regarding redundant trap calls with proxies there is another, more pervasive problem with the current spec: in lots of places it first calls [[HasProperty]] and then [[Get]]. With proxies, this always implies two trap calls, which seems wasteful. Would it be possible to refactor that, too? Seems more difficult, because we would need to enable [[Get]] (and hence the get trap) to signal lookup failure. (Too bad that we cannot reuse `undefined' for it.) But I think the current situation isn't satisfactory. I agree, the current specification style is fine when we are dealing with side-effectly free internal operations but as soon as they are reified they become problematic and a performance issue. Is the situation really that problematic? Skimming the ES5.1 spec for [[HasProperty]], I encounter calls in the following places: - ToPropertyDescriptor conversion (only triggers proxy code via Object.defineProperty(obj, name, aProxyAsDescriptor) and Object.defineProperties). - lots of methods on Array.prototype (map, forEach, filter, some, reduce, reduceRight, reverse, splice, indexOf, lastIndexOf, every, shift, slice, sort, unshift): the only way in which these trigger proxy code is when |this| is a proxy, i.e. when they are called as Array.prototype.theMethod.call(aProxy). (calling aProxy.theMethod triggers the proxy's get trap) - Array.prototype.concat: [[HasProperty]] is called on a built-in Array only. the array methods are my primary concern as the HasProperty/Get combination is used to preserve holes as the algorithms iterate over arrays. On large arrays, there can be lots of these calls. However, I'm not sure I understand your comments about theMethod.call(aProxy) vs. aProxy.theMethod(). In either case when theMethod is actually executed the this value must be aProxy and the internal calls to [[HasProperty]] and [[Get]] need to trap. - 10.2.1.2 Object environment records uses [[HasProperty]] in a number of places. Isn't this needed only for |with|, so only triggered by |with (aProxy) { ... }|? Since ES.next builds on ES5/strict, this doesn't seem to carry much weight. And for global object bindings. It still has to work, consider for example, a proxy based DOM object that is accessed from non-Harmony code. The global object, it self, might be a Proxy. - The in operator: only calls [[HasProperty]], not followed by a call to [[Get]] (so it's not an instance of the conditional [[Get]] pattern). None of the conditional [[Get]] patterns on proxies seem particularly common operations, even if proxies would be widely used. Are they worth the complexity of changing one of the most critical and common operations in Javascript? The array functions are my primary concern. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
On Nov 9, 2011, at 3:17 AM, Tom Van Cutsem wrote: 2011/11/8 Allen Wirfs-Brock al...@wirfs-brock.com I don't think that [[GetP]] and [[PutP]] need to be internal methods In spec'ing this I think I would make them be abstract operations. Internal methods are extensions points that can be over-ridden on a per object basis. That is what [[Get]] and [[Put]] provides. GetP and SetP define fixed operations. I proposed [[GetP]] and [[SetP]] as internal methods because Proxies would inherit the built-in behavior for [[Get]] and [[Put]], but override the [[GetP]] and [[SetP]] operations. If [[GetP]] and [[SetP]] become abstract operations, won't they need to explicitly dispatch? Proxies could override [[Get]] and [[Put]], but that would require them to duplicate the Object [[Get]] and [[Put]] code. [[Get]] seems to do nothing but redispatch to [[GetP]] so its definition could be replaced with the body of [[GetP]]. [[Puit] does a redispatch to [[SetP]] followed by a mode based conditional throw depending upon the result of the [[SetP]]. I din't think the the throwing behavior needs (or even should) be over-ride able by a proxy (or even by an alternative internal implementation). I would factor the conditional throw code out of [[Put]] and make it the responsibility of the caller. In practice, this just means that I would define a new Abstraction Operation that is used in place of direct calls to [[Put]]. The abstraction operation wold essentially have the definition you provide for [[Put]] except that it would call [[Put]] instead of [[SetP]] and the definition of [[Put]] would be replaced with the body of [[SetP]] Allen___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
I am a big fan of this refactoring. I like the idea that the algorithm transfers full control to the proxy. Also, accepting this strawman would solve my inherited event properties problem because I would have access to the receiver from the get trap and would be able to do the binding I want. Glad to hear that. About [[SetP]]: * It seems to me that we've traded a duplicated [[GetProperty]] call for a duplicated [[GetOwnProperty]] (Step 1 and 5.a) call on the receiver. This could be avoided by storing the property descriptor at the beginning (when O.[[setP]](Receiver, P, V) is called and O === receiver). Step 5.a could be remove by the change. The double [[GetOwnProperty]] invocation on the initial receiver should not be observable: if the initial Receiver of [[SetP]] is a proxy, the proxy's [[SetP]] algorithm (which triggers the set trap) executes instead of this algorithm. So it's only if the initial Receiver is a normal object that this code executes, and then the call to [[GetOwnProperty]] on line 1 will never trigger proxy code. When [[SetP]] is called by evaluating a property assignment expression, Receiver also always denotes a normal non-proxy object. However, with the proposed Object.getProperty built-in one can write: Object.getProperty(aProxy, name, aNormalObject) which would trigger aNormalObject.[[SetP]](aProxy, name), so then Receiver would indeed be a proxy. Line 1 then calls aNormalObject.[[GetOwnProperty]], while line 5.a, if reached, would trigger aProxy.[[GetOwnProperty]]. Long story short: the O variable in the [[SetP]] algorithm always denotes a normal non-proxy object. In general, the Receiver variable can be any object. * If step 5.a is removed, I think that step 5.b.i is useless, because we would have returned from the set when O was the receiver (by step 2.a of [[setP]]) * If step 5.a is removed, then step 5.c is useless, because if the desc had a [[set]], then we would have already returned from one of the substep of step 3 when O was the receiver Why not redefining [[Get]] as what you have defined as [[GetP]] and equivalent for [[Set]] and [[SetP]]? Current 8.12.3 [[Get]] (P) would become [[Get]] (Receiver, P). It would be called with O as initial value for Receiver pretty much everywhere in the spec except within [[Get]] recursive calls. Equivalent for [[Set]]. It would prevent the replacement of 2 Object.* methods by 2 others. The reason I introduced separate [[GetP]] and [[SetP]] methods is simply so that the public API of Objects remained intact. Your suggestion is reasonable: trade proliferation of built-in methods for a small public API change in the spec. I guess this is just a matter of find/replacing all the calls to [[Get]]/[[Put]]. Might even be an opportunity to get rid of put and use set consistently throughout the spec. With the refactoring, on the direct proxy strawman, I don't think we need the proxy argument for the get and set traps anymore. It was here as the receiver, but since we have the receiver itself, the get and set trap can just have the receiver and the target as argument. Good point. I'd wager that in most use cases, knowing that |receiver| is either your own proxy or a descendant of that proxy is sufficient. I can't currently think of a use case where the get/set trap would really want to be able to access the proxy itself, not just the proxy or a descendant. That doesn't mean that use cases for it don't exist, however. I guess if access to the proxy is really needed, it's easy enough to accomplish: var p = Proxy.for(target, handler); handler.proxy = p; Now the proxy is accessible as |this.proxy| in all traps. This may seem like a hassle, but if the use cases for accessing a proxy are sufficiently minor, getting rid of the proxy argument to the get/set traps seems like a good tradeoff. Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
On 7 November 2011 16:54, Tom Van Cutsem tomvc...@gmail.com wrote: I wrote up an initial (but fairly complete) draft of a proposed refactoring of the ES5 [[Get]], [[Put]] and [[HasProperty]] algorithms to change the way in which these algorithms climb the prototype chain: http://wiki.ecmascript.org/doku.php?id=strawman:refactoring_put Looks good, and as far as I can see from a first read, solves the issues we were discussing so far. But I have a follow-up request. :) Regarding redundant trap calls with proxies there is another, more pervasive problem with the current spec: in lots of places it first calls [[HasProperty]] and then [[Get]]. With proxies, this always implies two trap calls, which seems wasteful. Would it be possible to refactor that, too? Seems more difficult, because we would need to enable [[Get]] (and hence the get trap) to signal lookup failure. (Too bad that we cannot reuse `undefined' for it.) But I think the current situation isn't satisfactory. /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
Le 08/11/2011 11:54, Tom Van Cutsem a écrit : About [[SetP]]: * It seems to me that we've traded a duplicated [[GetProperty]] call for a duplicated [[GetOwnProperty]] (Step 1 and 5.a) call on the receiver. This could be avoided by storing the property descriptor at the beginning (when O.[[setP]](Receiver, P, V) is called and O === receiver). Step 5.a could be remove by the change. The double [[GetOwnProperty]] invocation on the initial receiver should not be observable: if the initial Receiver of [[SetP]] is a proxy, the proxy's [[SetP]] algorithm (which triggers the set trap) executes instead of this algorithm. Very true. And even if there is no trap, the default action is to trigger [[Set]] on the target, so no duplicated call. (...) Long story short: the O variable in the [[SetP]] algorithm always denotes a normal non-proxy object. In general, the Receiver variable can be any object. * If step 5.a is removed, I think that step 5.b.i is useless, because we would have returned from the set when O was the receiver (by step 2.a of [[setP]]) * If step 5.a is removed, then step 5.c is useless, because if the desc had a [[set]], then we would have already returned from one of the substep of step 3 when O was the receiver Why not redefining [[Get]] as what you have defined as [[GetP]] and equivalent for [[Set]] and [[SetP]]? Current 8.12.3 [[Get]] (P) would become [[Get]] (Receiver, P). It would be called with O as initial value for Receiver pretty much everywhere in the spec except within [[Get]] recursive calls. Equivalent for [[Set]]. It would prevent the replacement of 2 Object.* methods by 2 others. The reason I introduced separate [[GetP]] and [[SetP]] methods is simply so that the public API of Objects remained intact. Your suggestion is reasonable: trade proliferation of built-in methods for a small public API change in the spec. I guess this is just a matter of find/replacing all the calls to [[Get]]/[[Put]]. Exactly, that's what I had in mind. Might even be an opportunity to get rid of put and use set consistently throughout the spec. I cannot agree more with this idea. Especially with proxies having a set trap and accessor properties having setters, [[Put]] sounds wrong even though that has been the name in ES5, ES3 (and even before?) With the refactoring, on the direct proxy strawman, I don't think we need the proxy argument for the get and set traps anymore. It was here as the receiver, but since we have the receiver itself, the get and set trap can just have the receiver and the target as argument. Good point. I'd wager that in most use cases, knowing that |receiver| is either your own proxy or a descendant of that proxy is sufficient. I can't currently think of a use case where the get/set trap would really want to be able to access the proxy itself, not just the proxy or a descendant.That doesn't mean that use cases for it don't exist, however. Oh true, I thought that proxy and the receiver were the same objects but if the proxy is on the prototype, it is not the case. I guess if access to the proxy is really needed, it's easy enough to accomplish: var p = Proxy.for(target, handler); handler.proxy = p; Indeed. David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
This is all going in a good direction. I only have a few minor comments: I don't think that [[GetP]] and [[PutP]] need to be internal methods In spec'ing this I think I would make them be abstract operations. Internal methods are extensions points that can be over-ridden on a per object basis. That is what [[Get]] and [[Put]] provides. GetP and SetP define fixed operations. More generally, I think there should be a 1::1 correspondence between the internal methods in listed in ES5 Table 8 and fundamental proxy traps. We either need to eliminate any internal methods that don't trap or add corresponding traps. In this proposal you eliminate the need for [[CanPUt]] and you raise the question of whether that is possible for also get rid of [[GetProperty]]. My analysis (https://docs.google.com/spreadsheet/ccc?key=0Ak51JfLL8QLYdDFkcy1VUl9OQ3BSc1kxeDI4RkJsc0E ) says that we can, so we should just go ahead and eliminate it. We might even consider eliminating [[Delete]] by defining [[DefneOwnProperty]](name,undefined) to mean delete. Finally, I also have some thoughts about [[HasProperty]] but I will put those in a replay to Andreas message on that touches on that. Allen On Nov 7, 2011, at 7:54 AM, Tom Van Cutsem wrote: Hi, I wrote up an initial (but fairly complete) draft of a proposed refactoring of the ES5 [[Get]], [[Put]] and [[HasProperty]] algorithms to change the way in which these algorithms climb the prototype chain: http://wiki.ecmascript.org/doku.php?id=strawman:refactoring_put This is mainly beneficial for proxies, as the prototype walking strategy becomes observable to proxies-used-as-prototypes. IMHO, the refactored algorithms interoperate with proxies in a much saner way, finally restoring the intuitive semantics of the get and set traps that MarkM and I had in mind from the beginning, but which Sean Eagan pointed out were flawed given the ES5 spec algorithms. The biggest change is in the [[Put]] algorithm. For those not into ES spec language, I wrote up the behavior for ES5 [[Put]] and my proposed ES.next [[Put]] in JS itself: ES5 [[Put]]: http://code.google.com/p/es-lab/source/browse/trunk/src/es5adapt/setProperty.js#115 Proposed ES.next [[Put]]: http://code.google.com/p/es-lab/source/browse/trunk/src/es5adapt/setProperty.js#68 The refactored version also fixes the anomalies resulting from the ES5 [[CanPut]] vs. [[Put]] split that Andreas Rossberg pointed out earlier on this list. When I say refactoring here, I really do intend for these new algorithms to be equivalent to the ES5 algorithms for non-proxy objects. To test whether these algorithms are indeed equivalent, I wrote up a little test-suite that runs in the browser: http://es-lab.googlecode.com/svn/trunk/src/es5adapt/testSetProperty.html The results look promising (success on Firefox7, 1 failure on Chrome/Safari because these allow overriding of non-writable inherited data props, haven't tested other browsers yet). Still, the more es-discuss eye-balls that can scrutinize these algorithms, the better. 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: [Proxies] Refactoring prototype climbing in the spec
On Nov 8, 2011, at 7:33 AM, Andreas Rossberg wrote: On 7 November 2011 16:54, Tom Van Cutsem tomvc...@gmail.com wrote: I wrote up an initial (but fairly complete) draft of a proposed refactoring of the ES5 [[Get]], [[Put]] and [[HasProperty]] algorithms to change the way in which these algorithms climb the prototype chain: http://wiki.ecmascript.org/doku.php?id=strawman:refactoring_put Looks good, and as far as I can see from a first read, solves the issues we were discussing so far. But I have a follow-up request. :) Regarding redundant trap calls with proxies there is another, more pervasive problem with the current spec: in lots of places it first calls [[HasProperty]] and then [[Get]]. With proxies, this always implies two trap calls, which seems wasteful. Would it be possible to refactor that, too? Seems more difficult, because we would need to enable [[Get]] (and hence the get trap) to signal lookup failure. (Too bad that we cannot reuse `undefined' for it.) But I think the current situation isn't satisfactory. I agree, the current specification style is fine when we are dealing with side-effectly free internal operations but as soon as they are reified they become problematic and a performance issue. I believe that the [[HasProperty]] [[Get]] combination is always used in situations where a conditional [[Get]] is desired. I suggest that we redefine [[Get]] to take a second optional argument which is used as a signal value to indicated that the property does not exist. Internally (within the specification) any unique object value can be used as the signal value as the spec. will never allow that value to escape such that it could be stored as a property value. A similar approach could be taken with the trap and Object.getProperty APIs but there is the hazard that handler code might capture and misuse (store into a property) the signal value. It isn't clear to me whether this would constitute an exploitable hazard or whether it would just be an annoying bug. This capture problem could be avoid if the trap always passed a new object to the handler when the [[Get]] call uses its second argument. I'm reluctant to impose an (even trivial) object allocation on [[Get]] traps but it probably is still less overhead then making two traps. Also, it would not be needed in situations where the [[Get]] only had a single argument. Using this approach, the get handler signature could be something like: function(receiver, name, target, proxy, missingMarker=undefined) and the signature for Object.getProperty could be: function(receiver, name, missingMarker=undefined, parent=receiver) (for the optional argument ordering, I'm assuming that use of a missingMarker is more common then using an explicit parent) Given the limitations of ES function protocol the moral equivalent of either reference parameters or multiple value returns also require an object allocation. It isn't clear that we can do much better than this style of API. If we do this we should be able to eliminate both the [[HasProperty]] internal method and the has trap. allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
[Proxies] Refactoring prototype climbing in the spec
Hi, I wrote up an initial (but fairly complete) draft of a proposed refactoring of the ES5 [[Get]], [[Put]] and [[HasProperty]] algorithms to change the way in which these algorithms climb the prototype chain: http://wiki.ecmascript.org/doku.php?id=strawman:refactoring_put This is mainly beneficial for proxies, as the prototype walking strategy becomes observable to proxies-used-as-prototypes. IMHO, the refactored algorithms interoperate with proxies in a much saner way, finally restoring the intuitive semantics of the get and set traps that MarkM and I had in mind from the beginning, but which Sean Eagan pointed out were flawed given the ES5 spec algorithms. The biggest change is in the [[Put]] algorithm. For those not into ES spec language, I wrote up the behavior for ES5 [[Put]] and my proposed ES.next [[Put]] in JS itself: ES5 [[Put]]: http://code.google.com/p/es-lab/source/browse/trunk/src/es5adapt/setProperty.js#115 Proposed ES.next [[Put]]: http://code.google.com/p/es-lab/source/browse/trunk/src/es5adapt/setProperty.js#68 The refactored version also fixes the anomalies resulting from the ES5 [[CanPut]] vs. [[Put]] split that Andreas Rossberg pointed out earlier on this list. When I say refactoring here, I really do intend for these new algorithms to be equivalent to the ES5 algorithms for non-proxy objects. To test whether these algorithms are indeed equivalent, I wrote up a little test-suite that runs in the browser: http://es-lab.googlecode.com/svn/trunk/src/es5adapt/testSetProperty.html The results look promising (success on Firefox7, 1 failure on Chrome/Safari because these allow overriding of non-writable inherited data props, haven't tested other browsers yet). Still, the more es-discuss eye-balls that can scrutinize these algorithms, the better. Cheers, Tom ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: [Proxies] Refactoring prototype climbing in the spec
Hi Tom, Thanks for all this work! Le 07/11/2011 16:54, Tom Van Cutsem a écrit : Hi, I wrote up an initial (but fairly complete) draft of a proposed refactoring of the ES5 [[Get]], [[Put]] and [[HasProperty]] algorithms to change the way in which these algorithms climb the prototype chain: http://wiki.ecmascript.org/doku.php?id=strawman:refactoring_put This is mainly beneficial for proxies, as the prototype walking strategy becomes observable to proxies-used-as-prototypes. IMHO, the refactored algorithms interoperate with proxies in a much saner way, finally restoring the intuitive semantics of the get and set traps that MarkM and I had in mind from the beginning, but which Sean Eagan pointed out were flawed given the ES5 spec algorithms. I am a big fan of this refactoring. I like the idea that the algorithm transfers full control to the proxy. Also, accepting this strawman would solve my inherited event properties problem because I would have access to the receiver from the get trap and would be able to do the binding I want. About [[SetP]]: * It seems to me that we've traded a duplicated [[GetProperty]] call for a duplicated [[GetOwnProperty]] (Step 1 and 5.a) call on the receiver. This could be avoided by storing the property descriptor at the beginning (when O.[[setP]](Receiver, P, V) is called and O === receiver). Step 5.a could be remove by the change. * If step 5.a is removed, I think that step 5.b.i is useless, because we would have returned from the set when O was the receiver (by step 2.a of [[setP]]) * If step 5.a is removed, then step 5.c is useless, because if the desc had a [[set]], then we would have already returned from one of the substep of step 3 when O was the receiver Why not redefining [[Get]] as what you have defined as [[GetP]] and equivalent for [[Set]] and [[SetP]]? Current 8.12.3 [[Get]] (P) would become [[Get]] (Receiver, P). It would be called with O as initial value for Receiver pretty much everywhere in the spec except within [[Get]] recursive calls. Equivalent for [[Set]]. It would prevent the replacement of 2 Object.* methods by 2 others. With the refactoring, on the direct proxy strawman, I don't think we need the proxy argument for the get and set traps anymore. It was here as the receiver, but since we have the receiver itself, the get and set trap can just have the receiver and the target as argument. David ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss