[Harmony proxies] Another idea to give handler access to proxies

# David Bruant (15 years ago)

After reading this page (strawman:handler_access_to_proxy), I thought of another idea. Maybe that instead of adding an argument to all handler methods, we could add a property to the handler. Each time a proxy p method m is trapped (with handler h):

  • Object.defineProperty(h, 'currentProxy', {value: p, configurable:true})
  • call the m trap (which can freely use this.currentProxy)
  • delete h.currentProxy (that's why the property has to be configurable)

An implementation could run some static analysis to see if the handler is an actual object and if this.currentProxyis actually used in the trap to avoid the overhead of define+delete. They would have to be performed if the handler is itself a proxy. In order to reserve the 'currentProxy' (no strong conviction on the name. It could also be 'proxy') name, Proxy.create and Proxy.createFunction could throw an error is such a property is already defined.

On issue is that within a handler method, I could do: {keys: function(){ Object.defineProperty(this, 'currentProxy', {value: 'gotcha', configurable:false}); } } This is notoriously stupid, but it would make the "inner delete" throw an error. The issue I'm raising is that 'configurable' controls both the fact that a property can be redefined through Object.defineProperty and deleted.

Anyway, this solution sweeps away any argument positionning issue. It is very generic and for sure, any trap current or future will need access to the proxy, so it solves the problem for potential future traps. On the other hand, there may be some property configuration issues and maybe some performance issue since the two additional calls may be needed at each trap call (even though static analysis could help out with this).

This idea is unperfect like the others, but it might be worth investigating in that direction.

# Tom Van Cutsem (15 years ago)

I had also thought of this, but quickly discarded it as too imperative. It just seems like a bad idea, and it interacts horribly with nested handler traps (you'd need to maintain a stack of "currentProxies" to make traps reentrant) and functional idioms (anonymous closures defined within a trap won't capture the current value of "currentProxy" as they would if it were a formal parameter to the trap).

Another way of looking at this: your suggestion implements a dynamically scoped binding, passing "proxy" as a parameter to the trap implements a lexically scoped binding. The latter is almost always what you want.

Cheers, Tom

2011/2/28 David Bruant <bruant at enseirb-matmeca.fr>

# David Bruant (15 years ago)

Le 01/03/2011 11:10, Tom Van Cutsem a écrit :

Hi,

I had also thought of this, but quickly discarded it as too imperative. It just seems like a bad idea, and it interacts horribly with nested handler traps (you'd need to maintain a stack of "currentProxies" to make traps reentrant) and functional idioms (anonymous closures defined within a trap won't capture the current value of "currentProxy" as they would if it were a formal parameter to the trap).

oh right... That's kind of a terrible idea.

Another way of looking at this: your suggestion implements a dynamically scoped binding, passing "proxy" as a parameter to the trap implements a lexically scoped binding. The latter is almost always what you want.

Here is another idea: Adding a 'currentProxy' property to the arguments object of a handler methods when it's called as a trap (otherwise, the method is just a regular function). It's very close to the idea of adding an argument without the inconvenient/embarassement of placing it. It's generic, so it solves the issue for future traps. It obviously comes to the price of some sort of inconsistency, because adding things to the arguments object hasn't been done before (not that i'd be aware of, at least).

Within a handler method, there would be a subtle difference. Let's take the example of a derived trap implementation (has trap):

var h = { getPropertyDescriptor: function(name){return arguments.currentProxy;}, has: function(name) { return !!this.getPropertyDescriptor(name); } } var p = Proxy.create(h);

'a' in p; // false

When 'has' is trapped, it calls the handler method like a regular method, so there is no special treatment to arguments (arguments.currentProxy === undefined => !!this.getPropertyDescriptor(name) === false)

If 'has' was implemented as: has: function(name) { return !!Object.getPropertyDescriptor(arguments.currentProxy, name); } then the 'getPropertyDescriptor' handler would be called as a trap, so 'currentProxy' would be 'magically' added to the arguments object.

I'm realizing that my idea raises a question that had no reason to exist before. Before, proxy weren't accessible from handler methods, so in order to implement derived traps, one handler method could only have access to other handler methods through 'this'. However, if 'proxy' becomes accessible within handler methods (no matter how), it could be considered to reimplement derived traps with actual fundamental trap trapping. I would personnally feel this to be more consistent with the idea of a mapping between traps and internal methods. However, as of today, besides my arguments-proxy-patching idea, I do not see how to notice a difference between 'this.getPropertyDescriptor(/*proxy, */ name)' (proxy argument depending on how we give proxy access to handler methods) and being trapped on 'Object.getPropertyDescriptor(proxy, name)'.

# Tom Van Cutsem (15 years ago)

Here is another idea: Adding a 'currentProxy' property to the arguments object of a handler methods when it's called as a trap (otherwise, the method is just a regular function). It's very close to the idea of adding an argument without the inconvenient/embarassement of placing it. It's generic, so it solves the issue for future traps. It obviously comes to the price of some sort of inconsistency, because adding things to the arguments object hasn't been done before (not that i'd be aware of, at least).

With the introduction of the spread operator in Harmony, the intention is to move away from using |arguments|. I wouldn't want to tie the proxies proposal this tight to the |arguments| object. Also, as you note, there is no precedent for it. I would also claim that it still doesn't feel like a natural place to store the proxy.

Within a handler method, there would be a subtle difference. Let's take the example of a derived trap implementation (has trap):

var h = { getPropertyDescriptor: function(name){return arguments.currentProxy;}, has: function(name) { return !!this.getPropertyDescriptor(name); } } var p = Proxy.create(h);

'a' in p; // false

When 'has' is trapped, it calls the handler method like a regular method, so there is no special treatment to arguments (arguments.currentProxy === undefined => !!this.getPropertyDescriptor(name) === false)

If 'has' was implemented as: has: function(name) { return !!Object.getPropertyDescriptor(arguments.currentProxy, name); } then the 'getPropertyDescriptor' handler would be called as a trap, so 'currentProxy' would be 'magically' added to the arguments object.

I'm realizing that my idea raises a question that had no reason to exist before. Before, proxy weren't accessible from handler methods, so in order to implement derived traps, one handler method could only have access to other handler methods through 'this'. However, if 'proxy' becomes accessible within handler methods (no matter how), it could be considered to reimplement derived traps with actual fundamental trap trapping. I would personnally feel this to be more consistent with the idea of a mapping between traps and internal methods. However, as of today, besides my arguments-proxy-patching idea, I do not see how to notice a difference between 'this.getPropertyDescriptor(/*proxy, */ name)' (proxy argument depending on how we give proxy access to handler methods) and being trapped on 'Object.getPropertyDescriptor(proxy, name)'.

There should not be a difference. Any code that has access to both a proxy and to its handler object can choose to either invoke the traps explicitly on the handler, or implicitly via the proxy object. I don't see why a trap should be able to distinguish both cases.

Of the proposals listed on the strawman wiki page, I prefer the "Proxy as additional argument" option. I think consistency between the traps and the Javascript code that they intercept is what matters most in practice, and what will minimize bugs for developers that are not familiar with spec. details. IOW, calls like "Object.defineProperty(proxy, name, pd)" would then be trapped by "function defineProperty(proxy, name, pd) {...}". Principle of least surprise and all that :-)

# David Bruant (15 years ago)

Le 01/03/2011 15:35, Tom Van Cutsem a écrit :

Hi,

Here is another idea:
Adding a 'currentProxy' property to the arguments object of a
handler methods when it's called as a trap (otherwise, the method
is just a regular function). It's very close to the idea of adding
an argument without the inconvenient/embarassement of placing it.
It's generic, so it solves the issue for future traps. It
obviously comes to the price of some sort of inconsistency,
because adding things to the arguments object hasn't been done
before (not that i'd be aware of, at least).

With the introduction of the spread operator in Harmony, the intention is to move away from using |arguments|. I wouldn't want to tie the proxies proposal this tight to the |arguments| object. Also, as you note, there is no precedent for it. I would also claim that it still doesn't feel like a natural place to store the proxy.

Ok. I didn't know the intention to go away from the arguments object.

Within a handler method, there would be a subtle difference. Let's
take the example of a derived trap implementation (has trap):
--------------------------
var h = {
getPropertyDescriptor: function(name){return arguments.currentProxy;},
has: function(name) { return !!this.getPropertyDescriptor(name); }
}
var p = Proxy.create(h);

'a' in p; // false
--------------------------
When 'has' is trapped, it calls the handler method like a regular
method, so there is no special treatment to arguments
(arguments.currentProxy === undefined =>
!!this.getPropertyDescriptor(name) === false)

If 'has' was implemented as:
has: function(name) { return
!!Object.getPropertyDescriptor(arguments.currentProxy, name); }
then the 'getPropertyDescriptor' handler would be called as a
trap, so 'currentProxy' would be 'magically' added to the
arguments object.


I'm realizing that my idea raises a question that had no reason to
exist before.
Before, proxy weren't accessible from handler methods, so in order
to implement derived traps, one handler method could only have
access to other handler methods through 'this'. However, if
'proxy' becomes accessible within handler methods (no matter how),
it could be considered to reimplement derived traps with actual
fundamental trap trapping. I would personnally feel this to be
more consistent with the idea of a mapping between traps and
internal methods.
However, as of today, besides my arguments-proxy-patching idea, I
do not see how to notice a difference between
'this.getPropertyDescriptor(/*proxy, */ name)' (proxy argument
depending on how we give proxy access to handler methods) and
being trapped on 'Object.getPropertyDescriptor(proxy, name)'.

There should not be a difference. Any code that has access to both a proxy and to its handler object can choose to either invoke the traps explicitly on the handler, or implicitly via the proxy object. I don't see why a trap should be able to distinguish both cases.

I was just saying that I didn't see any way that they can distinguish and then implicitely asking if anyone else would see any. But the way you explain it make clear that there is no reason code could distinguish.

On "should code be able to distinguish", I have no strong opinion. It might be some way to enrich the proxy API. Anyway, since it's not possible, the question can be forgotten for now.

Of the proposals listed on the strawman wiki page, I prefer the "Proxy as additional argument" option. I think consistency between the traps and the Javascript code that they intercept is what matters most in practice, and what will minimize bugs for developers that are not familiar with spec. details. IOW, calls like "Object.defineProperty(proxy, name, pd)" would then be trapped by "function defineProperty(proxy, name, pd) {...}". Principle of least surprise and all that :-)

Actually, under this option, you list "less consistent" as a con. "Consistency" is used in two different points of view:

  • The consistency listed as a con in the proxy handler API consistency
  • The consistency you're talking in your paragraph is between the proxy handler API and surface syntax. I tend to agree that

About the last option "Proxy as argument only for particular traps", I think that this option should be considered only if the method list contains at least all methods which use prototypal inheritance. This would grow the list to:

  • getPropertyDescriptor
  • getPropertyNames
  • has
  • get
  • set
  • enumerate First, it makes sense, because in order to re-implement inheritance, there is a need to access prototype, which can only be done through Object.getPrototypeOf(proxy). More practically, as an example, the default 'has' trap cannot pass 'proxy' as an argument to the 'getPropertyDescriptor' trap if it hasn't itself a reference to the proxy. (actually, there is a similar issue with get set and enumerate) It should be noted that this option doesn't solve the first motivating use case (shared handler) since some method are disadvantaged regarding, for instance, being able to store per-proxy state.
# Tom Van Cutsem (15 years ago)

Of the proposals listed on the strawman wiki page, I prefer the "Proxy as additional argument" option. I think consistency between the traps and the Javascript code that they intercept is what matters most in practice, and what will minimize bugs for developers that are not familiar with spec. details. IOW, calls like "Object.defineProperty(proxy, name, pd)" would then be trapped by "function defineProperty(proxy, name, pd) {...}". Principle of least surprise and all that :-)

