Shared proxy handlers

# Sean Eagan (14 years ago)

One way to get truly shared proxy handlers would be to allow proxies to have some internal instance "state" that gets passed to each trap, just like objects have internal instance state ( [[Prototype]], [[Class]], [[Extensible]] etc) that gets "passed" to their "traps" ( [[Get]], [[GetOwnProperty]], [[Call]] etc.). This would add another argument to each trap, but I think it might be better anyway to switch to a single object argument for all traps so that the order becomes irrelevant, of course the argument names then become relevant, so it's a tradeoff. There could be a "create" trap to initialize this state, and a way to pass arguments to this trap.

It might also be useful to have a "getPrototypeOf" trap to avoid having to explicitly (and correctly) calculate and pass the prototype argument on each Proxy.create call. Removing the prototype argument could also allow Proxy.create and Proxy.createFunction to be merged into a single API. This trap could be called just once (to avoid mutable prototypes) right after the "create" trap. This trap would be ignored for function proxies since they should always have Function.prototype.

Putting this altogether, and utilizing destructuring, this could look something like...

Proxy.Handler = { ... create: function(target) { let state = {target: target}; return state; }, ... getPrototypeOf: function ( { state } ) { return Object.getPrototypeOf ( state.target ) ); } ... get: function( { state, proxy, receiver, name } ) { ... }, ... }

In order to be able to pass the createArgs to the "create" trap, definition of the full handler (handler + callTrap + constructTrap) would need to be separated from actual proxy instance construction. Here's how this might look...

//Proxy.Constructor would return a proxy constructor which constructs proxies having the passed handler, callTrap (optional), and constructTrap(optional) let ProxyConstructor = Proxy.Constructor(handler, callTrap, constructTrap);

The proxy constructor when called would first forward its arguments to the handler's "create" trap to initialize the state, then call the handler's "getPrototypeOf" trap passing the state, then construct and return the actual proxy using handler, callTrap, and constructTrap.

let proxy = new ProxyConstructor(...createArgs); // would also work without "new"

For Proxy.Handler proxies, this would translate to...

let ForwardingProxy = Proxy.Constructor(Proxy.Handler); let proxy = new ForwardingProxy(target);

Thanks, Sean Eagan

# David Bruant (14 years ago)

Le 13/04/2011 23:24, Sean Eagan a écrit :

One way to get truly shared proxy handlers

Do we want it? In my previous thread, I discussed shared handlers, but more from a proxy writer experience point of view, not with the idea of having something specified toward it. The topic had already been discussed, it was one of the rationale behind [1]. I wanted to provide a bit of experience of what is (for a proxy writer) to be considered when thinking about shared handlers: Which "proxy library" use cases can consider using shared handlers? Which use cases cannot use shared handlers? What are the benefits and downsides of shared handlers against one handler per instance (for proxy writers, not specifiers or implementors)?

But do we want to set "truly shared proxy handlers" as a Proxy API design goal? Having a bit of experience in discussing the Proxy API design here, I think that the first question to answer when discussing the Proxy API design is: What use cases does this proposal cover that wasn't covered before?

From what I understand, "truly" shared proxies do not solve anything

that couldn't be solved before.

would be to allow proxies to have some internal instance "state" that gets passed to each trap, just like objects have internal instance state ( [[Prototype]], [[Class]], [[Extensible]] etc) that gets "passed" to their "traps" ( [[Get]], [[GetOwnProperty]], [[Call]] etc.). This would add another argument to each trap, but I think it might be better anyway to switch to a single object argument for all traps so that the order becomes irrelevant, of course the argument names then become relevant, so it's a tradeoff. There could be a "create" trap to initialize this state, and a way to pass arguments to this trap.

It might also be useful to have a "getPrototypeOf" trap to avoid having to explicitly (and correctly) calculate and pass the prototype argument on each Proxy.create call. Removing the prototype argument could also allow Proxy.create and Proxy.createFunction to be merged into a single API.

TC39 seems to be going in the opposite direction (adding a prototype argument to createFunction) [2] I am under the impression that starting here, you're proposing other changes to the Proxy API, so at some point, the question will arise too: What is the use case you're addressing?

This trap could be called just once (to avoid mutable prototypes) right after the "create" trap. This trap would be ignored for function proxies since they should always have Function.prototype.

