[ES Harmony Proxies] Fundamental trap definition

# David Bruant (15 years ago)

I am wondering if getPropertyDescriptor and getPropertyNames fundamental traps shouldn't rather be derived traps since they could have a pretty straightforward default implementation. One implementation of getPropertyNames could be :

Object.getPropertyNames = function(o){ var objectToInspect; var result = [];

for(objectToInspect = o;
    objectToInspect !== null;
    objectToInspect = Object.getPrototypeOf(objectToInspect))
{
        result =

result.concat(Object.getOwnPropertyNames(objectToInspect)); }

return result.removeDuplicates(); // the removeDuplicates method is

made up, but you get the point }

This seem to fit the proposal (doku.php?id=harmony:extended_object_api&s=getpropertydescriptor) expectations). This could be also the default getPropertyNames trap implementation.

I haven't really seen a strong definition of what fundamental traps are in the proposal. On Mark Miller's e-mail (esdiscuss/2011-January/012601) is written: [a trap is fundamental if] there is no coherent default behavior to fall back to that would be defined in terms of the remaining traps. (please tell me if I misinterpret what you meant) If we're going with this definition, then getPropertyDescriptor and getPropertyNames should probably be derived traps since they can clearly be defined thanks to other traps (respectively getOwnPropertyDescriptorand getOwnPropertyNamesas I showed above) which seems to be coherent fallback behavior. Instead of a formal definition, this could be a rational for deciding what is a fundamental trap and what isn't.

In the 5 remaining traps (getOwnPropertyDescriptor, getOwnPropertyNames, defineProperty, delete, fix), I don't see any that could be defined thanks to the others. They seem to be also the fundamental actions that one can perform on a single object: Property-wise:

  • create/configure a property (defineProperty)
  • delete a property (delete)
  • retrieve a property (getOwnPropertyDescriptor. Can be used to separate the create and configure cases of defineProperty) Property-set-wise:
  • retrieve the property set (getOwnPropertyNames. More can be found later on each property with getOwnPropertyDescriptor)
  • prevent further extension (and optional reconfigurations for seal and freeze)(fix) The prototype can be retrieved thanks to Object.getPrototypeOf (which cannot be trapped for consistency purposes) and all actions can be performed by climbing the prototype chain.

Any thoughts on the fundamental trap definition? Or on the idea of turning getPropertyDescriptor and getPropertyNames into derived traps with the suggested definition?

# François REMY (15 years ago)

Moreover, should a Proxy really have a prototype ? I mean, what’s the point about having a prototype, since the “get” method can return everything you would like for any property ?

I think getProtototypeOf should be defined as a new trap. And its default behaviour should be to return null (or Object.prototype, but I think null is the intented behavior of a proxy).

On the other hand, overriding getPropertyNames should not be allowed. getPropertyNames should always return the concatenation of the properties returned by getOwnPropertyNames() and by getPrototypeOf(this).getPropertyNames(). It makes no sense otherwhise.

I understand that we may want to redefine the way an ECMAScript Object can handle the native commands, but I’m strongly against anything that can conduce to illogical results (if there’s not an use case that justify it, naturally). The definition of getPropertyNames is clear and being able to redefine it locally seems me wrong. A proxy can modify is own behavior, not the behavior of the ES engine. Being able to have getPropertyNames and getOwnPropertyNames returning incompatible results is somewhat hurting me. Is there any reason we should allow that ? Any use case ?

I’m issuing the same concerns for the “has” trap. It think it should not be a trap. It should always return hasOwn(key) || prototype.has(key). Each one of hasOwn and prototype can be tuned by the proxy, but not the “has” itself. We’re not removing features, but we prevent bad usage of it. Whatever the user code do, the ‘has’ behavior will stay logical.

Another thing I don’t quite understand is the difference between “keys” and “enumerate”. If there’s no strong difference, it should be the same trap (same logic: enumerate should return the concatenation of this.keys and this.prototype.keys). Having two different but similar traps will cause confusion. Maybe there’s a need for this difference however. I just didn’t understand why such a difference should exist, but at least I see more possible usages than the first three traps I “contested”.

Anyway, if we should retain only one thing from the discussions we already have seen on the Proxy hub, is that it’s a great feature, but one that still needs some work before implementation ;-)

, François

From: David Bruant Sent: Sunday, January 23, 2011 4:24 PM To: es-discuss Cc: Mark S. Miller Subject: [ES Harmony Proxies] Fundamental trap definition

Hi,

I am wondering if getPropertyDescriptor and getPropertyNames fundamental traps shouldn't rather be derived traps since they could have a pretty straightforward default implementation. One implementation of getPropertyNames could be :

# Brendan Eich (15 years ago)

I think David has a point. The fundamental vs. derived distinction is, as I understand it, what Mark wrote recently, and David's application of it is sound (we do that prototype-walk with shadowing "duplicate removal" when starting a for-in loop in SpiderMonkey). Tom should weigh in.

