Direct proxies strawman

# Tom Van Cutsem (13 years ago)

A couple of weeks ago, Mark and I sat together to work on a number of open issues with proxies, in particular, how to make proxies work better with non-configurable properties and non-extensible objects. The result is what we call "direct proxies": in our new proposal, a proxy is always a wrapper for another "target" object. By slightly shifting our perspective on proxies in this way, many of the earlier open issues go away, and the overhead of proxies may be substantially reduced in some cases.

I just finished a first draft of the strawman: < strawman:direct_proxies>. Feedback

welcome.

A prototype implementation that builds upon FF7 proxies is available also: < code.google.com/p/es-lab/source/browse/trunk/src/proxies/DirectProxies.js

(note: I still need to thoroughly test this code, but it's otherwise an accurate description of the semantics of direct proxies in terms of current proxies.)

Mark and I will present this proposal in detail at the TC39 November meeting. In the meantime, it may use some es-discuss eyeballs.

To whet your appetite, some advantages of direct proxies w.r.t. the current proposal:

  • Enable emulation of non-configurable properties.
  • Enable emulation of non-extensible, sealed and frozen objects.
  • Proxies inherit [[Class]] from target. Enables faithful wrapping of Host objects, Arrays, etc.
  • “Fixing” a proxy (i.e. stopping the handler from trapping), is decoupled from “protecting” a proxy (making it non-extensible, sealed or frozen).
  • No need to distinguish between object and function proxies anymore.
  • All traps become optional.
  • Wrapping an existing object using a direct proxy creates less overhead, no need to pass through an explicit ForwardingHandler anymore.
  • Proxies can't violate non-configurability / non-extensibility invariants. This in turn enables safe transformation of regular objects into proxies (Proxy.attach)
  • The default forwarding handler becomes a stateless singleton object.

Also, thanks to David Bruant for being an early sounding board and providing some useful API tweaks.

# William Edney (13 years ago)

Tom -

Been following the proxy work for quite a while and, as the original requestor for noSuchMethod to be added to Spidermonkey, I have been keen to see the functionality as I describe here:

bugzilla.mozilla.org/show_bug.cgi?id=683218#c8

I'm not sure as to whether direct proxies would solve that problem for me. AFAICT, they wouldn't...

Comments?

# David Bruant (13 years ago)

Le 17/10/2011 21:43, William Edney a écrit :

Tom -

Been following the proxy work for quite a while and, as the original requestor for noSuchMethod to be added to Spidermonkey, I have been keen to see the functionality as I describe here:

bugzilla.mozilla.org/show_bug.cgi?id=683218#c8

I'm not sure as to whether direct proxies would solve that problem for me. AFAICT, they wouldn't...

Direct proxies can be used to implement the previous proposal and the previous proposal can be used to implement direct proxies, so direct proxies cannot solve more than the previous proxy proposal could.

I gave an answer to your concern. See bugzilla.mozilla.org/show_bug.cgi?id=683218#c13

Direct proxies have access to the target (which is mostly the equivalent to accessing the proxy in the previous design). Combined with the proto operator, it seems that you could mostly solve your use case. What would be missing?

Additionally, Proxy.startTrapping could be helpful to you (though it may be too controversial. We'll see)

# William Edney (13 years ago)

David -

You did give an answer on that bug, and I do appreciate it :-). I wanted to raise the issue here for folks that are not subscribed to that bug, since it seems (thankfully) that the proposed functionality hasn't been frozen yet.

I guess what would be missing for me is that, unless I misunderstand your proposed syntax there, the creation of the 'Array' using the '<|' operator would still require the creator to use said syntax. What I'm looking for is "transparent" behavior (i.e. if I'm using a third-party library that uses '[]' to create Arrays, for instance, it 'automagically' gets my Proxy/backstop behavior).

'Proxy.startTrapping()' sounds very interesting... a quick search didn't turn up anything. Do you have any pointers to discussions, strawmen, etc.?

# David Bruant (13 years ago)

Le 17/10/2011 23:52, William Edney a écrit :

(...)

'Proxy.startTrapping()' sounds very interesting... a quick search didn't turn up anything. Do you have any pointers to discussions, strawmen, etc.?

Sorry, I forgot the link. It's on the direct proxy proposal: strawman:direct_proxies#proxy.starttrapping_a.k.a._proxy.attach

# Erik Arvidsson (13 years ago)

I find this proposal a lot cleaner than the original and the usage is a lot simpler than Proxy.create and Proxy.createFunction. Great job as always.

My main interest lies in Proxy.startTrapping/Proxy.attach. This is a high priority to enable data binding. However, I feel like that section on the wiki page is a bit hand wavy. For example, what is the target in that case. Also, it is important that attaching to an existing proxy works and all handlers get invoked.

# David Herman (13 years ago)

this looks very promising. Some comments below; quoting the wiki page inline.

  • target is the object which the direct proxy wraps

Just checking: presumably this proposal doesn't allow for target to be a primitive, right? (Other than the special case of null you mention later.) I.e., this is still just a spec for virtualized objects, not for virtualized primitives.

Unlike in the original proxy proposal, any non-configurable properties reported by the handler of a direct proxy are also stored on the target object. In doing so, the proxy ensures that the target object always “matches” the proxy as far as non-configurability of properties is concerned.

I'm confused about this. Do you mean that, if the proxy provides a property descriptor that said .foo is non-configurable, the semantics automatically adds the property to the target and from that point on, all operations involving .foo go through the target instead of through the handler? Does this let you faithfully virtualize the .length property of arrays? How? You still want to intercept gets and sets, but you want getOwnPropertyDescriptor to say that it's a data property, not an accessor property.

When a direct proxy is made non-extensible, so is its target. Once a direct proxy is non-extensible, all properties reported by the handler are stored on the target, regardless of their configurability. This ensures that the handler cannot report any “new” properties, since the target object will now reject any attempt to add new properties.

I'm still confused about when operations go through the handler and when they go through the target. If they can still go through the handler after making the proxy non-extensible, then what's to stop the handler from making it look like new properties are appearing?

A direct proxy may acquire some of its internal properties from its target object. This includes the [[Class]] and [[Prototype]] internal properties:

This is awesome.

  • typeof aProxy is equal to typeof target.

To make sure I'm following along correctly: the typeof result can only be "object" or "function", right?

We could even allow for direct proxies to acquire non-standard internal properties from their target object. This could be a useful principle when wrapping host objects.

This seems important in order to make host methods work, e.g., the ones that access the [[Value]] property. I guess you could code around it by proxying those methods as well?

For Direct Proxies, rather than adopting the handler_access_to_proxy strawman that adds the proxy itself as an additional argument to all traps, we propose to instead add the target as an additional, last, argument to all traps. That allows the handler to interact with the target that it implicitly wraps.