If you merge Proxy.create and Proxy.createFunction, how do you intend to distinguish function proxies from "object proxies"? I think that the rational behind having two methods is actually to have a clear distinction between how both are created so that creation rules can be different.

Putting this altogether, and utilizing destructuring, this could look something like...

Proxy.Handler = { ... create: function(target) {

I'm puzzled by the "target" argument name. Are you only addressing the forwarding proxy use case?

 let state = {target: target};
 return state;

}, ... getPrototypeOf: function ( { state } ) { return Object.getPrototypeOf ( state.target ) ); } ... get: function( { state, proxy, receiver, name } ) { ... }, ... } In order to be able to pass the createArgs to the "create" trap, definition of the full handler (handler + callTrap + constructTrap) would need to be separated from actual proxy instance construction. Here's how this might look...

//Proxy.Constructor would return a proxy constructor which constructs proxies having the passed handler, callTrap (optional), and constructTrap(optional) let ProxyConstructor = Proxy.Constructor(handler, callTrap, constructTrap);

The proxy constructor when called would first forward its arguments to the handler's "create" trap to initialize the state, then call the handler's "getPrototypeOf" trap passing the state, then construct and return the actual proxy using handler, callTrap, and constructTrap.

let proxy = new ProxyConstructor(...createArgs); // would also work without "new"

For Proxy.Handler proxies, this would translate to...

let ForwardingProxy = Proxy.Constructor(Proxy.Handler); let proxy = new ForwardingProxy(target);

All what you're describing is quite interesting, but I don't think it is solving a new use case. I have the impression that besides the additional state argument (which could be emulated as state objects in a WeakMap indexed on your proxies), all what you're describing could be written as a library on top of the current Proxy API.

Cheers,

David

[1] strawman:handler_access_to_proxy [2] strawman:function_proxy_prototype

# Sean Eagan (14 years ago)

I think it might be better anyway to switch to a single object argument for all traps so that the order becomes irrelevant, of course the argument names then become relevant, so it's a tradeoff.

Any comments on this idea?

TC39 seems to be going in the opposite direction (adding a prototype argument to createFunction) [2]

The API I proposed could also allow for custom function proxy prototypes, just using the "getPrototypeOf" trap instead of a prototype argument.

I am under the impression that starting here, you're proposing other changes to the Proxy API, so at some point, the question will arise too: What is the use case you're addressing?

The main use case would be a way to package up all meta behavior (handler + callTrap + constuctTrap + initialization of internal state / prototype used by these traps) so that it can be reused by multiple proxies. In the API I proposed these are what are returned from Proxy.Constructor, which is sort of like a meta-constructor (analogous to meta-class). It could someday lead to an executable spec for native constructors (Object, Array, etc), but as you mentioned that it is not nearly as important as application use cases. Also, this packaging would allow implementations to only store a reference to the package for each proxy, rather than to each of handler, callTrap, constructTrap.

If you merge Proxy.create and Proxy.createFunction, how do you intend to distinguish function proxies from "object proxies"? I think that the rational behind having two methods is actually to have a clear distinction between how both are created so that creation rules can be different.

With a single API, the distinction would just be that function proxies have a callTrap.