But proxies have a proto chain for instanceof and good old prototype-based delegation purposes, even though their traps could ignore the proto parameter. The instanceof operator will not, and note how there is no getPrototypeOf trap, either.

This is a feature. I asked early on in the development of proxies whether the alternative, of putting the proto-chain walk "on the outside" of the proxy's handler traps was considered, and Mark pointed out the obvious: that doing so is strictly less flexible for cases where a proxy wants to do something different.

But this greater-flexibility design decision does not mean the universal, object -invariant (ignoring mutable proto) instanceof and getPrototypeOf (and ES3's isPrototypeOf) relations should be outside of a proxy's control. The Proxy.create proto parameter is the way to control those relations, for the life of a given proxy, and modulo property lookup trap freedom to do other things than just delegate up the proto chain.

# Tom Van Cutsem (15 years ago)

David,

Your intuition of what constitutes a fundamental vs. a derived trap is correct. Strictly speaking, the Proxy API could do with just the fundamental traps. The motivation for providing the optional derived traps was that overriding derived traps often allows proxy implementors to implement the operation more efficiently (usually with less object allocations).

As for your suggestion of turning 'getPropertyNames' and 'getPropertyDescriptor' into derived traps: I think you're correct. We could specify getPropertyNames and getPropertyDescriptor in terms of their "own" siblings + a prototype-chain-walk.

I recall a discussion with Mark where this issue was briefly discussed before. At that time, I was concerned by the fact that you could equally well define getOwnPropertyDescriptor as the derived trap, in terms of getPropertyDescriptor + getOwnPropertyNames, as follows:

getOwnPropertyDescriptor: function(name) { var desc = this.getPropertyDescriptor(name); var ownnames = this.getOwnPropertyNames(); if (ownnames.indexOf(name) !== -1) return desc; else return undefined; }

But applying the principle of minimizing the amount of allocations in default trap implementations, your definition is strictly the better one.

I'm in favor of any change that can minimize the amount of fundamental traps, so I'm in favor of adopting your change. Maybe Mark still recalls reasons for why these traps were categorized as fundamental traps in the first place.

Cheers, Tom

2011/1/23 Brendan Eich <brendan at mozilla.com>

# Tom Van Cutsem (15 years ago)

Ok, so Mark and I briefly discussed the implications of making "getPropertyDescriptor" and "getPropertyNames" derived.

Here's one issue: if you try and write these traps as methods of some sort of "default" handler as we did for the other derived traps (see < harmony:proxies#trap_defaults>)

you'll notice that the handler, by default, has no way of referring to the proxy it's intercepting, hence can't get at its prototype and hence can't do the prototype-chain-walk.

Now, from an implementation point-of-view, this isn't really a problem since the proxy implementation can make the connection between proxy and handler when the trap is called, and thus can do the prototype-chain-walk. It's just that you can't write it out in Javascript itself. Mark argues that therefore, "getPropertyDescriptor" and "getPropertyNames" are still fundamental.

Yet we both agree that providing a default implementation for these 2 traps makes perfect sense. Mark suggested we could classify traps in two different ways: ("optional" vs "mandatory") vs ("fundamental" vs "derived"). In that view, getPropertyDescriptor and getPropertyNames would be optional and fundamental. All other traps would be either (fundamental & mandatory) or (derived & optional).

I think that for developers, really the only distinction that matters is "optional" vs "mandatory". The "fundamental" vs "derived" distinction helps to clarify things for us spec. readers/writers.

While writing this, I also thought of another potential solution: we could choose to pass the proxy as an additional argument to the traps. Then the default implementation of getPropertyDescriptor could be written in Javascript as follows (adapting David's code):

// currently, this trap only takes the 'name' param getPropertyDescriptor: function(proxy, name) { var objectToInspect; var result;

for(objectToInspect = proxy;
    objectToInspect !== null;
    objectToInspect = Object.getPrototypeOf(objectToInspect))
{
        result = Object.getOwnPropertyDescriptor(objectToInspect, name);
        if (result !== undefined) { return result; }
}

return undefined;

}

Cheers, Tom

2011/1/24 Tom Van Cutsem <tomvc.be at gmail.com>

# David Bruant (15 years ago)

Le 26/01/2011 17:45, Tom Van Cutsem a écrit :

Ok, so Mark and I briefly discussed the implications of making "getPropertyDescriptor" and "getPropertyNames" derived.

Here's one issue: if you try and write these traps as methods of some sort of "default" handler as we did for the other derived traps (see harmony:proxies#trap_defaults) you'll notice that the handler, by default, has no way of referring to the proxy it's intercepting, hence can't get at its prototype and hence can't do the prototype-chain-walk.

This problem seems to have occured already for the set and get trap for which (like your proposition at the end) has been resolved with adding the proxy as an argument (called 'receiver' or 'proxy' depending on the example).

Now, from an implementation point-of-view, this isn't really a problem since the proxy implementation can make the connection between proxy and handler when the trap is called, and thus can do the prototype-chain-walk. It's just that you can't write it out in Javascript itself. Mark argues that therefore, "getPropertyDescriptor" and "getPropertyNames" are still fundamental.

Tell me if I'm wrong, but I think that one of proxies goal is to enable people to write in ECMAScript what they usually can't. If we're starting to make things that aren't writable in ECMAScript, we're kind of failing at empowering proxy users. I'm highly in favor of having default proxies behavior writable in ECMAScript.

Yet we both agree that providing a default implementation for these 2 traps makes perfect sense. Mark suggested we could classify traps in two different ways: ("optional" vs "mandatory") vs ("fundamental" vs "derived"). In that view, getPropertyDescriptor and getPropertyNames would be optional and fundamental. All other traps would be either (fundamental & mandatory) or (derived & optional).

I think that for developers, really the only distinction that matters is "optional" vs "mandatory". The "fundamental" vs "derived" distinction helps to clarify things for us spec. readers/writers.

This is a very good point.

While writing this, I also thought of another potential solution: we could choose to pass the proxy as an additional argument to the traps. Then the default implementation of getPropertyDescriptor could be written in Javascript as follows (adapting David's code):

// currently, this trap only takes the 'name' param getPropertyDescriptor: function(proxy, name) { var objectToInspect; var result;

for(objectToInspect = proxy; 
    objectToInspect !== null; 
    objectToInspect = Object.getPrototypeOf(objectToInspect))
{
        result = Object.getOwnPropertyDescriptor(objectToInspect,

name); if (result !== undefined) { return result; } }

return undefined;

}

This idea brings back the fact that getPropertyDescriptor has a fallback if it's not explicitely defined and the fact that we're not doing something that proxy users can't do.

However, it naturally raises the question: which trap should have the proxy as an argument?

I think that the only reason why we haven't needed this argument so far is that the handler object could be self sufficient (so we thought). Indeed, all the proxy semantics was in the handler. Who'd need the proxy as an argument when you already have all the semantics with you (through 'this' as used in the no-op forwarding example)? But the point we were missing in that we do NOT have ALL the semantics. Looking at the "Semantics of Proxies" page (harmony:proxies_semantics), we can notice that the [[Prototype]] property isn't reified by any method. Same for [[Class]], [[DefaultValue]] and [[HasInstance]]. There are reasons for not having a handler method that reify them, so this is very unlikely to change. This means that in any handler method that does not have the proxy as a argument, these things won't be available. Meaning, that:

  • we cannot reach the proxy prototype within a handler method (this is probably the most annoying point, because it means that within your handler code, you do not have access to the prototype you have set for your proxy)
  • there is no way within a handler method to know if the handler is used for a proxy and function proxy
  • there is no way to use instanceOf withing a handler method
  • (is there a way to reach [[DefaultValue]]?)

For all these reasons, I think that the proxy should be given as an argument to all handler methods.

This would also make the handler API more consistent with object APIs. When you look at the API method list, you can notice that all commented methods (surface syntax) are (obviously) using the proxy as an argument. And certainly that all native method implementations use this argument. The only methods that does use it in a different way than calling another internal method are 'set' and 'get', because they need to do a this-binding (see no-op forwarding example); something that you cannot do with only the handler.

# Allen Wirfs-Brock (15 years ago)

Note that passing the proxy means that a shared handler object can do caching or other discrimination based upon the proxy it is being invoked on. For example, the getPropertyDescriptor implementation shown below could have a cache to avoid repeated proto chain chasing.

Adding the proxy parameter seems to add power, but also may add complexity because it opens the door for addition hander design alternatives that weren't available before. For example, forwarding could be supported by a single shared forwarding handler that used used a weak map to maintain the association between the proxy and the target object. This design saves (relative to the currently forwarding proxy design) on handler instances at the cost of a weak map lookup for every trap invocation. Whether this additional design flexibility is good or bad is probably debatable. However, I personally am included towards the increased flexibility side of the debate.

# Brendan Eich (15 years ago)

On Jan 26, 2011, at 9:54 AM, David Bruant wrote:

Le 26/01/2011 17:45, Tom Van Cutsem a écrit :

Ok, so Mark and I briefly discussed the implications of making "getPropertyDescriptor" and "getPropertyNames" derived.

Here's one issue: if you try and write these traps as methods of some sort of "default" handler as we did for the other derived traps (see harmony:proxies#trap_defaults) you'll notice that the handler, by default, has no way of referring to the proxy it's intercepting, hence can't get at its prototype and hence can't do the prototype-chain-walk. This problem seems to have occured already for the set and get trap for which (like your proposition at the end) has been resolved with adding the proxy as an argument (called 'receiver' or 'proxy' depending on the example).

'receiver' is not the same as 'proxy' for get and set -- this is important:

js> function C() { this.bar = "hi"; } js>

js> var H = { has: function (name) { return name == 'foo'; }, get: function (rcvr, name) { if (name != 'foo') return undefined; print(rcvr instanceof C, rcvr.bar); return "bye"; }, }; js>

js> C.prototype = Proxy.create(H);

null js>

js> var c = new C;

js> print(c.foo);

true hi bye

Now, from an implementation point-of-view, this isn't really a problem since the proxy implementation can make the connection between proxy and handler when the trap is called, and thus can do the prototype-chain-walk. It's just that you can't write it out in Javascript itself. Mark argues that therefore, "getPropertyDescriptor" and "getPropertyNames" are still fundamental. Tell me if I'm wrong, but I think that one of proxies goal is to enable people to write in ECMAScript what they usually can't. If we're starting to make things that aren't writable in ECMAScript, we're kind of failing at empowering proxy users. I'm highly in favor of having default proxies behavior writable in ECMAScript.

+1, +many actually.

On the question of proxy parameters for all traps (well, receiver for get and set): fewer args are better, and closure capture of proxy by handler avoids leaking the proxy to handler friends, if that matters. Likewise you can't get the handler from the proxy.

These aren't absolute arguments, but I remember working through the alternative of a leading proxy parameter, both as JS hacker using proxies and in terms of SpiderMonkey's C++ implementation, and it was just bigger and less tidy, for both "sides" (JS and C++), without enough payoff.

# David Bruant (15 years ago)

Le 26/01/2011 19:17, Brendan Eich a écrit :

On Jan 26, 2011, at 9:54 AM, David Bruant wrote:

Le 26/01/2011 17:45, Tom Van Cutsem a écrit :

Ok, so Mark and I briefly discussed the implications of making "getPropertyDescriptor" and "getPropertyNames" derived.

Here's one issue: if you try and write these traps as methods of some sort of "default" handler as we did for the other derived traps (see harmony:proxies#trap_defaults) you'll notice that the handler, by default, has no way of referring to the proxy it's intercepting, hence can't get at its prototype and hence can't do the prototype-chain-walk. This problem seems to have occured already for the set and get trap for which (like your proposition at the end) has been resolved with adding the proxy as an argument (called 'receiver' or 'proxy' depending on the example).

'receiver' is not the same as 'proxy' for get and set -- this is important:

js> function C() { this.bar = "hi"; } js> js> var H = { has: function (name) { return name == 'foo'; }, get: function (rcvr, name) { if (name != 'foo') return undefined; print(rcvr instanceof C, rcvr.bar); return "bye"; }, }; js> js> C.prototype = Proxy.create(H); null js> js> var c = new C; js> print(c.foo); true hi bye

Ooooh... right.... Sorry for the confusion.

Now, from an implementation point-of-view, this isn't really a problem since the proxy implementation can make the connection between proxy and handler when the trap is called, and thus can do the prototype-chain-walk. It's just that you can't write it out in Javascript itself. Mark argues that therefore, "getPropertyDescriptor" and "getPropertyNames" are still fundamental. Tell me if I'm wrong, but I think that one of proxies goal is to enable people to write in ECMAScript what they usually can't. If we're starting to make things that aren't writable in ECMAScript, we're kind of failing at empowering proxy users. I'm highly in favor of having default proxies behavior writable in ECMAScript.

+1, +many actually.

On the question of proxy parameters for all traps (well, receiver for get and set): fewer args are better, and closure capture of proxy by handler avoids leaking the proxy to handler friends, if that matters. Likewise you can't get the handler from the proxy.

If you handlers are frozen, there should be no leaking, shouldn't there? (I actually think that freezing the handler should be done in the no-op forwarding proxy example or any handler factory, but that's another issue).