You still might want access to the identity of the proxy itself, e.g., to store the object in a WeakMap. But as you guys have pointed out, you can store this in the handler object, which can still inherit its traps from prototype methods. So I guess this isn't critical.

The protect trap no longer needs to return a property descriptor map...

This seems like a big deal to me. The property descriptor map could potentially be quite large.

Proxy.stopTrapping()

This one makes me a little queasy. I'm sure you guys already thought of and dismissed the possibility of having Proxy.for(...) return a pair of a proxy and a stopTrapping() thunk that's tied to the one proxy. That's obviously got wretched ergonomics. But I'm not crazy about the idea of drive-by deproxification. Just my initial reaction, anyway.

Both the call and new trap are optional and default to forwarding the operation to the wrapped target function.

Nicely done! Much cleaner than Proxy.create and Proxy.createFunction.

Proxy.startTrapping() (a.k.a. Proxy.attach)

I don't fully understand how this one works. Is it essentially a Smalltalk become, in the sense that the existing object is turned into a proxy, and its guts (aka brain) now become a different object that the proxy uses as its target?

So, this has obvious appeal; for example, it addresses the data binding use cases.

But I have some serious reservations about it. For one, tying the notion of "becomeability" to extensibility seems sub-optimal. I'm not sure you always want an object to be non-extensible when you want it to be non-becomeable. And a serious practical issue is whether host objects could be becomeable. I'm pretty sure that's going to be a serious problem for implementations.

It’s still as easy to create such “virtual” proxies: just pass a fresh empty object (or perhaps even null?)

Please, make it null. So much more pleasant (and avoids needless allocation).

(The only downside of allowing null to mean "no target" would be if you wanted to future-proof for virtualizable primitives, including a virtualizable null.)

Proxy.create = function(handler, proto) { return Proxy.for(Object.create(proto), handler); }; Proxy.createFunction = function(handler, call, opt_construct) { var extHandler = Object.create(handler); extHandler.call = call; extHandler.new = opt_construct; return Proxy.for(call, extHandler); };

Doesn't this leak the property table of the call function? ISTM you want:

Proxy.createFunction = function(handler, call, opt_construct) {
    var extHandler = Object.create(handler);
    extHandler.call = call;
    extHandler.new = opt_construct;
    var target = function(...args) { return call(...args); }; // new object
    return Proxy.for(target, extHandler);
}

We propose to bind the singleton forwarding handler to Proxy.forward.

Singleton within a single window (aka loader), or singleton shared across all windows (aka loaders)? I would expect the former.

Advantages w.r.t. existing proxies

These are pretty awesome. :)

  • Proxy.for could be renamed to Proxy.create.

If we were not in a module setting, I would say just make Proxy a function (which behaves the same whether called via |new| or not), now that there's only one function needed. But we still need to figure out our naming conventions for the module-ized standard library, so maybe this is best decided after we have made headway on hat.

  • We may still want to make all traps mandatory rather than defaulting to forwarding to the target:

Please, let's keep them optional. The future-proofing is going to be crucial, I'd wager. And it will make an enormous difference in ergonomics and palatability. The difference between saying here's a simple proxy:

Proxy.for(obj, { get: function(key) { alert("getting " + key + "!") } })

and the current state of affairs is pretty huge.

# William Edney (13 years ago)

David -

So I assume this would allow something like:

arrayProtoProxy = Proxy.for(Array.prototype, {...blah...}); // Or even Object.prototype... Proxy.startTrapping(arrayProtoProxy);

Then I could do:

myArr = []; myArr.methodArraysDontUnderstand();

And that would be trapped by the stuff in {...blah...} ???

If so, then I'm very happy :-).

# Erik Arvidsson (13 years ago)

On Mon, Oct 17, 2011 at 15:45, William Edney <bedney at technicalpursuit.com> wrote:

So I assume this would allow something like:

arrayProtoProxy = Proxy.for(Array.prototype, {...blah...});

This probably has to be:

arrayProtoProxy = Proxy.for([], {...blah...});

since the magic collection setter is on the instance and not on the prototype

# David Bruant (13 years ago)

About functions, what is the benefit of having a call trap? A "function proxy" (typeof === function) can only be created is the target contains a [[Call]] internal method (by definition of typeof and the fact that it's forwarded from the proxy to the target). So, why having a trap instead of just reusing the internal [[call]] of the target? Reusing would enforce typeof stability and avoids the hazard of the trap being deleted on-the-fly. Also, since we're dealing with functions, changing from f1 to f2 under a condition c can be done within the function body with something like "if(c){f1()}else{f2()}", so there is no lack in semantics to force the [[Call]] at creation.

One point not mentionned is what happens with objects which would have a 'new' trap.

var o = {} var p = Proxy.for(o, {new:function(){...}}) ) // typeof p === 'object'; // === typeof o var q = new p(); // ?

Constructable objects are not forbidden by the ES spec but i think that we've never seen such a creature.

Based on the fact that the proposal is in one page and according to the reactions and questions so far, I think that it should be clarified that the strawman contains 3 (maybe more?) different proposals:

  1. Proxy.for
  2. Proxy.stopTrapping (which could have been proposed independently if the fixed handler strawman had been accepted)
  3. Proxy.startTrapping The 3 can be accepted and rejected independently. That's my understanding at least.

Overall, I'm very enthusiastic about Proxy.for as it solves many many problems by design. It covers the main use case (forwarding proxies) and will allow a very efficient implementation of it. Regarding the recent question of [[DefaultValue]], a trap could be added or it could just be forwarded to the target.

# Tom Van Cutsem (13 years ago)

2011/10/18 David Herman <dherman at mozilla.com>

Hi Tom, this looks very promising. Some comments below; quoting the wiki page inline.

  • target is the object which the direct proxy wraps

Just checking: presumably this proposal doesn't allow for target to be a primitive, right? (Other than the special case of null you mention later.) I.e., this is still just a spec for virtualized objects, not for virtualized primitives.

Correct.

Unlike in the original proxy proposal, any non-configurable properties reported by the handler of a direct proxy are also stored on the target object. In doing so, the proxy ensures that the target object always “matches” the proxy as far as non-configurability of properties is concerned.

I'm confused about this. Do you mean that, if the proxy provides a property descriptor that said .foo is non-configurable, the semantics automatically adds the property to the target and from that point on, all operations involving .foo go through the target instead of through the handler? Does this let you faithfully virtualize the .length property of arrays? How? You still want to intercept gets and sets, but you want getOwnPropertyDescriptor to say that it's a data property, not an accessor property.

If a handler says a property "foo" is non-configurable, "foo" will indeed be automatically added to the target (think of the proxy as trying to keep the target and the virtual object described by the handler "in sync"). However, all further operations involving "foo" still trap the handler.

.length on arrays can be fully intercepted, but a proxy can't violate the non-configurability constraints on it (which it shouldn't want to anyway). An example:

var a = []; var arrayProxy = Proxy.for(a, { get: function(name, target, proxy) { console.log("got: "+name); return target[name]; }, getOwnPropertyDescriptor: function(name, target) { if (name === "length") { return { value: 22, writable: true, configurable: false }; } return Object.getOwnPropertyDescriptor(target, name); } });

arrayProxy.length // got: "length" Object.getOwnPropertyDescriptor(arrayProxy, "length") // returns {value:22,writable:true,configurable:false,enumerable:false} // also calls Object.defineProperty(a, "length", {value:22,writable:true,configurable:false}) // which should succeed a.length // 22

(I tried to briefly test this in tracemonkey and v8, but it seems support for updating "length" is very shaky: v8:

var a = [] Object.defineProperty(a,"length",{value:22}) a.length

0 tracemonkey:

var a = [] Object.defineProperty(a,"length",{value:22})

typein:2: InternalError: defining the length property on an array is not currently supported

so don't expect the above example to work just yet)

When a direct proxy is made non-extensible, so is its target. Once a direct proxy is non-extensible, all properties reported by the handler are stored on the target, regardless of their configurability. This ensures that the handler cannot report any “new” properties, since the target object will now reject any attempt to add new properties.

I'm still confused about when operations go through the handler and when they go through the target. If they can still go through the handler after making the proxy non-extensible, then what's to stop the handler from making it look like new properties are appearing?

The rule is simple: as long as stopTrapping was not successfully invoked on a proxy, the proxy always keeps trapping, regardless of extensibility of the target.

However, a direct proxy will inspect the result of all handler traps and will try to keep the target "in sync" with the handler. So, if the proxy (and thus its target) is made non-extensible, and the handler subsequently reports a new property, the proxy will try to add that property to the target, which will fail, because the target is now non-extensible. This is why the proxy cannot violate the non-configurable/non-extensible invariants of the target.

A direct proxy may acquire some of its internal properties from its target object. This includes the [[Class]] and [[Prototype]] internal properties:

This is awesome.

  • typeof aProxy is equal to typeof target.

To make sure I'm following along correctly: the typeof result can only be "object" or "function", right?

Indeed. Although if there would exist a host object h that according to ES5 11.4.3. returns something else, there is room in the direct proxies spec to have typeof Proxy.for(h,{}) also return that custom string.

We could even allow for direct proxies to acquire non-standard internal properties from their target object. This could be a useful principle when wrapping host objects.

This seems important in order to make host methods work, e.g., the ones that access the [[Value]] property. I guess you could code around it by proxying those methods as well?

What's the [[Value]] property? I'm not sure I understand.

For Direct Proxies, rather than adopting the handler_access_to_proxy strawman that adds the proxy itself as an additional argument to all traps, we propose to instead add the target as an additional, last, argument to all traps. That allows the handler to interact with the target that it implicitly wraps.

You still might want access to the identity of the proxy itself, e.g., to store the object in a WeakMap. But as you guys have pointed out, you can store this in the handler object, which can still inherit its traps from prototype methods. So I guess this isn't critical.

Also, in those cases where you can assume a 1-to-1 mapping between a proxy and its handler, you might as well use the identity of the handler. Or, presumably, what would make even more sense in general is to use the identity of the target, not the proxy.

The protect trap no longer needs to return a property descriptor map...

This seems like a big deal to me. The property descriptor map could potentially be quite large.

Absolutely.

Proxy.stopTrapping()

This one makes me a little queasy. I'm sure you guys already thought of and dismissed the possibility of having Proxy.for(...) return a pair of a proxy and a stopTrapping() thunk that's tied to the one proxy. That's obviously got wretched ergonomics. But I'm not crazy about the idea of drive-by deproxification. Just my initial reaction, anyway.

I agree 200%. To me, a much better interface for stopTrapping would be:

var {proxy, stopTrapping} = Proxy.temporaryFor(target, handler); // ... use the proxy stopTrapping(); // switch off the proxy unconditionally, no need to ask its handler first

That would remove the need for a 'stopTrapping' trap (at the expense of an arcane "temporaryFor" proxy-creation-function). IMHO removing 'stopTrapping' from the public API of a handler is a good thing. It better isolates the feature to the few experts that would need it.

We didn't propose it because we suspected pushback against this type of API. In any case, if we should stick to a trap, by default that trap should reject the request. Obviously, the reverse would allow arbitrary clients to switch off proxies, which would be a huge security hole.

Both the call and new trap are optional and default to forwarding the operation to the wrapped target function.

Nicely done! Much cleaner than Proxy.create and Proxy.createFunction.

Proxy.startTrapping() (a.k.a. Proxy.attach)

I don't fully understand how this one works. Is it essentially a Smalltalk become, in the sense that the existing object is turned into a proxy, and its guts (aka brain) now become a different object that the proxy uses as its target?

That is exactly right, although I think Smalltalk's "become" is more powerful. In the Proxy.startTrapping case, all pointers to target now become pointers to a fresh proxy object. That's important implementation-wise. BTW, I added some figures to the strawman page that should help clarify things.

So, this has obvious appeal; for example, it addresses the data binding use cases.

That is indeed the main use case.

But I have some serious reservations about it. For one, tying the notion of "becomeability" to extensibility seems sub-optimal. I'm not sure you always want an object to be non-extensible when you want it to be non-becomeable. And a serious practical issue is whether host objects could be becomeable. I'm pretty sure that's going to be a serious problem for implementations.

I agree in principle that "attachability" or "becomeability" is distinct from extensibility. But from a usability POV, what's the alternative? To introduce yet another bit, and to put the onus on defensive objects by requiring them to do Object.freeze(Object.cantTrap(myObject))? To me, that seems worse than tying non-extensibility to non-becomeability.

You're right that startTrapping + host objects = potential trouble. My answer there would be that host objects, like non-extensible objects, are allowed to reject the request.

It’s still as easy to create such “virtual” proxies: just pass a fresh empty object (or perhaps even null?)

Please, make it null. So much more pleasant (and avoids needless allocation).

(The only downside of allowing null to mean "no target" would be if you wanted to future-proof for virtualizable primitives, including a virtualizable null.)

Avoiding needless allocation is why I proposed null, indeed. But there are some unresolved issues here: if the target is null, how should the proxy determine its typeof, [[Class]] and [[Prototype]]? This needs more thought.

Proxy.create = function(handler, proto) { return Proxy.for(Object.create(proto), handler); }; Proxy.createFunction = function(handler, call, opt_construct) { var extHandler = Object.create(handler); extHandler.call = call; extHandler.new = opt_construct; return Proxy.for(call, extHandler); };

Doesn't this leak the property table of the call function? ISTM you want:

Yes and no. There's a little note under that implementation stating that the above is only an accurate emulation of Proxy.createFunction if the handler implements all traps. For instance, the ForwardingHandler implements all traps, and then calling the above Proxy.createFunction as follows will not leak properties of the call trap as properties of the target:

Proxy.createFunction(new ForwardingHandler(targetFunction), targetFunction);

IMHO, if we would buy into direct proxies, I see no need to continue supporting Proxy.create{Function}.

Proxy.createFunction = function(handler, call, opt_construct) {
    var extHandler = Object.create(handler);
    extHandler.call = call;
    extHandler.new = opt_construct;
    var target = function(...args) { return call(...args); }; // new

object return Proxy.for(target, extHandler); }

We propose to bind the singleton forwarding handler to Proxy.forward.

Singleton within a single window (aka loader), or singleton shared across all windows (aka loaders)? I would expect the former.

The former seems fine. I used the word "singleton" only to stress that Proxy.forward can be stateless. There's no need to generate new instances of it as was the case with the ForwardingHandler.

Advantages w.r.t. existing proxies

These are pretty awesome. :)

  • Proxy.for could be renamed to Proxy.create.

