custom proto arg for Proxy.createFunction?

David Bruant bruant at enseirb-matmeca.fr
Thu Feb 24 02:42:35 PST 2011


Le 24/02/2011 02:32, David Herman a écrit :
>>> The simple instanceof check seems simpler and reasonably intuitive.
>> By "hardcoding" p instanceof Function === true
> I'm not proposing this -- it's already true. Try it out in SpiderMonkey:
>
>     js> (Proxy.createFunction({}, function(){}, function(){})) instanceof Function
>     true
>
>> you're breaking your
>> user expectation of finding the Function.prototype methods ("call",
>> "apply" or any further addition) in the object.
> I think I haven't made myself clear. Certainly, users generally expect invariants like:
>
>     if (x instanceof y) and (key in y.prototype) then (key in x)
>     if (y === Object.getPrototypeOf(x)) and (key in y) then (key in x)
>
> But proxies *already* break these invariants. It's *already* possible to write a proxy that doesn't have a property that its prototype has. You can do this *both* with Proxy.create(), which lets you provide a prototype but act as though you don't have properties of the prototype, *and* with Proxy.createFunction(), which insists that your proxy is an instanceof Function but again lets you act as though you don't have the properties of Function.prototype.
>
> None of this has anything to do with my suggested extension, which is allowing a user-specified prototype to Proxy.createFunction.
>
>>>> With your solution, by removing Function.prototype from the chain, proxy
>>>> functions couldn't be .call()-ed or .apply()-ed
>>> That's already true, even without my extension:
>>>
>>>    js> var proxy = Proxy.createFunction({}, function(){}, function(){})
>>>    js> proxy.call(this)   
>>>    typein:11: TypeError: getPropertyDescriptor is not a function
>> The bug is in your code:
> No, there's no bug. The point of my code was to demonstrate that it's already possible to break the above invariant.
>
> If you want to promote as best practices that people should *strive* to uphold the invariant, even though it isn't enforced by the language, that's fine. But allowing user-specified prototypes for Proxy.createFunction doesn't change this. It's still up to the proxy writer to ensure that they delegate to the prototype, regardless of whether the prototype is Function.prototype or a descendant of Function.prototype.
This is currently true that it's up to the writer, but is likely to
become half-false:
Previously (starting here:
https://mail.mozilla.org/pipermail/es-discuss/2011-January/012603.html)
we've been discussing the idea that getPropertyNames and
getPropertyDescriptor become derived traps and that their default
behavior would be to climb the prototype chain. This way, by default,
the prototype delegation occurs, so it wouldn't be up to the proxy
writer to ensure prototype delegation.
But the proxy writer can decide to override getPropertyNames and
getPropertyDescriptor (or any trap that would) anyway and then mess with
prototype delegation, I agree.


>> When you do "proxy.call(this)", the "proxy.call" getter is trapped as
>> the "get" trap. Since you do not define it (your handler objectis
>> empty), the default get trap is called (see here :
>> http://wiki.ecmascript.org/doku.php?id=harmony:proxies#trap_defaults).
>> In this default get trap, the first thing that is done is a call to
>> this.getPropertyDescriptor ("this" refers to the handler object which is
>> empty in your example). And here, the error is thrown by the engine
>> since handler.getPropertyDescriptor isn't a function because it's undefined.
> Well, of course! That's my point. Proxies do not force you to respect prototype delegation.
Ok, I thought you made a mistake.

>> This code is ugly, because my get handler methods assumes that the proxy
>> object is reachable from the lexical scope which could not be the case
>> (I could have used the "rec" argument, but that's not very clean). There
>> is an ongoing discussion to make the proxy object available as an
>> argument in each trap. This way, the get trap could be rewritten as:
>> get: function (rec, name, proxy){
>>    return Object.getPrototypeOf(proxy)[name];
>>    // Object.getPrototypeOf(proxy) is Function.prototype by definition here
>> }
>> This is actually one more argument in the direction of making this happen.
> That seems like a non sequitur; in what you've written, I don't see any argument against allowing Proxy.createFunction() to accept a user-specified prototype.
>
> Again: the whole design of the proxy system *already* requires you to implement the delegation manually if you want to respect the prototype chain. But you don't have to respect the prototype delegation chain. That's true for both Object.create() and Object.createFunction().
>
> All I'm proposing is allowing you to specify a descendant of Function.prototype as your prototype link, rather than Function.prototype itself.
What do you call a "descendant of Function.prototype". Does it mean that
your proto argument MUST have Function.prototype in its chain too (and
Proxy.createFunction throws an error if it isn't the case for instance)?

I've been puzzled in your first message when you said "the library could
enforce that proto instanceof Function". I see four ways to do that:
1) Hardcode it
This may break some promise. I agree the delegation promise can be
broken by proxies. However, the following stands even with current proxies:
p instanceof C <=> There exist n such that (Object.getPrototypeOf)^n(p)
=== C.prototype
(please forgive the "to the power of n" notation).
And this would be broken if the instanceof result was hardcoded. So this
isn't a solution (I know you said that's not your proposal. I'm just
enumerating)

2) Change the proto prototype chain:
We have a proto object with a prototype chain that looks like:
proto --> p1 --> p2 --> ... --> pn --> null
I understand your suggestion as, when:
var p = Proxy.createFunction(h, c, cstr, proto);
then the prototype chain of p would be:
p --> proto --> p1 --> p2 --> ... --> pn --> Function.prototype -->
Object.prototype --> null (the Object.prototype is specified in ES5 15.3.4)
However, this would break some invariants.

var o = Object.create(proto);
// o instanceof Function === false
var p = Proxy.createFunction(h, c, cstr, proto);
// p instanceof Function === true // which is what we want
// But we now also have:
// o instanceof Function === true // which is an unwanted side effect of
changing proto's prototype chain.

There is no mecanism in ES5 to change the prototype chain of an object,
certainly because it would artificially break inheritance invariants.

3) Cloning the prototype chain of proto, append Function.prototype to
the fresh clone prototype chain. But it causes an object identity
problem (certainly among many others):
function C(){};
C.prototype = proto;
var p = Proxy.create(a,b,c, proto);
// p instanceof C === false // because we're dealing with a copy of proto

4) Make sure that proto contains Function.prototype in its chain and
throw an error if it isn't the case


Apologies, I've jumped a bit too quickly on the first solution when you
talked about "enforcing" the instanceof result.
As shown, none of the three first solutions are really a solution since
they break a language invariant (even respected by proxies in their
current form). So, is the 4th solution what you suggest? Or is it
something else?


> Either way, it's up to the programmer to decide whether they want to respect prototype delegation in their traps.
Indeed. Actually, since it's currently up to the programmer to
re-implement delegation, what are you expecting from choosing your
prototype object? Can't you just use it in your handler object? I'm just
talking about currently (current spec state and current FF4 implementation).
For the longerer term, if, as discussed, getPropertyNames and
getPropertyDescriptor become derived traps with the suggestion default
implementation, it completly makes sense to add an additional argument,
with the still pending issue of how to enforce the result of instanceof
(I'm actually a fan of the 4th solution).

David


More information about the es-discuss mailing list