About the closure capturing, it works only if one proxy is using the handler. It doesn't if several proxies share it.

I think that the problem I raised earlier remaims with receiver in get and set: there is no way to reach the proxy. If there are two proxies in the prototype chain, unless you do some string hackery on them to distinguish them, climbing the prototype chain from receiver won't allow you to know which proxy you're manipulating (which can be embarassing if the handler is shared among your two proxies).

These aren't absolute arguments, but I remember working through the alternative of a leading proxy parameter, both as JS hacker using proxies and in terms of SpiderMonkey's C++ implementation, and it was just bigger and less tidy, for both "sides" (JS and C++), without enough payoff. ... what about a trailing proxy parameter? It won't change anything on the C++ side (by comparison to a leading parameter), but will on the JS side, because you can omit the parameter if you don't need it. I fully agree that the proxy isn't useful as a parameter most of the time. However, for the things I've mentionned earlier (Object.getPrototypeOf, distinguishing proxy and function proxy within the handler if it's shared, use of instanceof... all of the object semantics which isn't part of a handler defintion), there is no way to access them without the proxy argument (and the closure solution cannot work with shared handlers). I personnaly think that not being able, within the handler to access to the prototype of your proxy, the one you have set, is a loss. It's like if the ES5 object internal methods didn't have access to the [[Prototype]] internal property.