If we were not in a module setting, I would say just make Proxy a function (which behaves the same whether called via |new| or not), now that there's only one function needed. But we still need to figure out our naming conventions for the module-ized standard library, so maybe this is best decided after we have made headway on hat.

Good idea.

  • We may still want to make all traps mandatory rather than defaulting to forwarding to the target:

Please, let's keep them optional. The future-proofing is going to be crucial, I'd wager. And it will make an enormous difference in ergonomics and palatability. The difference between saying here's a simple proxy:

Proxy.for(obj, { get: function(key) { alert("getting " + key + "!") }

})

and the current state of affairs is pretty huge.

I agree. The distinction between fundamental vs. derived traps and the advantages that this brings (only having to implement the fundamental traps) can easily be offloaded into a little userland library that facilitates working with Proxies.

Thanks for the feedback, Tom

# Tom Van Cutsem (13 years ago)

Even simpler:

var arrayProxy = Proxy.for([], { get: function(name, target) { if (!(name in target)) { return function(...args) { /* no such method behavior here */ } } } });

That assumes you are the creator of the array. If it already exists, Proxy.startTrapping would be able to turn the array into a proxy wrapping the array. But, as David Bruant mentioned, Proxy.startTrapping is still controversial.

Cheers, Tom

2011/10/18 Erik Arvidsson <erik.arvidsson at gmail.com>

# Tom Van Cutsem (13 years ago)

2011/10/18 David Bruant <bruant.d at gmail.com>

About functions, what is the benefit of having a call trap? A "function proxy" (typeof === function) can only be created is the target contains a [[Call]] internal method (by definition of typeof and the fact that it's forwarded from the proxy to the target). So, why having a trap instead of just reusing the internal [[call]] of the target?

Because a trap typically wants to intercept operations to do additional stuff (logging, access control, ...). If there were no call trap, the proxy would have no place to intercept [[Call]]. Of course, you could install the call trap as the target of the proxy, but that is not ideal: the proxy will then enforce that all non-configurable properties reported by the handler are also present on the call-trap-as-target, which is conflating base- and meta-levels: in general, the call trap's properties have nothing to do with the target function's properties.

Reusing would enforce typeof stability and avoids the hazard of the trap being deleted on-the-fly. Also, since we're dealing with functions, changing from f1 to f2 under a condition c can be done within the function body with something like "if(c){f1()}else{f2()}", so there is no lack in semantics to force the [[Call]] at creation.

typeof is stable in the current proposal. The call trap being deleted on-the-fly does not impact the typeof result. I was concerned about this too, at first. MarkM pointed out that we can derive |typeof proxy| from |typeof target|, regardless of whether or not a call trap is present on the handler.

One point not mentionned is what happens with objects which would have a 'new' trap.

var o = {} var p = Proxy.for(o, {new:function(){...}}) ) // typeof p === 'object'; // === typeof o var q = new p(); // ?

Constructable objects are not forbidden by the ES spec but i think that we've never seen such a creature.

The last line of code will throw a "TypeError: o is not a constructor", just like calling "new o()" would.

Based on the fact that the proposal is in one page and according to the reactions and questions so far, I think that it should be clarified that the strawman contains 3 (maybe more?) different proposals:

  1. Proxy.for
  2. Proxy.stopTrapping (which could have been proposed independently if the fixed handler strawman had been accepted)
  3. Proxy.startTrapping The 3 can be accepted and rejected independently. That's my understanding at least.

This is absolutely true. Thanks for clarifying.

Overall, I'm very enthusiastic about Proxy.for as it solves many many problems by design. It covers the main use case (forwarding proxies) and will allow a very efficient implementation of it. Regarding the recent question of [[DefaultValue]], a trap could be added or it could just be forwarded to the target.

Indeed.

# David Bruant (13 years ago)

Le 18/10/2011 11:34, Tom Van Cutsem a écrit :

2011/10/18 David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>>

About functions, what is the benefit of having a call trap?
A "function proxy" (typeof === function) can only be created is
the target contains a [[Call]] internal method (by definition of
typeof and the fact that it's forwarded from the proxy to the
target). So, why having a trap instead of just reusing the
internal [[call]] of the target?

Because a trap typically wants to intercept operations to do additional stuff (logging, access control, ...).If there were no call trap, the proxy would have no place to intercept [[Call]]. Of course, you could install the call trap as the target of the proxy, but that is not ideal: the proxy will then enforce that all non-configurable properties reported by the handler are also present on the call-trap-as-target, which is conflating base- and meta-levels: in general, the call trap's properties have nothing to do with the target function's properties.

Reusing would enforce typeof stability and avoids the hazard of
the trap being deleted on-the-fly.
Also, since we're dealing with functions, changing from f1 to f2
under a condition c can be done within the function body with
something like "if(c){f1()}else{f2()}", so there is no lack in
semantics to force the [[Call]] at creation.

typeof is stable in the current proposal. The call trap being deleted on-the-fly does not impact the typeof result. I was concerned about this too, at first. MarkM pointed out that we can derive |typeof proxy| from |typeof target|, regardless of whether or not a call trap is present on the handler.

Ok for typeof. But there are other places where [[Call]] is used and the proxy is expected to (indirectly) expose it. For instance bind:

var fpb = Function.prototype.bind; var bind = fpb.bind(fpb); var p = Proxy.for(function(){}, {}); // purposefully no 'call' trap var p2 = bind(p, {}); // ?

Here, bind will look for an internal [[Call]] from p. What is it? It cannot be the call trap since this one doesn't exist. Fallback to target.[[call]]?

If target.[[Call]] is a fallback, it means that the internal [[call]] of an object can be changed... actually, just changing the call trap makes [[call]] dynamic. I'm not sure what are the ramifications of this. For instance, when binding a function, should it take the [[call]] value at bind call or the dynamic one (current ES5.1 definition says "dynamic", but both are equivalent with today's objects)