Proxy.Handler = {   ...   create: function(target) { I'm puzzled by the "target" argument name. Are you only addressing the forwarding proxy use case?

There would be no requirements on the "create" arguments, was just an example for Proxy.Handler. Sorry, forgot to mention here that I intended "Proxy.Handler" to replace "Proxy.Handler.prototype" from the existing API.

All what you're describing is quite interesting, but I don't think it is solving a new use case. I have the impression that besides the additional state argument (which could be emulated as state objects in a WeakMap indexed on your proxies), all what you're describing could be written as a library on top of the current Proxy API.

WeakMaps would work. Built in support though could allow for more interoperability between use cases, better performance, and simpler code. Here is hypothetical code to compare that does the same thing for both strategies...

// existing proxy API using weak maps let handler = { states: new WeakMap(), setState: function(proxy, state) { this.states.set(proxy, state); }, getState: function(proxy) { return this.states.get(proxy); }, ... getOwnPropertyDescriptor: function(name, proxy) { var state = this.getState(proxy); ... }, ... }; let proxy = Proxy.create(handler, getPrototype(state)), handler.setState(proxy, state), functionProxy = Proxy.createFunction(handler, callTrap, constructTrap, getPrototype(functionState)), handler.setState(functionProxy, functionState);

// API proposed in this thread let handler = { create: function(state) { return state; }, ... getPrototypeOf: function ( { state } ) { return getPrototype(state); } ... getOwnPropertyDescriptor: function( {state, name} ) { ... }, ... }; let Handler = new Proxy.Constructor(handler), proxy = new Handler(state), FunctionHandler = new Proxy.Constructor(handler, callTrap, constructTrap), functionProxy = new FunctionHandler(functionState);

Weak Maps:

Need to retrieve proxy state in each trap Need to retrieve state multiple times for derived traps Boilerplate code in each trap Slightly higher infinite recursion hazard since you have to use the proxy reference in order to get the internal state.

Proposed API:

Impossible to forget to initialize the state and prototype, because it is built into the API. Impossible for any traps to be called prematurely before state is initialized.

Thanks, Sean Eagan

# Tom Van Cutsem (14 years ago)

2011/4/15 Sean Eagan <seaneagan1 at gmail.com>

I think it might be better anyway to switch to a single object argument for all traps so that the order becomes irrelevant, of course the argument names then become relevant, so it's a tradeoff.

Any comments on this idea?

I don't think the traps take that many arguments that they merit a keyword-arguments API.

TC39 seems to be going in the opposite direction (adding a prototype argument to createFunction) [2]

The API I proposed could also allow for custom function proxy prototypes, just using the "getPrototypeOf" trap instead of a prototype argument.

The cached getPrototypeOf trap behavior falls prone to the same source of confusion that caused us to steer away from defining call and construct as separate traps (cf. the other discussion thread on the uniform proxy API).

I agree with David that the use cases of shared proxy handlers are not sufficiently explored to merit the addition of customizable "internal proxy state".

# Sean Eagan (14 years ago)

I don't think the traps take that many arguments that they merit a keyword-arguments API.

Agreed, the quantity of arguments, even if one were to be added, is manageable without keyword arguments. However, if any arguments need to be selectively added to certain traps in the future, then the proxy would no longer be the last argument to all traps, which could make the parameter lists difficult to remember.

The cached getPrototypeOf trap behavior falls prone to the same source of confusion that caused us to steer away from defining call and construct as separate traps (cf. the other discussion thread on the uniform proxy API).

Note that it is not the "getPrototypeOf" trap that is cached (as I was proposing for the "call" and "construct" trap in the other thread), but rather its return value, and that the only reason for that is to match current (and AFAIK future) behavior that an object's [[Prototype]] is immutable, in order to not break proxy transparency. If mutable prototypes were allowed in the future, a "getPrototypeOf" trap would be needed, but would be, at that point, very difficult to add since the prototype argument would probably want to be removed from Proxy.create / Proxy.createFunction, which would involve breaking existing code.

I agree with David that the use cases of shared proxy handlers are not sufficiently explored to merit the addition of customizable "internal proxy state".

Agreed, which is why I took some time to start exploring it further :). Here are my findings up to this point...

Storing proxy instance state directly within handlers:

Trap namespace pollution:

The default handler, Proxy.Handler, stores proxy instance state directly in each handler's "target" property. Notice that this immediately prevents a "target" trap from being added in the future, which of course isn't so bad as it is an unlikely future trap name. However, as soon as proxies are standardized and start to be used, if user defined proxy instance state is allowed (and even strongly encouraged by implementing Proxy.Handler this way) to be stored in handlers, the entire remainder of the trap namespace will be open to pollution, and thus prevent any new traps from being added in the future without the likelihood of breaking existing code. Beyond proxy instance state, users may also add helper methods including perceived "missing" traps, which may directly correlate to traps that would be wanted to be added in the future. Thus, it seems to me that storing anything in handlers besides existing traps is likely to cause future headaches, and should be avoided.

Extra inheritance chain step when resolving traps:

Just a minor observation that, if proxy instance state is stored in the own properties of a handler, and traps are inherited (as with Proxy.Handler created handlers), then all traps are at least one step up the inheritance chain. With shared handlers, traps would be on average about one less step up the inheritance chain. I realize prototype chain walking is highly optimized, but proxies allow for transparent non-optimized user defined inheritance.

Existing default handler useless to shared handlers:

The existing Proxy.Handler.prototype and all of its traps are useless to a shared handler because they all have a reference to "this.target". This could be resolved by replacing these references with "this.getTarget(proxy)", but that again starts to pollute the trap namespace. Conversely, with a share-able Proxy.Handler, handler-per-instance use cases could be accommodated simply by wrapping a singleton pattern around a handler.

Thanks, Sean Eagan

# David Bruant (14 years ago)

Le 18/04/2011 21:55, Sean Eagan a écrit :

Storing proxy instance state directly within handlers:

Trap namespace pollution:

The default handler, Proxy.Handler, stores proxy instance state directly in each handler's "target" property. Notice that this immediately prevents a "target" trap from being added in the future, which of course isn't so bad as it is an unlikely future trap name. However, as soon as proxies are standardized and start to be used, if user defined proxy instance state is allowed (and even strongly encouraged by implementing Proxy.Handler this way) to be stored in handlers, the entire remainder of the trap namespace will be open to pollution, and thus prevent any new traps from being added in the future without the likelihood of breaking existing code. Beyond proxy instance state, users may also add helper methods including perceived "missing" traps, which may directly correlate to traps that would be wanted to be added in the future. Thus, it seems to me that storing anything in handlers besides existing traps is likely to cause future headaches, and should be avoided.

That's an excellent point. It sounds to be a safe idea to consider the handler as a namespace used for traps and that anything internal state-related should be captured. On the other hand, the main advantage of having "this.target" is that anyone can replace one trap with user JS code (to add a logger for instance) and have a way to access the target anyway. This would be impossible if the target was encapsulated and only accessible within built-in traps scopes. This advantage extends to any proxy library that would decide to expose its handler and partial internal state by exposing the partial state on the handler object.

As a side note, I'd like to say that, in my opinion, Proxy.Handler is a misleading name. It seems to imply that all handlers must/should/had better be a forwarding handler. I agree that it is certainly (one of) the most important use case, nevertheless, it is a restricting view of proxies which internal state has no reason to be reduced to a unique object. I would be more in favor of Proxy.ForwardingHandler, even if it's a bit long, I admit.

Extra inheritance chain step when resolving traps:

Just a minor observation that, if proxy instance state is stored in the own properties of a handler, and traps are inherited (as with Proxy.Handler created handlers), then all traps are at least one step up the inheritance chain. With shared handlers, traps would be on average about one less step up the inheritance chain. I realize prototype chain walking is highly optimized, but proxies allow for transparent non-optimized user defined inheritance.

The JS code in harmony:proxy_defaulthandler is given for illustration purposes. All of this will be native code, so there should be no performance issues.

Existing default handler useless to shared handlers:

The existing Proxy.Handler.prototype and all of its traps are useless to a shared handler because they all have a reference to "this.target". This could be resolved by replacing these references with "this.getTarget(proxy)", but that again starts to pollute the trap namespace.

Unless we consider generalizing "this.getTarget" to any use case by having a "this.getState" method (it's not a trap) on handlers. It would be used when there is a need to standardize the state of the proxy library (like the "target" for forwarding proxies). It would be reserved for proxy library authors who would like to expose their handlers with in mind the idea that people would change traps while still being able to access partial internal state. Actually a |this.state| weak map should be enough. This constraint would change the way forwarding proxies are created since the proxy object identity would need to be known before initializing the state. Hence we would probably need to do something like: gist.github.com/926581

We loose in simplicity (destructuring is helping though) but get all advantages mentionned above. The simplicity is lost because of the need of knowing the proxy object identity before being able to release the handler (order of lines 9-11 which cannot be changed).

Conversely, with a share-able Proxy.Handler, handler-per-instance use cases could be accommodated simply by wrapping a singleton pattern around a handler.

I did not understand this point. Could you provide an example or a code snippet?

# Sean Eagan (14 years ago)

On Mon, Apr 18, 2011 at 7:38 PM, David Bruant <david.bruant at labri.fr> wrote:

Thus, it seems to me that storing anything in handlers besides existing traps is likely to cause future headaches, and should be avoided. That's an excellent point. It sounds to be a safe idea to consider the handler as a namespace used for traps and that anything internal state-related should be captured. On the other hand, the main advantage of having "this.target" is that anyone can replace one trap with user JS code (to add a logger for instance) and have a way to access the target anyway. This would be impossible if the target was encapsulated and only accessible within built-in traps scopes. This advantage extends to any proxy library that would decide to expose its handler and partial internal state by exposing the partial state on the handler object.

None of this is an issue in the API I am proposing, since the state is passed to each trap, not captured from the scope.

As a side note, I'd like to say that, in my opinion, Proxy.Handler is a misleading name. It seems to imply that all handlers must/should/had better be a forwarding handler. I agree that it is certainly (one of) the most important use case, nevertheless, it is a restricting view of proxies which internal state has no reason to be reduced to a unique object. I would be more in favor of Proxy.ForwardingHandler, even if it's a bit long, I admit.

+1

Don't want to get too far off topic, but let me quickly dive into the use case I alluded to above. If the proxy API can provide hooks that map back to the ES object spec (traps map to internal object methods, internal proxy state maps to internal object properties), then at some point in the future objects can be executably spec'ed using the proxy API. Each native constructor would have a handler which could even be extended to support custom {Array,Function, etc} use cases. A prerequisite for this would be primitives for each type for "bootstrapping" purposes, such as records, dicts, tuples (+ a mutable version), and primitive functions (i.e. only callable, no property access).

The JS code in harmony:proxy_defaulthandler is given for illustration purposes. All of this will be native code, so there should be no performance issues.

To get to that native code though you still need to take an extra step up the inheritance chain since currently Proxy.Handler based handlers' own properties hold proxy instance state as opposed to traps.

Unless we consider generalizing "this.getTarget" to any use case by having a "this.getState" method (it's not a trap) on handlers. It would be used when there is a need to standardize the state of the proxy library (like the "target" for forwarding proxies). It would be reserved for proxy library authors who would like to expose their handlers with in mind the idea that people would change traps while still being able to access partial internal state. Actually a |this.state| weak map should be enough.

Here are the reasons why I believe the API I am proposing is superior to a weak map implementation:

Code simplicity: Can reference proxy state via |state| which is much shorter than |this.state.get(proxy)| or |this.getState(proxy)| No proxy-assocation code needed, so avoids the complexities of "ForwardingPair" from your example.

Interoperability: Standardized and abstract proxy-state association mechanism, handlers do not need to coordinate how to do this.

Safety: No "state" or "getState" property needed on handler, so completely avoids trap namespace pollution Less infinite recursion hazard as there is no need to use the proxy reference to get to the state

Performance: Avoid weak map lookups, derived traps and traps that call inherited traps could have arbitrarily many of these as there is no mechanism to pass state to traps

Also, if for any reason a given handler author did not want to use the native proxy state support, preferring to implement this on their own (such as by using WeakMap's), then we could potentially provide an opt out mechanism of just not defining a "create" trap. For these handlers, the implementation would not store any proxy state, and could even omit the state argument when calling its traps (or just pass undefined).

This constraint would change the way forwarding proxies are created since the proxy object identity would need to be known before initializing the state.

This is not an issue in the API I am proposing, as it abstracts the proxy-state association process via the "create" trap.

Hence we would probably need to do something like: gist.github.com/926581

Here's what the same functionality would look like in the API I am proposing:

gist.github.com/929185

Conversely, with a share-able Proxy.Handler, handler-per-instance use cases could be accommodated simply by wrapping a singleton pattern around a handler. I did not understand this point. Could you provide an example or a code snippet?

Sure, in the API I'm proposing you could simply do...

let handlersOnlyProxy = (function(){ let handler = new Proxy.ForwardingHandler; // customize |handler| as desired here return new (Proxy.Constructor(handler))(...createArgs); })();

Thanks, Sean Eagan

# Tom Van Cutsem (14 years ago)

Sean,

Thanks for sharing your alternative design. Some follow-up comments below:

2011/4/18 Sean Eagan <seaneagan1 at gmail.com>

I don't think the traps take that many arguments that they merit a keyword-arguments API.

Agreed, the quantity of arguments, even if one were to be added, is manageable without keyword arguments. However, if any arguments need to be selectively added to certain traps in the future, then the proxy would no longer be the last argument to all traps, which could make the parameter lists difficult to remember.

True, specifying |proxy| as an optional trailing argument makes it difficult to extend the argument lists later without exposing it. Still, I'd argue keyword-arguments aren't worth the extra syntax in the common case.

The cached getPrototypeOf trap behavior falls prone to the same source of confusion that caused us to steer away from defining call and construct as separate traps (cf. the other discussion thread on the uniform proxy API).

Note that it is not the "getPrototypeOf" trap that is cached (as I was proposing for the "call" and "construct" trap in the other thread), but rather its return value, and that the only reason for that is to match current (and AFAIK future) behavior that an object's [[Prototype]] is immutable, in order to not break proxy transparency. If mutable prototypes were allowed in the future, a "getPrototypeOf" trap would be needed, but would be, at that point, very difficult to add since the prototype argument would probably want to be removed from Proxy.create / Proxy.createFunction, which would involve breaking existing code.

Whether it's the trap or its return value that is cached, the confusion remains the same. The current API makes it crystal-clear to the programmer that the prototype is to be calculated ahead-of-time and can't be changed. I don't see why this API needs to change.

I agree with David that the use cases of shared proxy handlers are not sufficiently explored to merit the addition of customizable "internal proxy state".

Agreed, which is why I took some time to start exploring it further :). Here are my findings up to this point...

Storing proxy instance state directly within handlers:

Trap namespace pollution:

The default handler, Proxy.Handler, stores proxy instance state directly in each handler's "target" property. Notice that this immediately prevents a "target" trap from being added in the future, which of course isn't so bad as it is an unlikely future trap name. However, as soon as proxies are standardized and start to be used, if user defined proxy instance state is allowed (and even strongly encouraged by implementing Proxy.Handler this way) to be stored in handlers, the entire remainder of the trap namespace will be open to pollution, and thus prevent any new traps from being added in the future without the likelihood of breaking existing code. Beyond proxy instance state, users may also add helper methods including perceived "missing" traps, which may directly correlate to traps that would be wanted to be added in the future. Thus, it seems to me that storing anything in handlers besides existing traps is likely to cause future headaches, and should be avoided.

This critique can be applied to any OO framework in general. Still, people happily subclass standard classes and add new methods to them without fear of the possible evolution problems (which do exist).

You're right about potential backwards-compat. issues, but that didn't stop ES5 from adding new methods on Array.prototype, for example.

Extra inheritance chain step when resolving traps:

Just a minor observation that, if proxy instance state is stored in the own properties of a handler, and traps are inherited (as with Proxy.Handler created handlers), then all traps are at least one step up the inheritance chain. With shared handlers, traps would be on average about one less step up the inheritance chain. I realize prototype chain walking is highly optimized, but proxies allow for transparent non-optimized user defined inheritance.

Given the overall cost of trapping an operation on a proxy, my feeling is that this added inheritance step is a non-issue. Implementors should speak up if it's not.

Existing default handler useless to shared handlers:

The existing Proxy.Handler.prototype and all of its traps are useless to a shared handler because they all have a reference to "this.target". This could be resolved by replacing these references with "this.getTarget(proxy)", but that again starts to pollute the trap namespace. Conversely, with a share-able Proxy.Handler, handler-per-instance use cases could be accommodated simply by wrapping a singleton pattern around a handler.

True, the existing Proxy.Handler API was not designed to support shared handlers. I'm not sure whether that is a problem that needs to be addressed.

Overall, your proposed API is consistent and does support shared handlers well. The question remains whether the added complexity (i.e. Proxy.Constructor) is worth it. In terms of object allocation, I don't see the benefit of shared handlers as opposed to the current design:

  • In typical uses of the current design, for N targets, one allocates N virtually-empty proxies, N handler objects that contain per-instance state (e.g. |target|), and 1 shared handler prototype that typically contains all the traps.
  • In the shared-handler design, for N targets, one allocates N proxies, each storing a reference to each of N state objects (the return values from your |create| trap) and 1 shared state-free handler object.

Unless I'm missing something, the net result is the same in both cases.

# Tom Van Cutsem (14 years ago)

As a side note, I'd like to say that, in my opinion, Proxy.Handler is a misleading name. It seems to imply that all handlers must/should/had better be a forwarding handler. I agree that it is certainly (one of) the most important use case, nevertheless, it is a restricting view of proxies which internal state has no reason to be reduced to a unique object. I would be more in favor of Proxy.ForwardingHandler, even if it's a bit long, I admit.

That's how it was called originally. The name is definitely open for discussion. I have to say I never thought the name "Proxy.Handler" would imply that all handlers must be forwarding handlers, but I see your point.

# David Bruant (14 years ago)

Le 19/04/2011 21:33, Sean Eagan a écrit :

On Mon, Apr 18, 2011 at 7:38 PM, David Bruant <david.bruant at labri.fr> wrote:

Thus, it seems to me that storing anything in handlers besides existing traps is likely to cause future headaches, and should be avoided. That's an excellent point. It sounds to be a safe idea to consider the handler as a namespace used for traps and that anything internal state-related should be captured. On the other hand, the main advantage of having "this.target" is that anyone can replace one trap with user JS code (to add a logger for instance) and have a way to access the target anyway. This would be impossible if the target was encapsulated and only accessible within built-in traps scopes. This advantage extends to any proxy library that would decide to expose its handler and partial internal state by exposing the partial state on the handler object. None of this is an issue in the API I am proposing, since the state is passed to each trap, not captured from the scope.

You are perfectly right.

Unless we consider generalizing "this.getTarget" to any use case by having a "this.getState" method (it's not a trap) on handlers. It would be used when there is a need to standardize the state of the proxy library (like the "target" for forwarding proxies). It would be reserved for proxy library authors who would like to expose their handlers with in mind the idea that people would change traps while still being able to access partial internal state. Actually a |this.state| weak map should be enough. Here are the reasons why I believe the API I am proposing is superior to a weak map implementation:

Just to clarify, a weak map was just an example. If this.state is an accessor or if there is a .getState method, I'm fine with it.

Code simplicity: Can reference proxy state via |state| which is much shorter than |this.state.get(proxy)| or |this.getState(proxy)| No proxy-assocation code needed, so avoids the complexities of "ForwardingPair" from your example. (...)

This constraint would change the way forwarding proxies are created since the proxy object identity would need to be known before initializing the state. This is not an issue in the API I am proposing, as it abstracts the proxy-state association process via the "create" trap.

I'll give you this point. (both are very close, that's why I comment them together)

Interoperability: Standardized and abstract proxy-state association mechanism, handlers do not need to coordinate how to do this.

Safety: No "state" or "getState" property needed on handler, so completely avoids trap namespace pollution

If only one "state" or "getState" property is standardized, then, there is no pollution. There is one available thing that is available for people to use, everyone is encouraged to use it and if they don't and use their own names, then, they'll end up having collisions, but at least, they would have been provided a standardized mechanism, so they cannot accuse the spec for not taking care of their use case.

Less infinite recursion hazard as there is no need to use the proxy reference to get to the state

The weak map idea associate the state with the proxy object identity. There should be no recursion hazard. Your state argument would be based on object identity too; the difference between our two ideas is that you're using internal mechanisms while I explicit the state exposure. To be honest, right now, I think that both ideas are good and am unable to say which is better yet.

Performance: Avoid weak map lookups, derived traps and traps that call inherited traps could have arbitrarily many of these as there is no mechanism to pass state to traps

Performance should never be an argument unless we're talking about asymptotic complexity in space or time. Implementations optimizations come and go. A weak map lookup may be better optimize thanks to static analysis or type inference in some cases. No one can tell.

Also, if for any reason a given handler author did not want to use the native proxy state support, preferring to implement this on their own (such as by using WeakMap's), then we could potentially provide an opt out mechanism of just not defining a "create" trap. For these handlers, the implementation would not store any proxy state, and could even omit the state argument when calling its traps (or just pass undefined).

For the matter of comparison, with "state"/"getState" handler property, not having the property would be an opt-out, so both ideas are even on the topic from what I see.

Hence we would probably need to do something like: gist.github.com/926581 Here's what the same functionality would look like in the API I am proposing:

gist.github.com/929185

That's interesting. I agree with what Tom said in some reply about "getPrototypeOf". The name is confusing, but the point is good. Why not just having a prototype property?