[Harmony proxies] Partial internal proxy state exposure (was: Shared proxy handlers)

# David Bruant (14 years ago)

Le 20/04/2011 17:09, Tom Van Cutsem a écrit :

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

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.

I think that this thread has brought a use case that is different from shared handlers and that is the idea of exposing partial internal proxy state in order to allow programmers to dynamically change a few traps while being able to interact (or rather "be consistent") with other traps (which could be native as in the ForwardingHandler example or not). Hence the thread title change.

Currently, the Handler constructor creates an object. At the own layer is the state of the proxy ("target" property). On the prototype are the traps. Given a handler, if I want to change a trap, I can redefine it at the own layer and shadowing the prototype method with the same name. But within my trap, how can I access the proxy state? Well, by using "this.target". So if I want to redefine the set trap, I can do the following:

var h = new Proxy.Handler({});

h.set = function(rec, name, val, proxy){ var target = this.target; // I am able to retrieve "internal" proxy state /* ... */ }

However, this method doesn't scale well. If a proxy has several internal properties, is it going to expose all of them with one different name each time? My idea is to take the convention to have a unique "state" property. This convention could be standardized by saying that the "state" property name is reserved for people to use (no obligation whatsoever) and warn them that any other property name could be used in the future, so they should be careful of handler property namespace pollution. Now that I think about it, in the case where proxy traps are shared in the prototype and that there is no shared handler but rather a handler per proxy, then the state can safely be stored at the handler object own layer. This technique won't work if we want actual shared handlers.

Sean Eagan is taking another approach that will be compatible with shared handlers.

I'd like to say a few more words on dynamic trap change. I see two cases with different constraints:

  • Decorator pattern (logger, etc.) In that case, redefining the trap by wrapping the default method and decorate it should be enough. No need to access the internal proxy state. Passing arguments and this binding should be enough. This is what Sean did l.14-17 (gist.github.com/929185). He didn't extract the actual internal state to manipulate it.
  • Actual internal state manipulation Here is the case where there is a need to access the internal proxy state. For instance, if we want to log the result of Object.isExtensible(target), then we need access to the target. This is the class of use cases we are targetting here in my opinion.

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.

Just for the sake of completeness, there is another case that hasn't been described:

  • N targets, N handler objects with new traps (new function objects identities) for each handler object. This is the nothing-shared case. This was the case I wanted to avoid when first thinking about shared handlers. But I agree that the shared prototype idea is a very good solution to that problem too. Your comparison actually convinced me that sharing methods on the prototype and storing state at the handler object own layer could be the way to go.
# Tom Van Cutsem (14 years ago)

David,

You raise valid points, but I can't help but think that many of these issues are no longer related to proxies per-se, but more to "principles on how to develop an OO framework / hierarchy in Javascript". The particular set-up we are discussing with per-proxy handler objects that encapsulate state, and delegate to a shared handler prototype for their methods is just an instance of what I think of as the general pattern of how to model class-based inheritance in JS.

Other class-based languages have visibility modifiers to make state private in instances. Javascript doesn't have such a feature. But should we go out of our way in the Proxy API to deal with this? I'm not so sure. It would be good if we could look to other parts of the spec for guidance on how to approach this OO design. Unfortunately I can't currently think of any similar such API.

Cheers, Tom

2011/4/22 David Bruant <david.bruant at labri.fr>