# Andreas Rossberg (13 years ago)

On 17 October 2011 21:15, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

I just finished a first draft of the strawman: strawman:direct_proxies. Feedback welcome.

Hi Tom,

this looks nice, but a couple of things confuse me.

First, Proxy.startTrapping (a.k.a. Proxy.attach). As far as I can see, this implies a significantly more general 'become' operation than the current semantics. I don't see how we can implement that without substantial (and potentially costly) changes to VMs, like support for transparent forwarding references. I also share Dave's concerns regarding conflation of "non-attachability" and "non-extensibility".

Proxy.stopTrapping worries me, too, especially in combination with attaching. It seems like I could create a proxy p, then later want to deactivate trapping for my handler. But in the meantime, somebody else morphed my p into his own proxy by invoking startTrapping(p). So stopTrapping(p) would deactivate his handler, not mine. So, the proposed interface seems broken to me.

Finally, I'm not sure I fully understand the performance implications of direct proxies vs the current proposal. From looking at your prototype implementation, it seems that we need quite a number of additional checks and calls, even for non-fixed properties. Can you perhaps quantify that overhead a bit?

Thanks,

# Tom Van Cutsem (13 years ago)

2011/10/18 David Bruant <bruant.d at gmail.com>

Ok for typeof. But there are other places where [[Call]] is used and the proxy is expected to (indirectly) expose it. For instance bind:

var fpb = Function.prototype.bind; var bind = fpb.bind(fpb); var p = Proxy.for(function(){}, {}); // purposefully no 'call' trap var p2 = bind(p, {}); // ?

Here, bind will look for an internal [[Call]] from p. What is it? It cannot be the call trap since this one doesn't exist. Fallback to target.[[call]]?

No, the way I see it, for a direct proxy wrapping a target whose typeof is "function", that proxy's internal [[Call]] tries to invoke the call trap, so its spec would be something like:

[[Call]] ( Receiver, Args )

  1. Let H be the [[Handler]] property of the proxy
  2. Let callTrap be the result of calling the [[Get]] internal method on H passing "call" as an argument
  3. If callTrap is undefined or IsCallable(callTrap) is false, throw a TypeError
  4. Return the result of calling the [[Call]] internal method of callTrap, passing Receiver and Args as arguments

'bind' will wrap the above [[Call]] method, which I think works out fine.

# Andreas Rossberg (13 years ago)

On 18 October 2011 17:08, David Bruant <bruant.d at gmail.com> wrote:

Ok for typeof. But there are other places where [[Call]] is used and the proxy is expected to (indirectly) expose it. For instance bind:

var fpb = Function.prototype.bind; var bind = fpb.bind(fpb); var p = Proxy.for(function(){}, {}); // purposefully no 'call' trap var p2 = bind(p, {}); // ?

Here, bind will look for an internal [[Call]] from p. What is it? It cannot be the call trap since this one doesn't exist. Fallback to target.[[call]]?

If target.[[Call]] is a fallback, it means that the internal [[call]] of an object can be changed... actually, just changing the call trap makes [[call]] dynamic. I'm not sure what are the ramifications of this. For instance, when binding a function, should it take the [[call]] value at bind call or the dynamic one (current ES5.1 definition says "dynamic", but both are equivalent with today's objects)

I don't think the presence of [[Call]] itself is "dynamic". It's always there, but it checks for the presence of the call trap.

# Tom Van Cutsem (13 years ago)

2011/10/18 Andreas Rossberg <rossberg at google.com>

First, Proxy.startTrapping (a.k.a. Proxy.attach). As far as I can see, this implies a significantly more general 'become' operation than the current semantics. I don't see how we can implement that without substantial (and potentially costly) changes to VMs, like support for transparent forwarding references. I also share Dave's concerns regarding conflation of "non-attachability" and "non-extensibility".

Your concerns are justified and this is why we need implementors to study the strawman. I'm not an implementor, so I have no clue as to the actual implementation costs of supporting Proxy.startTrapping. Yes, it is a very powerful operation. Not as powerful as Smalltalk's "become", but getting close. Since the target would become a "fresh" proxy object, perhaps tricks similar to those used to make the old "fix()" behavior work could be used (swapping the internals of two objects). I realize this is highly VM-specific.

Proxy.stopTrapping worries me, too, especially in combination with attaching. It seems like I could create a proxy p, then later want to deactivate trapping for my handler. But in the meantime, somebody else morphed my p into his own proxy by invoking startTrapping(p). So stopTrapping(p) would deactivate his handler, not mine. So, the proposed interface seems broken to me.

Good point. Yet another reason why I prefer the alternate Proxy.temporaryFor API I sketched in reply to Dave Herman. That API does not necessarily suffer from this issue.

I'm aware that Proxy.{stop,start}Trapping are controversial features. As David mentioned earlier, direct proxies by themselves don't depend on these features. For Proxy.startTrapping, there is clear value. For Proxy.stopTrapping, it seems to me the only use case would be performance (switching off proxies that the creator knows are no longer useful).

Finally, I'm not sure I fully understand the performance implications of direct proxies vs the current proposal. From looking at your prototype implementation, it seems that we need quite a number of additional checks and calls, even for non-fixed properties. Can you perhaps quantify that overhead a bit?

Taken together, lots of checks are needed, but the amount of checks per trap is fairly limited. Also, most checks reuse the pathways of existing primitives like delete and defineProperty. In the scenario where we are wrapping a target object, we're trading one type of overhead for another: in the current proxy proposal, say I only want to intercept "gets" on a target object. I am still forced to implement a full ForwardingHandler (in JS itself), and override only its 'get' trap. All operations other than "get" incur an unnecessary overhead: the operation must trap the handler, only to have the handler forward the operation anyway (IOW: the operation is lifted to the meta-level, only to be lowered to base-level immediately afterward).

With direct proxies, all traps other than "get" should incur very little overhead. I envision that a direct proxy can very efficiently forward an operation to the target, no need to lift & lower. On the other hand, now the "get" operation will incur an additional check to verify that its reported result is consistent with the target object (only if the property was previously exposed as non-configurable).

I'm speculating at this stage, but I assume that the vast majority of existing JS code does not use Object.getOwnPropertyDescriptor, hence has no way of determining whether a property is non-configurable, hence does not activate the more expensive checks. The overhead then is mostly checking whether the corresponding target object's property is non-configurable.

In any case, I'm not sure that performing micro-benchmarks on my DirectProxies.js prototype implementation will generate useful results: I think it's too dependent on the current proxy implementation, and moreover I'm sure that many of my checks can be done way more efficiently at the VM-level. For instance, to test whether a target property is non-configurable, I check Object.getOwnPropertyDescriptor(target, name).configurable. In a VM I presume this can be made considerably more efficient. No need to allocate a property descriptor just to test configurability, for a start.

