[Harmony proxies] proxy, receiver, target: an alternative to additional arguments
On Tue, Oct 18, 2011 at 6:45 AM, David Bruant <bruant.d at gmail.com> wrote:
Hi,
The initial proxy proposal was about to provide "proxy" as an additional argument to all traps. The current proxy has "target" instead, but still "proxy" for get and set traps. Regardless of the proposal, I have recently come up with a use case [1] where I expressed the need of passing the "receiver" to some traps when the proxy is up on the [[Prototype]] chain. (of course, there is still need for an agreement that this use case is worthwhile and that there is really no way to implement what i want without passing "receiver").
I think the "reciever" issue is unaffected by the change to direct proxies, it is still unnecessary if [[GetProperty]] continues to be propogated up the prototype chain, but necessary if [[Put]] and [[Get]] are propogated instead. The advantage of the latter is that you don't have to construct a complete property descriptor for [[Get]]s and [[Put]]s when you only really need the "get" or "set" attribute, the disadvantage is that it allows proxies to lie in their Object.getOwnPropertyDescriptor return value's "get" and "set" attributes, since that is no longer necessarily what is actually used for [[Get]]s and [[Put]]s.
I'm afraid this is getting a bit verbose when writing/reading traps. I think alternatives should be considered. Of course, putting things directly on the handler is out of question to respect proper stratification as previously said (by Tom?) on this list.
One idea could be to have a unique optional argument which would be an object with 3 properties: "proxy", "target" & "receiver". These aren't always necessary, but I think there is no harm in making all of them available to all traps.
The downside of named arguments is that you commit to the names for eternity, the function author is not allowed to use their own names, such as abbreviations, and they have to remember the names (as opposed to indexes) of the arguments. Also, since the other traps have varying numbers of other arguments, and this argument is proposed as last, this argument would not appear at the same index, which would not be a problem if the argument were first. It also seems kind of arbitrary to have some named arguments and some positional. Destructuring helps with the conciseness of named arguments though. If changing to named arguments, I would propose a single Object argument containing all args:
set: function( {name, value, proxy, target /, receiver/} )
Another alternative would be to do natively what we've been doing in code which is that when a call is trapped, instead of using the handler, an new object is created. This object inherits from the handler and has at the own layer the 3 properties. If another trap is traversed, another object is created, etc. (this is of course in theory. Engines are free to optimize).
Another close alternative is that instead of an object inheriting from the handler, a forwarding proxy with handler as target could be created and when performing [[Get]] on this proxy with one of the 3 property names, it would return the correct object based on the context.
In the 2 last cases, the proxy would be made accessible from traps with "this.proxy", etc.
Thoughts?
David
[1] mail.mozilla.orgpipermail/es-discuss/2011- October/017438.htmlesdiscuss/2011-October/017438 _____________** es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/**listinfo/es-discussmail.mozilla.org/listinfo/es-discuss
Thanks, Sean Eagan
On 18 October 2011 13:45, David Bruant <bruant.d at gmail.com> wrote:
Another alternative would be to do natively what we've been doing in code which is that when a call is trapped, instead of using the handler, an new object is created. This object inherits from the handler and has at the own layer the 3 properties. If another trap is traversed, another object is created, etc. (this is of course in theory. Engines are free to optimize).
Unfortunately, they aren't, because the difference is observable, due to unpleasant features like object identity.
Le 18/10/2011 17:17, Sean Eagan a écrit :
On Tue, Oct 18, 2011 at 6:45 AM, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:
Hi, The initial proxy proposal was about to provide "proxy" as an additional argument to all traps. The current proxy has "target" instead, but still "proxy" for get and set traps. Regardless of the proposal, I have recently come up with a use case [1] where I expressed the need of passing the "receiver" to some traps when the proxy is up on the [[Prototype]] chain. (of course, there is still need for an agreement that this use case is worthwhile and that there is really no way to implement what i want without passing "receiver").
I think the "reciever" issue is unaffected by the change to direct proxies, it is still unnecessary if [[GetProperty]] continues to be propogated up the prototype chain, but necessary if [[Put]] and [[Get]] are propogated instead.
My need is unrelated to the exact behavior of [[Put]] and [[Get]]. Here is my use case. I'm working in the context of event as properties [1]. I want inherited events:
var o = new EventedObject(); o.o = 'o';
Object.defineProperty(o, 'e', {event:true}); o.e.addListener(function(){console.log(JSON.stringify(this));});
var o1 = Object.create(o); o1.a = 1;
var o2 = Object.create(o); o2.b = 2;
o1.e(); // I expect '{"a":1}' to be logged o2.e(); // I expect '{"b":2}' to be logged
o1 and o2 are normal objects following ES5.1 - 8.12 definitions. In the current definition of [[Get]], "o1.e" does a [[GetProperty]] on o1, then on o. At this point, o has to return a property descriptor. I could return a property descriptor of a regular data property, but it looses the nice event enumeration I'm trying to add. So, i'll return the thing that has been passed to create the event property: {event:true, configurable:false, enumerable:false} Back to [[get]] with this descriptor, the step 4 (or 11 with the typo in ES5) is incorrect: my descriptor is not a data descriptor, but is not an accessor descriptor either! So first of all, o1.[[Get]] has no idea of what to do with my descriptor (it cannot find the listeners related to o.e). This is true for any non-standardized property descriptor. This is ISSUE 1.
ISSUE 2 is that since o1.[Get] cannot do what I need, I need to do it by myself. I can't rely on o1.[[Get]] semantics to do the proper |this| binding, so I need to do it myself at the prototype level. So I need the receiver. I really do not see alternative. And the trap that is being called on does not matter to me, i just need it in a way or another. One problem with "o1.e" calling o.[[GetProperty]] is that there is no way to distinguish from "Object.getPropertyDescriptor(o)" which is annoying.
I think ISSUE 2 can find a solution if the get trap is called on the prototype with the receiver. I'm not sure ISSUE 1 can have a solution besides reforming [[Get]] (and certainly [[Put]]?) to delegate to the same internal on the prototype.
The advantage of the latter is that you don't have to construct a complete property descriptor for [[Get]]s and [[Put]]s when you only really need the "get" or "set" attribute, the disadvantage is that it allows proxies to lie in their Object.getOwnPropertyDescriptor return value's "get" and "set" attributes, since that is no longer necessarily what is actually used for [[Get]]s and [[Put]]s.
I fully agree with the rationale of receiver being useless for current property descriptors (data and accessor), but my exeriment shows that [[Get]] and [[Put]] are unable to handle property descriptors they have not been prepared for.
I'm afraid this is getting a bit verbose when writing/reading traps. I think alternatives should be considered. Of course, putting things directly on the handler is out of question to respect proper stratification as previously said (by Tom?) on this list. One idea could be to have a unique optional argument which would be an object with 3 properties: "proxy", "target" & "receiver". These aren't always necessary, but I think there is no harm in making all of them available to all traps.
The downside of named arguments is that you commit to the names for eternity, the function author is not allowed to use their own names, such as abbreviations, and they have to remember the names (as opposed to indexes) of the arguments. Also, since the other traps have varying numbers of other arguments, and this argument is proposed as last, this argument would not appear at the same index, which would not be a problem if the argument were first. It also seems kind of arbitrary to have some named arguments and some positional. Destructuring helps with the conciseness of named arguments though. If changing to named arguments, I would propose a single Object argument containing all args:
set: function( {name, value, proxy, target /, receiver/} )
Interesting idea. Thanks.
David
Le 18/10/2011 17:28, Andreas Rossberg a écrit :
On 18 October 2011 13:45, David Bruant <bruant.d at gmail.com> wrote:
Another alternative would be to do natively what we've been doing in code which is that when a call is trapped, instead of using the handler, an new object is created. This object inherits from the handler and has at the own layer the 3 properties. If another trap is traversed, another object is created, etc. (this is of course in theory. Engines are free to optimize). Unfortunately, they aren't, because the difference is observable, due to unpleasant features like object identity.
True. The idea of a |this| being a forwarding proxy with the handler as its target can still hold since it would just be one additional identity and some magic for the particular property names "proxy", "target" and "receiver".
What you seem to want is the ability to define new kinds of properties in addition to data and accessor properties. That is beyond the goal of proxies.
However, I really don't see why you would want to define your "event properties" as a third type of property. What would be the problem with representing your "event properties" as, e.g. accessor properties with an additional event:true attribute? You state:
"I could return a property descriptor of a regular data property, but it
looses the nice event enumeration I'm trying to add."
But I don't understand what you mean by that.
I would model your event property descriptors as follows:
getOwnPropertyDescriptor: function(name) { if (/* name is an event property */) { return { event: true, get: function() { // we now have access to "receiver", it's |this| // fire the listeners } } } }
2011/10/18 David Bruant <bruant.d at gmail.com>
True. The idea of a |this| being a forwarding proxy with the handler as its target can still hold since it would just be one additional identity and some magic for the particular property names "proxy", "target" and "receiver".
Binding "this" inside a handler to a proxy to the handler, from which you can then access a property called "proxy"? That's really way too confusing. I think we can cope with target, proxy and potentially receiver being listed as separate arguments.
Le 18/10/2011 21:47, Tom Van Cutsem a écrit :
Hi David,
What you seem to want is the ability to define new kinds of properties in addition to data and accessor properties. That is beyond the goal of proxies.
If proxies allow to pass property descriptor attributes other than get/set/configurable/enumerable/writable/value, then I think whatever new attribute is passed is part of a new kind of property descriptor. The approach you describe below is very interesting but seem to force event properties to be accessor properties. If ever implemented natively, this may result in particular semantics of Object.defineProperty(o, name, {event:true}) (defaulting to an accessor rather than a data descriptor as it would now).
The idea of having only data or accessor property + some metadata is appealing, but I'm afraid it won't be enough to give these other "description attribute" enough power. And I'm afraid that if this isn't discussed today, we run into backward compatibility issues if new kinds of property descriptors are ever considered for standardization.
(...)
I would model your event property descriptors as follows:
getOwnPropertyDescriptor: function(name) { if (/* name is an event property */) { return { event: true, get: function() { // we now have access to "receiver", it's |this| // fire the listeners } } } }
That's a very interesting approach. I'll try to implement it and give feedback.
Thanks,
On Oct 18, 2011, at 12:49 PM, Tom Van Cutsem wrote:
2011/10/18 David Bruant <bruant.d at gmail.com> True. The idea of a |this| being a forwarding proxy with the handler as its target can still hold since it would just be one additional identity and some magic for the particular property names "proxy", "target" and "receiver".
Binding "this" inside a handler to a proxy to the handler, from which you can then access a property called "proxy"? That's really way too confusing. I think we can cope with target, proxy and potentially receiver being listed as separate arguments.
Seriously! :-/
The initial proxy proposal was about to provide "proxy" as an additional argument to all traps. The current proxy has "target" instead, but still "proxy" for get and set traps. Regardless of the proposal, I have recently come up with a use case [1] where I expressed the need of passing the "receiver" to some traps when the proxy is up on the [[Prototype]] chain. (of course, there is still need for an agreement that this use case is worthwhile and that there is really no way to implement what i want without passing "receiver").
I'm afraid this is getting a bit verbose when writing/reading traps. I think alternatives should be considered. Of course, putting things directly on the handler is out of question to respect proper stratification as previously said (by Tom?) on this list.
One idea could be to have a unique optional argument which would be an object with 3 properties: "proxy", "target" & "receiver". These aren't always necessary, but I think there is no harm in making all of them available to all traps.
Another alternative would be to do natively what we've been doing in code which is that when a call is trapped, instead of using the handler, an new object is created. This object inherits from the handler and has at the own layer the 3 properties. If another trap is traversed, another object is created, etc. (this is of course in theory. Engines are free to optimize).
Another close alternative is that instead of an object inheriting from the handler, a forwarding proxy with handler as target could be created and when performing [[Get]] on this proxy with one of the 3 property names, it would return the correct object based on the context.
In the 2 last cases, the proxy would be made accessible from traps with "this.proxy", etc.
Thoughts?
David
[1] esdiscuss/2011-October/017438