Actually, under this option, you list "less consistent" as a con. "Consistency" is used in two different points of view:

  • The consistency listed as a con in the proxy handler API consistency
  • The consistency you're talking in your paragraph is between the proxy handler API and surface syntax. I tend to agree that

Indeed. I'll try to clarify this.

About the last option "Proxy as argument only for particular traps", I think that this option should be considered only if the method list contains at least all methods which use prototypal inheritance. This would grow the list to:

  • getPropertyDescriptor
  • getPropertyNames
  • has
  • get
  • set
  • enumerate First, it makes sense, because in order to re-implement inheritance, there is a need to access prototype, which can only be done through Object.getPrototypeOf(proxy). More practically, as an example, the default 'has' trap cannot pass 'proxy' as an argument to the 'getPropertyDescriptor' trap if it hasn't itself a reference to the proxy. (actually, there is a similar issue with get set and enumerate)

It should be noted that this option doesn't solve the first motivating use

case (shared handler) since some method are disadvantaged regarding, for instance, being able to store per-proxy state.

Sounds very reasonable. I also think that the most common reason the handler would require access to |proxy| is to properly implement inheritance. In that regard, we could even consider passing the prototype argument immediately to these traps, instead of the proxy (which would decrease the chance of runaway recursion by touching the proxy from within the handler).

As you note, that does prevent the storing-proxy-state-in-weakmaps use case, but I don't have a good feeling of how important this use case is at the moment.