Thanks for your feedback, Tom

# Tom Van Cutsem (13 years ago)

2011/10/18 Andreas Rossberg <rossberg at google.com>

Finally, I'm not sure I fully understand the performance implications of direct proxies vs the current proposal. From looking at your prototype implementation, it seems that we need quite a number of additional checks and calls, even for non-fixed properties. Can you perhaps quantify that overhead a bit?

BTW, I intend to summarize all the imperative invariant checks in a more declarative way on the strawman wiki page. That will help give us insight into the intrinsic overhead of the checks.

# Andreas Rossberg (13 years ago)

On 18 October 2011 17:48, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

2011/10/18 Andreas Rossberg <rossberg at google.com>

First, Proxy.startTrapping (a.k.a. Proxy.attach). As far as I can see, this implies a significantly more general 'become' operation than the current semantics. I don't see how we can implement that without substantial (and potentially costly) changes to VMs, like support for transparent forwarding references. I also share Dave's concerns regarding conflation of "non-attachability" and "non-extensibility".

Your concerns are justified and this is why we need implementors to study the strawman. I'm not an implementor, so I have no clue as to the actual implementation costs of supporting Proxy.startTrapping. Yes, it is a very powerful operation. Not as powerful as Smalltalk's "become", but getting close. Since the target would become a "fresh" proxy object, perhaps tricks similar to those used to make the old "fix()" behavior work could be used (swapping the internals of two objects). I realize this is highly VM-specific.

The trick that you can use with the current proposal won't work any longer. In the current semantics, the only two possible 'become' transitions are proxy->JSobject and functionproxy->JSfunction. It is

easy to implement 'become' by just overwriting the object itself. All you need to ensure is that the representation of a proxy is large enough for the representation of a regular object (and similarly for functions).

Proxy.startTrapping, however, goes the other way, with more general transitions. To use the same trick one would need to make any object to which a proxy can be attached at least as large as a proxy (or the other way round, be able to represent proxies in a way that takes at most as much space as the smallest object you can attach to). With some extra work and indirection overhead, we could implement a proxy object in two words. But that might not be good enough, e.g. when attaching to foreign ("host") objects or some internal ones.

Proxy.stopTrapping worries me, too, especially in combination with attaching. It seems like I could create a proxy p, then later want to deactivate trapping for my handler. But in the meantime, somebody else morphed my p into his own proxy by invoking startTrapping(p). So stopTrapping(p) would deactivate his handler, not mine. So, the proposed interface seems broken to me.

Good point. Yet another reason why I prefer the alternate Proxy.temporaryFor API I sketched in reply to Dave Herman. That API does not necessarily suffer from this issue.

Yes, I think that interface, while less slick, is the right one.

Finally, I'm not sure I fully understand the performance implications of direct proxies vs the current proposal. From looking at your prototype implementation, it seems that we need quite a number of additional checks and calls, even for non-fixed properties. Can you perhaps quantify that overhead a bit?

Taken together, lots of checks are needed, but the amount of checks per trap is fairly limited. Also, most checks reuse the pathways of existing primitives like delete and defineProperty. In the scenario where we are wrapping a target object, we're trading one type of overhead for another: in the current proxy proposal, say I only want to intercept "gets" on a target object. I am still forced to implement a full ForwardingHandler (in JS itself), and override only its 'get' trap. All operations other than "get" incur an unnecessary overhead: the operation must trap the handler, only to have the handler forward the operation anyway (IOW: the operation is lifted to the meta-level, only to be lowered to base-level immediately afterward). With direct proxies, all traps other than "get" should incur very little overhead. I envision that a direct proxy can very efficiently forward an operation to the target, no need to lift & lower. On the other hand, now the "get" operation will incur an additional check to verify that its reported result is consistent with the target object (only if the property was previously exposed as non-configurable). I'm speculating at this stage, but I assume that the vast majority of existing JS code does not use Object.getOwnPropertyDescriptor, hence has no way of determining whether a property is non-configurable, hence does not activate the more expensive checks. The overhead then is mostly checking whether the corresponding target object's property is non-configurable. In any case, I'm not sure that performing micro-benchmarks on my DirectProxies.js prototype implementation will generate useful results: I think it's too dependent on the current proxy implementation, and moreover I'm sure that many of my checks can be done way more efficiently at the VM-level. For instance, to test whether a target property is non-configurable, I check Object.getOwnPropertyDescriptor(target, name).configurable. In a VM I presume this can be made considerably more efficient. No need to allocate a property descriptor just to test configurability, for a start.

Thanks for the summary, that is helpful.

I agree that lots of these checks will be far more efficient when implemented in a VM.

[Sorry, have to run now.]

Thanks,

# Mark S. Miller (13 years ago)

On Tue, Oct 18, 2011 at 9:51 AM, Andreas Rossberg <rossberg at google.com>wrote:

Good point. Yet another reason why I prefer the alternate Proxy.temporaryFor API I sketched in reply to Dave Herman. That API does not necessarily suffer from this issue.

Yes, I think that interface, while less slick, is the right one.

Interesting. Naming aside, I also like the Proxy.temporaryFor API better. But when Tom raised it, I argued against it for the reason he mentions: I thought it would run into more resistance. If no one feels strongly against Proxy.temporaryFor, I was wrong to anticipate trouble and we should do that instead.

# Russell Leggett (13 years ago)

I have been thinking that proxies could be a nice way of having private data members instead of the private name proposal. The target would be the full implementation, including all "private" members done as normal properties. Then the implementation object would get wrapped with a proxy, exposing only the "public" members. I suggested this a ways back, and Brendan shot it down as too heavyweight, pointing to private name objects. I was aware of those, but I still feel like having proxies act as a public interface is possibly more intuitive / works more like other languages which have private members. Now with direct proxies, I thought I might suggest it again. If we can bring the cost of proxies down low enough, this might not be such a bad thing, and would possibly make it so private name objects are no longer needed. That would certainly help reduce surface area of new harmony features. Here's a simple example:

var Set = Proxy.for({ constructor(){ this.data = []; } add(elem){ if(this.contains(elem)){ return false; }else{ this.data.push(elem); return true; } } contains(elem){ return this.data.indexOf(elem) >= 0; } },hide("data")); Here, the hide function could just create a handler object which only exposes properties of the target which are not passed as arguments. Or, to match modules a little better, perhaps something like:

var Set = Proxy.for({ ... },export("constructor","add","contains"]));