# Brendan Eich (15 years ago)

On Jan 26, 2011, at 11:02 AM, David Bruant wrote:

On the question of proxy parameters for all traps (well, receiver for get and set): fewer args are better, and closure capture of proxy by handler avoids leaking the proxy to handler friends, if that matters. Likewise you can't get the handler from the proxy. If you handlers are frozen, there should be no leaking, shouldn't there? (I actually think that freezing the handler should be done in the no-op forwarding proxy example or any handler factory, but that's another issue).

No default freezing is going to be imposed. :-|

About the closure capturing, it works only if one proxy is using the handler. It doesn't if several proxies share it.

Good point.

These aren't absolute arguments, but I remember working through the alternative of a leading proxy parameter, both as JS hacker using proxies and in terms of SpiderMonkey's C++ implementation, and it was just bigger and less tidy, for both "sides" (JS and C++), without enough payoff. ... what about a trailing proxy parameter? It won't change anything on the C++ side (by comparison to a leading parameter), but will on the JS side, because you can omit the parameter if you don't need it.

Not a bad idea, and the C++ gurus can take it. Tom, Mark?

# David Bruant (15 years ago)

Le 26/01/2011 22:31, Brendan Eich a écrit :

On Jan 26, 2011, at 11:02 AM, David Bruant wrote:

On the question of proxy parameters for all traps (well, receiver for get and set): fewer args are better, and closure capture of proxy by handler avoids leaking the proxy to handler friends, if that matters. Likewise you can't get the handler from the proxy. If you handlers are frozen, there should be no leaking, shouldn't there? (I actually think that freezing the handler should be done in the no-op forwarding proxy example or any handler factory, but that's another issue). No default freezing is going to be imposed. :-|

I didn't mean to impose freezing objects, sorry for the misunderstanding. I was more going for highly encouraging people to do so by showing it in the no-op forwarding proxy example.

# Brendan Eich (15 years ago)

We avoid freeze-didacticism in general ;-) -- is there a strong reason to freeze in this example?

# David Bruant (15 years ago)

Actually, freezing would prevent the pattern: forwardingHandler+"delta definition" (where you only override a few traps). So I'm just going to defend seal.

For the no-op forwarding proxy example (harmony:proxies#examplea_no-op_forwarding_proxy), if the handlerMaker function is only used once in the context of : Proxy.create(handlerMaker(aProxy)); then no, not even the least reason to seal.

However, if you do: var h = handlerMaker(aProxy); then the risk is taken that a handler method gets deleted. Deleting a property may trigger an error or perform an unexpected behavior depending on if the trap is fundamental or derived. Sealing the object avoids unexpected delete by throwing an error (at least in strict mode. Not sure in non-strict) making a bug easier to find. Finally, for this particular case there is no obvious reason to extend the handler or to be allowed to reconfigure the properties.

I don't know if it is what you call a "strong reason".

My opinion on preventExtension, seal and freeze is to use them when they don't prevent any usage of the object and when they could be helpful with detecting stupid mistakes (who has never wasted at least a couple of minutes after a myDiv.innerHtml = 'blabla'?). I obviously won't force anyone to share my opinion.

David

Le 26/01/2011 23:59, Brendan Eich a écrit :

# Tom Van Cutsem (15 years ago)
  • Agreed that if we contemplate adding 'proxy' as an argument to getPropertyDescriptor and getPropertyNames, we should add it to all other traps as well.
  • Agreed that this is the simplest way of allowing a shared handler to get at the proxy it's currently 'serving'
  • W.r.t. a handler not being able to perform typeof/instanceof on the proxy it's intercepting: this does have the benefit of detracting programmers from writing handlers that act differently for function vs object proxies. But agreed that this functionality might be needed to fix the quirky behavior of a host object.

Re. adding 'proxy' as an optional last parameter to all traps: what worries me is that for some traps, this could be terribly confusing. Consider:

Object.getOwnPropertyDescriptor(proxy, name); // will trap as: getOwnPropertyDescriptor: function(name, proxy) { ... }

The reversed argument order is going to bite people. Adding 'proxy' as a last optional argument is confusing in this way for get{Own}PropertyDescriptor, defineProperty, delete, hasOwn It's OK for get{Own}PropertyNames, keys, fix, has, enumerate get and set would then take 4 arguments, and it's unclear to me whether tacking 'proxy' to the front or to the back is better. (alternatively, maybe 'get' and 'set' don't need access to the proxy after all. It seems to me that if 'receiver' has multiple proxies in its prototype chain, only the one closest to receiver is going to trigger a handler get/set trap. OTOH, without a built-in Proxy.isTrapping(obj) method, I don't think the handler can find out what object on the prototype chain is a proxy)

We could choose to add 'proxy' at the "right" place in the argument list depending on the trap. This trades one kind of cognitive load (unexpected argument order for some traps) for another (no uniform position of 'proxy' across all traps, and no longer an optional argument for all traps). Either way, we need to decide whether this added API complexity is worth it.

One more thing: giving the handler access to its proxy by default may increase chances of runaway recursion hazards, as every operation that the handler performs on its own proxy is going to call some trap on the handler itself. Ideally, we should be able to label the 'proxy' parameter with a big red warning sign saying "handle with care!". I ran into this problem already a couple of times when writing "get" traps. The 'receiver' parameter often is the proxy itself, and doing even simple things like trying to print the proxy (e.g. for debugging) will call proxy.toString, hence will recursively call the "get" trap you're trying to debug. But maybe the problem is just more exaggerated in the "get" trap since property access is so common, and it won't really be an issue for the other traps.

Cheers, Tom

2011/1/26 Brendan Eich <brendan at mozilla.com>

# David Bruant (15 years ago)

Le 27/01/2011 11:20, Tom Van Cutsem a écrit :

  • Agreed that if we contemplate adding 'proxy' as an argument to getPropertyDescriptor and getPropertyNames, we should add it to all other traps as well.
  • Agreed that this is the simplest way of allowing a shared handler to get at the proxy it's currently 'serving'
  • W.r.t. a handler not being able to perform typeof/instanceof on the proxy it's intercepting: this does have the benefit of detracting programmers from writing handlers that act differently for function vs object proxies. But agreed that this functionality might be needed to fix the quirky behavior of a host object.

This last point is actually a concern. Is the (one?) point of proxies to being able to fully emulate (and by 'emulate', I also mean 'fix') host objects. If so, proxies should have access to all internal methods&properties. As said in ES5 8.6.2 right under Table 8: "Every object (including host objects) must implement all of the internal properties listed in Table 8.". If we want to be able to emulate proxies, we need to be as powerful with proxies. Question is: do we want to?

I have recently been working on emulating Arrays (they are host objects, aren't they?) with proxies and native objects. I'll put the code on github and provide a longer feedback in another e-mail, but the basic idea was to use a forwarding proxy and implement the only method that matters: defineProperty. I have almost blindly implemented+adapted [[defineOwnProperty]] (ES5 15.4.5.1). Everything works perfectly so far. There are a few differences with native arrays, though:

  • I have no control over the [[class]] internal property, so even though my "proxyArrays" act like native ones, they can be discriminated quite easily.
  • I haven't tested, but according to the semantics of Array initialisers (ES5 11.1.4), "[]" calls "new Array() where Array is the standard built-in constructor with that name.". So even if I try window.Array = proxyArray, "[]" should not create one of my arrays. This sounds perfectly fair.
  • I obviously initialize my proxyArrays with Array.prototype as prototype. I haven't tested if it works; it is allowed not to, because in ES5, under each array prototype method is written: " The *** function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the *** function can be applied successfully to a host object is implementation-dependent. ". They could be reimplemented if needed to fully emulate Arrays. (I've worked on that a billion years ago : DavidBruant/ecma5array/blob/test_conformance/ecma5array.js)

So here is another question on proxies goal/rational: Should proxies be powerful enough to fully emulate native Arrays? (If so and if ES Harmony has proxies in it, then native Array could be specified as a proxy which is awesome and a half in my opinion, but that's a different problem).

Re. adding 'proxy' as an optional last parameter to all traps: what worries me is that for some traps, this could be terribly confusing. Consider:

Object.getOwnPropertyDescriptor(proxy, name); // will trap as: getOwnPropertyDescriptor: function(name, proxy) { ... }

The reversed argument order is going to bite people. Adding 'proxy' as a last optional argument is confusing in this way for get{Own}PropertyDescriptor, defineProperty, delete, hasOwn It's OK for get{Own}PropertyNames, keys, fix, has, enumerate get and set would then take 4 arguments, and it's unclear to me whether tacking 'proxy' to the front or to the back is better. (alternatively, maybe 'get' and 'set' don't need access to the proxy after all. It seems to me that if 'receiver' has multiple proxies in its prototype chain, only the one closest to receiver is going to trigger a handler get/set trap. OTOH, without a built-in Proxy.isTrapping(obj) method, I don't think the handler can find out what object on the prototype chain is a proxy)

We could choose to add 'proxy' at the "right" place in the argument list depending on the trap. This trades one kind of cognitive load (unexpected argument order for some traps) for another (no uniform position of 'proxy' across all traps, and no longer an optional argument for all traps). Either way, we need to decide whether this added API complexity is worth it.

One more thing: giving the handler access to its proxy by default may increase chances of runaway recursion hazards, as every operation that the handler performs on its own proxy is going to call some trap on the handler itself. Ideally, we should be able to label the 'proxy' parameter with a big red warning sign saying "handle with care!". I ran into this problem already a couple of times when writing "get" traps. The 'receiver' parameter often is the proxy itself, and doing even simple things like trying to print the proxy (e.g. for debugging) will call proxy.toString, hence will recursively call the "get" trap you're trying to debug. But maybe the problem is just more exaggerated in the "get" trap since property access is so common, and it won't really be an issue for the other traps.

I had already read that point in your proxy tutorial. It will be up to the resources that people go to to learn how proxies work to tell them that they should be careful with unwanted recursion. One important such resource is very likely to be the MDN page. I'll take care of writing down all these points. Otherwise, it's a wiki, feel free to jump in anytime to correct things or add examples.

# Brendan Eich (15 years ago)

I will let Tom and Mark field this one in full, but Arrays are not host objects, they are native objects in ECMA-262 terms.

However, Allen has taken the action to reconcile and perhaps unify the spec's internal methods (its Meta-Object Protocol) and Proxies' MOP. This would allow the spec to treat arrays as if they were proxies with native handlers.

In reality, of course, a proxy is an empty object with a handler object (shared or not) that holdsl mutable state (somehow: closure, weakmap, whatever). Arrays OTOH must be efficient, dense if possible, single allocation if small, etc. No practical implementation would make a proxy and handler holding mutable state.

This beautiful slide of Tom's (I believe) shows the parallelism and differences:

brendaneich.com/brendaneich_content/uploads/selective-interception.png

In C and C++ implementations, the handler is a suite of functions or methods, and all the array data is in the green "object" circle, or attached to it for efficient access with dynamic range over length.

# Tom Van Cutsem (15 years ago)

2011/1/27 David Bruant <bruant at enseirb-matmeca.fr>

Le 27/01/2011 11:20, Tom Van Cutsem a écrit :

  • Agreed that if we contemplate adding 'proxy' as an argument to getPropertyDescriptor and getPropertyNames, we should add it to all other traps as well.
  • Agreed that this is the simplest way of allowing a shared handler to get at the proxy it's currently 'serving'
  • W.r.t. a handler not being able to perform typeof/instanceof on the proxy it's intercepting: this does have the benefit of detracting programmers from writing handlers that act differently for function vs object proxies. But agreed that this functionality might be needed to fix the quirky behavior of a host object.

This last point is actually a concern. Is the (one?) point of proxies to being able to fully emulate (and by 'emulate', I also mean 'fix') host objects. If so, proxies should have access to all internal methods&properties. As said in ES5 8.6.2 right under Table 8: "Every object (including host objects) must implement all of the internal properties listed in Table 8.". If we want to be able to emulate proxies, we need to be as powerful with proxies. Question is: do we want to?

I have recently been working on emulating Arrays (they are host objects, aren't they?) with proxies and native objects. I'll put the code on github and provide a longer feedback in another e-mail, but the basic idea was to use a forwarding proxy and implement the only method that matters: defineProperty. I have almost blindly implemented+adapted [[defineOwnProperty]] (ES5 15.4.5.1). Everything works perfectly so far. There are a few differences with native arrays, though:

  • I have no control over the [[class]] internal property, so even though my "proxyArrays" act like native ones, they can be discriminated quite easily.

Right, this issue has come up before. From the "open issues" on the proxies wiki page:

*TC39 Meeting 1/28/10 *The number of operators that proxies can virtualize is still open for discussion. The current proposal does not allow proxies to virtualize typeof and [[Class]]. Perhaps these could be virtualized but kept constant by passing appropriate values for typeof and [[Class]] to the Proxy constructor. The alternative APIstrawman:uniform_proxies

does virtualize typeof in this manner. *TC39 Meeting 3/24/10 *The experimental third className parameter to Proxy.create in spidermonkey should not be standardized. Objects should not be able to virtualize the [[Class]] built-in property. However, Object.prototype.toString.call(proxy) will reveal proxies if the object they are proxying has a non-Object or non-Function [[Class]].

There is concern within the committee that overriding [[Class]] may violate spec. invariants. I believe it is Allen's intention to take issues like this into account in his MOP refactoring (Allen, correct me if I'm wrong).

IMHO, I think being able to override the [[Class]] at least for toString() purposes will be required for proxies to fully fix/emulate existing host objects.

  • I haven't tested, but according to the semantics of Array initialisers

(ES5 11.1.4), "[]" calls "new Array() where Array is the standard built-in constructor with that name.". So even if I try window.Array = proxyArray, "[]" should not create one of my arrays. This sounds perfectly fair.

  • I obviously initialize my proxyArrays with Array.prototype as prototype. I haven't tested if it works; it is allowed not to, because in ES5, under each array prototype method is written: " The *** function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the *** function can be applied successfully to a host object is implementation-dependent. ". They could be reimplemented if needed to fully emulate Arrays. (I've worked on that a billion years ago : DavidBruant/ecma5array/blob/test_conformance/ecma5array.js )

So here is another question on proxies goal/rational: Should proxies be powerful enough to fully emulate native Arrays? (If so and if ES Harmony has proxies in it, then native Array could be specified as a proxy which is awesome and a half in my opinion, but that's a different problem).

As Brendan pointed out, Arrays are not host objects, they are much more privileged and part of the core language.

To elaborate on Brendan's response, I think the point of Allen's MOP-refactoring is to structure the spec. such that ES-harmony has a very well-defined internal object hierarchy, and proxies are the mechanism by which JS developers can hook into that hierarchy. All existing built-ins (Object, String, Array, ...) are part of the predefined internal object hierarchy. They could or could not be defined in terms of proxies & handlers, but even if they would be, their traps would still be defined in terms of spec. language, not in terms of pure JS, to avoid over-specification and to allow for efficient internal representations.

That said, it would be very nice if the behavior of all built-ins can in fact be emulated via proxies. I can imagine that a tutorial on ES-harmony could then explain the behavior of built-ins by showing example code similar to your Array emulation code.

# Allen Wirfs-Brock (15 years ago)

On Jan 30, 2011, at 8:32 AM, Tom Van Cutsem wrote:

...

Right, this issue has come up before. From the "open issues" on the proxies wiki page:

TC39 Meeting 1/28/10 The number of operators that proxies can virtualize is still open for discussion. The current proposal does not allow proxies to virtualize typeof and [[Class]]. Perhaps these could be virtualized but kept constant by passing appropriate values for typeof and [[Class]] to the Proxy constructor. The alternative API does virtualize typeof in this manner.

TC39 Meeting 3/24/10 The experimental third className parameter to Proxy.create in spidermonkey should not be standardized. Objects should not be able to virtualize the [[Class]] built-in property. However, Object.prototype.toString.call(proxy) will reveal proxies if the object they are proxying has a non-Object or non-Function [[Class]].

There is concern within the committee that overriding [[Class]] may violate spec. invariants. I believe it is Allen's intention to take issues like this into account in his MOP refactoring (Allen, correct me if I'm wrong).

IMHO, I think being able to override the [[Class]] at least for toString() purposes will be required for proxies to fully fix/emulate existing host objects.

I actually think we can get rid of [[Class]]. For toString() purposes we can provide a new, user extensible mechanism for parameterizing the default implementation. I think most other uses of [[Class]] in the spec. really wants to be test against the internal "class" hierarchy I identified in my recent documents. There are other implicit "class" tests that that aren't currently expressed in terms of [[Class]] but instead use language like "if obj is an Array object then ...". I think we can regularize all by always being explicit about the internal "class" of an object and using spec. language that explicitly tests for specific internal classes.

  • I haven't tested, but according to the semantics of Array initialisers (ES5 11.1.4), "[]" calls "new Array() where Array is the standard built-in constructor with that name.". So even if I try window.Array = proxyArray, "[]" should not create one of my arrays. This sounds perfectly fair.
  • I obviously initialize my proxyArrays with Array.prototype as prototype. I haven't tested if it works; it is allowed not to, because in ES5, under each array prototype method is written: " The *** function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the *** function can be applied successfully to a host object is implementation-dependent. ". They could be reimplemented if needed to fully emulate Arrays. (I've worked on that a billion years ago : DavidBruant/ecma5array/blob/test_conformance/ecma5array.js)

So here is another question on proxies goal/rational: Should proxies be powerful enough to fully emulate native Arrays? (If so and if ES Harmony has proxies in it, then native Array could be specified as a proxy which is awesome and a half in my opinion, but that's a different problem).

As Brendan pointed out, Arrays are not host objects, they are much more privileged and part of the core language.

To elaborate on Brendan's response, I think the point of Allen's MOP-refactoring is to structure the spec. such that ES-harmony has a very well-defined internal object hierarchy, and proxies are the mechanism by which JS developers can hook into that hierarchy. All existing built-ins (Object, String, Array, ...) are part of the predefined internal object hierarchy. They could or could not be defined in terms of proxies & handlers, but even if they would be, their traps would still be defined in terms of spec. language, not in terms of pure JS, to avoid over-specification and to allow for efficient internal representations.

The way I think about it is that the internal methods (the internal object hierarchy) and a few abstract operations is what connects the statement/expression level semantics of ECMAScript to the ECMAScript object semantics. Where ever an internal method is referenced it is an indication that some sort of object specific behavior variation is allowed. However, for built-in objects, these variations are all explicitly defined by the specification. It's up to implementations do decide how to implement this semantics and as far as built-in objects are concerned there is no particular requirement to actually implement the internal methods "calls" as actual calls. However, if an implementation is going to support any sort of open-end host object support then it probably does need to have code paths that do reify the internal method calls as calls into the host object implementation layer. I view Proxies as simply a variation on that host object implementation pattern, but one where the reified class are reflected back into ECMASscript code. Viewed in this manner, host objects and proxies seems to have equal expressive power. Here host objects might have additional power would be if an implementation provided supplemental APIs that a host object implementation could use to directly interact with the actual runtime implementation. If some of these implementation level back channels were common and useful enough (for example, the ability to associate some non-property state with object instances) then we might consider making them available in some manner to Proxy handlers.

That said, it would be very nice if the behavior of all built-ins can in fact be emulated via proxies. I can imagine that a tutorial on ES-harmony could then explain the behavior of built-ins by showing example code similar to your Array emulation code.

I think it is more than nice. I think it is a measure of success that we can do this. However, I wouldn't expect the Proxy forms to be optimizable to the same level as the built-in implementations.