Which does the reverse and exposes only the properties passed as arguments. If this still seems heavyweight, perhaps this functionality could even be added to the spec for the handler, possibly allowing more optimizations in the vm: var Set = Proxy.for({ ... },{hiddenProperties:["data"]};

Or at the very least, something like Proxy.export / Proxy.hide

# Brendan Eich (13 years ago)

On Oct 18, 2011, at 1:25 PM, Russell Leggett wrote:

I have been thinking that proxies could be a nice way of having private data members instead of the private name proposal. The target would be the full implementation, including all "private" members done as normal properties. Then the implementation object would get wrapped with a proxy, exposing only the "public" members. I suggested this a ways back, and Brendan shot it down as too heavyweight,

Proxies are likely to be heavyweight compared to objects with "static" (singleton per name) private name objects. Just sayin'.

pointing to private name objects. I was aware of those, but I still feel like having proxies act as a public interface is possibly more intuitive / works more like other languages which have private members.

I don't see anything like proxies or other stratified metaprogramming APIs acting like public vs. private in other languages. Could you name a language?

Proxies are awesome but they're a power tool. Data hiding shouldn't need to reach for them, just on general principles -- and just based on all the other languages that support data hiding!

# Russell Leggett (13 years ago)

On Tue, Oct 18, 2011 at 4:28 PM, Brendan Eich <brendan at mozilla.com> wrote:

On Oct 18, 2011, at 1:25 PM, Russell Leggett wrote:

I have been thinking that proxies could be a nice way of having private data members instead of the private name proposal. The target would be the full implementation, including all "private" members done as normal properties. Then the implementation object would get wrapped with a proxy, exposing only the "public" members. I suggested this a ways back, and Brendan shot it down as too heavyweight,

Proxies are likely to be heavyweight compared to objects with "static" (singleton per name) private name objects. Just sayin'.

I imagine this will always be true, but depending on the implementation like making special cases in the vm for a pass through proxy with only additional data hiding semantics, it may not be.

pointing to private name objects. I was aware of those, but I still feel like having proxies act as a public interface is possibly more intuitive / works more like other languages which have private members.

I don't see anything like proxies or other stratified metaprogramming APIs acting like public vs. private in other languages. Could you name a language?

I think proxies as a whole are very powerful and, I think, elegant way of approaching metaprogramming. However, the semantics are already available to use them for data hiding, and if one were to use them as I suggest in my example, would very much resemble data hiding in other languages. Inside the implementation, hidden properties are no different than any other property, but then using additional meta-data only a public interface is exposed. This is not so different than how header files can be used in C, or how export semantics are done for harmony modules. In fact, the way I coded it in my example, it actually looks pretty similar to how erlang or haskell modules export functionality. Its not so much that the implementation is similar as the semantics. When I write a Java class, I am very aware of code I'm writing inside the class, and outside the class. Inside, I have access to everything. Outside, I only have access to what has been exposed through the public keyword. Without really having to worry about what exactly a proxy is, and everything it is capable of, with the right helper functions, I can easily achieve the same effect. Heck, I can even easily create "classes" that work like Dart, where everything that starts with _ is assumed to be private.

var MyClass = defineClass({ publicMethod(){} _privateMethod(){} });

Where the object passed to defineClass is used as the target, and all of its properties without a _ at the beginning are exposed.

Proxies are awesome but they're a power tool. Data hiding shouldn't need to reach for them, just on general principles -- and just based on all the other languages that support data hiding!

I don't necessarily think data hiding should have to reach for them, but the functionality is already there with them. I even think that if it were sanctioned as the right way to do it, we could facilitate it being easy to do and efficient to run. Considering the other challenges that private names pose in to proxies, cloning, etc., perhaps one feature is better than two.

# Brendan Eich (13 years ago)

On Oct 18, 2011, at 2:06 PM, Russell Leggett wrote:

I don't necessarily think data hiding should have to reach for them, but the functionality is already there with them.

Closures are the current best way to hide information in JS. With ES5's standardized accessors, you can have public properties updating private state. The closure cost is comparable to proxy overhead, but better optimized in current VMs, since proxies are new and closures are old.

I even think that if it were sanctioned as the right way to do it, we could facilitate it being easy to do and efficient to run. Considering the other challenges that private names pose in to proxies, cloning, etc., perhaps one feature is better than two.

When all you have is a hammer, everything looks like your thumb. ;-)

The ability to express many things using proxies, or closures, is a package deal. It doesn't mean the best way to hide private data is to use proxies. But if it floats your boat, or helps prototype better data-hiding abstractions, go for it.

# David Herman (13 years ago)

There are other alternatives, such as supporting both alternatives with two different entry points (con: API proliferation), taking an optional boolean flag indicating to return the pair (con: too dynamic a type), taking an optional outparam object (con: what is this? C?). OK, so most of those suggestions suck. :) But there are bigger questions to settle before we need to settle this one.

# David Herman (13 years ago)

We could even allow for direct proxies to acquire non-standard internal properties from their target object. This could be a useful principle when wrapping host objects.

This seems important in order to make host methods work, e.g., the ones that access the [[Value]] property. I guess you could code around it by proxying those methods as well?

What's the [[Value]] property? I'm not sure I understand.

Er, sorry, [[PrimitiveValue]]. (It was called [[Value]] in Edition 3.) Section 8.6.2, Table 9.

But I have some serious reservations about it. For one, tying the notion of "becomeability" to extensibility seems sub-optimal. I'm not sure you always want an object to be non-extensible when you want it to be non-becomeable. And a serious practical issue is whether host objects could be becomeable. I'm pretty sure that's going to be a serious problem for implementations.

I agree in principle that "attachability" or "becomeability" is distinct from extensibility. But from a usability POV, what's the alternative? To introduce yet another bit, and to put the onus on defensive objects by requiring them to do Object.freeze(Object.cantTrap(myObject))? To me, that seems worse than tying non-extensibility to non-becomeability.

One alternative is not to include Proxy.attach. :)

But I need to think about this more; I'm not sure those are the only options. Maybe they are. You've had more time to think about this than I have. :)

It’s still as easy to create such “virtual” proxies: just pass a fresh empty object (or perhaps even null?)

Please, make it null. So much more pleasant (and avoids needless allocation).

(The only downside of allowing null to mean "no target" would be if you wanted to future-proof for virtualizable primitives, including a virtualizable null.)

Avoiding needless allocation is why I proposed null, indeed. But there are some unresolved issues here: if the target is null, how should the proxy determine its typeof, [[Class]] and [[Prototype]]?

"object", "Object", and null. :)

This needs more thought.

Sure, I can believe that. But as long as there are reasonable defaults for all these things, I would much prefer to allow null.

IMHO, if we would buy into direct proxies, I see no need to continue supporting Proxy.create{Function}.

Agreed.

# Tom Van Cutsem (13 years ago)

2011/10/19 David Herman <dherman at mozilla.com>

What's the [[Value]] property? I'm not sure I understand.

Er, sorry, [[PrimitiveValue]]. (It was called [[Value]] in Edition 3.) Section 8.6.2, Table 9.

I see. Yes, I can imagine that if p is a direct proxy wrapping a Date instance, then calling, for instance |Date.prototype.setTime.call(p, aNumber)| would set the wrapped Date's [[PrimitiveValue]].

I think the easiest way to specify such behavior is to have conversion functions take direct proxies into account. I don't see a ToDate conversion function in Section 9, but if it would exist, I can imagine that ToDate, when given a direct proxy p, accesses the [[Target]] t of p and returns ToDate(t). IOW: built-in methods that expect a Date would automatically unwrap proxies.

But I have some serious reservations about it. For one, tying the notion of

"becomeability" to extensibility seems sub-optimal. I'm not sure you always want an object to be non-extensible when you want it to be non-becomeable. And a serious practical issue is whether host objects could be becomeable. I'm pretty sure that's going to be a serious problem for implementations.

I agree in principle that "attachability" or "becomeability" is distinct from extensibility. But from a usability POV, what's the alternative? To introduce yet another bit, and to put the onus on defensive objects by requiring them to do Object.freeze(Object.cantTrap(myObject))? To me, that seems worse than tying non-extensibility to non-becomeability.

One alternative is not to include Proxy.attach. :)

But I need to think about this more; I'm not sure those are the only options. Maybe they are. You've had more time to think about this than I have. :)

Actually, the least worrisome way to introduce Proxy.attach would be via the dual of the sketched "Proxy.temporaryFor" API:

let {obj, attach} = Object.createAttachable(proto, props); // start using obj as a normal object // ... // some time later attach(handler); // now obj becomes a proxy

While this would deal with most of the security hazards and implementation worries of Proxy.attach (the VM knows at creation-time that 'obj' may become a proxy), it would require foresight on behalf of application writers to explicitly "opt-in" to Proxy.attach ahead-of-time, which doesn't serve its main use case (data-binding after-the-facts).

I don't see any other alternatives, but suggestions are welcome.

It’s still as easy to create such “virtual” proxies: just pass a fresh

empty object (or perhaps even null?)

Please, make it null. So much more pleasant (and avoids needless allocation).

(The only downside of allowing null to mean "no target" would be if you wanted to future-proof for virtualizable primitives, including a virtualizable null.)

Avoiding needless allocation is why I proposed null, indeed. But there are some unresolved issues here: if the target is null, how should the proxy determine its typeof, [[Class]] and [[Prototype]]?

"object", "Object", and null. :)

This needs more thought.

Sure, I can believe that. But as long as there are reasonable defaults for all these things, I would much prefer to allow null.

Well, there's no questioning that the above three are all reasonable defaults :-)

Note that, if you'd pass "null" as the target, the handler would not be able to report non-configurable properties or become non-extensible. Without a target, there's no way to enforce the implied invariants.

# Andreas Rossberg (13 years ago)

On 19 October 2011 05:08, David Herman <dherman at mozilla.com> wrote:

It’s still as easy to create such “virtual” proxies: just pass a fresh empty object (or perhaps even null?)

Please, make it null. So much more pleasant (and avoids needless allocation). (The only downside of allowing null to mean "no target" would be if you wanted to future-proof for virtualizable primitives, including a virtualizable null.)

If I understand the proposal correctly, you cannot avoid the allocation, because the target is used as a backing store for fixed properties.

# David Bruant (13 years ago)

Le 19/10/2011 10:57, Andreas Rossberg a écrit :

On 19 October 2011 05:08, David Herman<dherman at mozilla.com> wrote:

It’s still as easy to create such “virtual” proxies: just pass a fresh empty object (or perhaps even null?) Please, make it null. So much more pleasant (and avoids needless allocation). (The only downside of allowing null to mean "no target" would be if you wanted to future-proof for virtualizable primitives, including a virtualizable null.) If I understand the proposal correctly, you cannot avoid the allocation, because the target is used as a backing store for fixed properties.

Indeed. The target is "merged" with the "fixedProps" of the FixedHandler proposal prototype implementation [1]

This is a reason why a lot of checks that appear in Tom's Proxy.for are actually not necessary (and won't be a performance burden) since they are performed by some native code implementing internal methods (ES5.1 - 8.12 for native objects, but also the custom [[DefineOwnProperty]] for arrays if the target is one, etc.)

David

[1] code.google.com/p/es-lab/source/browse/trunk/src/proxies/FixedHandler.js

# Tom Van Cutsem (13 years ago)

2011/10/19 David Bruant <bruant.d at gmail.com>

Le 19/10/2011 10:57, Andreas Rossberg a écrit :

If I understand the proposal correctly, you cannot avoid the allocation, because the target is used as a backing store for fixed properties.

Indeed. The target is "merged" with the "fixedProps" of the FixedHandler proposal prototype implementation [1]

That was in the old proposal. In the latest design [2], you don't necessarily need to think of the target as a backing store, it's more as if the proxy wants to keep the target "in sync" with the handler.

If one would provide "null" as a target, then either: (1) the proxy could throw when it would otherwise need to access the target (e.g. to check for non-configurable properties). (2) the proxy implicitly does create an empty target object, to be used as a "backing store" for fixed properties, so to speak.

I would much prefer (1): if you then want to create a fully virtual object that only exposes configurable properties, there is no allocation overhead. If you want (2), just call Proxy.for(Object.create(null), handler).

This is a reason why a lot of checks that appear in Tom's Proxy.for are actually not necessary (and won't be a performance burden) since they are performed by some native code implementing internal methods (ES5.1 - 8.12 for native objects, but also the custom [[DefineOwnProperty]] for arrays if the target is one, etc.)

Not sure what you mean by "not necessary": it's true that I expect most checks to be fast since they are very similar to checks that need to be performed by existing built-ins, but that's not the same as stating that the checks are not necessary.

Cheers, Tom

[2] code.google.com/p/es-lab/source/browse/trunk/src/proxies/DirectProxies.js

# David Bruant (13 years ago)

Le 19/10/2011 12:30, Tom Van Cutsem a écrit :

This is a reason why a lot of checks that appear in Tom's
Proxy.for are actually not necessary (and won't be a performance
burden) since they are performed by some native code implementing
internal methods (ES5.1 - 8.12 for native objects, but also the
custom [[DefineOwnProperty]] for arrays if the target is one, etc.)

Not sure what you mean by "not necessary": it's true that I expect most checks to be fast since they are very similar to checks that need to be performed by existing built-ins, but that's not the same as stating that the checks are not necessary.

I meant that additional code is not necessary to perform these checks. The checks themselves are necessary though. Sorry for the confusion.

# David Herman (13 years ago)

These are all good points. I'm not sure (1) is worth bringing back in all the "we won't let you say things you can't enforce" complexity, but (2) is maybe non-obvious enough not to be worth it. I'm backing off my "please make it null" position now. :) It actually seems pretty reasonable just to require an object, and this also leaves the door open to potentially expanding the API to allow primitive value proxies at some point down the road.