[[Invoke]] and implicit method calls

Brendan Eich brendan at mozilla.com
Fri Sep 20 04:20:43 PDT 2013


Well stated. Comments below.

> Tom Van Cutsem <mailto:tomvc.be at gmail.com>
> September 20, 2013 12:02 PM
> 2013/9/20 David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>>
>
>     Further, a long-standing invariant in JS has been the equivalence
>     of o.m(...args)  and m.call(o, ...args). The invoke trap allows
>     allows to break this invariant. I'm not sure this is for the best.
>     This promotes a given coding pattern, but at the detriment of another.
>
>
> I'm well aware. I originally advocated against invoke() for precisely 
> this reason :-)
>
>     I'm still inclined to think a generic solution to private state
>     and proxies should be found. Given this solution, the invoke trap
>     may end up being plain redundant. That would be unfortunate.
>     I realize private state isn't figured out for ES6, so I think this
>     issue should be left pending, the invoke trap included.
>
>
> My point of view is that we have already found the "generic solution" 
> to private state: WeakMaps. WeakMaps are able to store private state, 
> and they don't interact with proxies at all. I realize WeakMaps have 
> syntactic and implementation-level usability issues, and I'm hopeful 
> the relationships strawman can overcome these. But relationships 
> semantically still build upon WeakMaps, and they were tested to work 
> across membranes in all cases.

Relationships as a strawman for ES7 is important in my view. Link:

http://wiki.ecmascript.org/doku.php?id=strawman:relationships

As Mark pointed out (this may not be obvious from the strawman, but it's 
there), for private fields on instancse of a class, the relationship 
outlives all instances, so no WeakMap with its O(N^2) worst-case GC 
behavior should be used naively. Sophisticated implementations of any JS 
that includes an @ dyadic operator special form for relationships should 
put the fields "in" the object and avoid a side table.

I think we will end up here, and it will take much of the torturous 
false dilemma between "private symbols" and "weak maps" out of the 
developer's brain. That false dilemma is a non-starter, so I think we 
must include relationships. But this is a digreesion as you note.

> Coming back to invoke(), I reviewed my notes from the May meeting 
> (where we decided to add the trap):
>
> A good point made by Yehuda is that the "get" trap already allows a 
> proxy to re-bind |this| for intercepted accessors.
> That is, in ES5, you had the "invariant" that if obj.x triggers a 
> getter, the getter's |this| is always bound to obj. With proxies, this 
> is no longer true. The "get" trap gives access to the thisBinding, and 
> allows the proxy to override. invoke() simply extends the power to 
> re-bind |this| from just accessor calls to general method calls.
>
> Dave Herman also reminded us that on platforms with __noSuchMethod__, 
> the invoke = get + call invariant is already weakened.
> In fact, as we've discussed on several occasions on this list, proxies 
> can't faithfully emulate __noSuchMethod__ without invoke().
>
> And finally, if all we gain by leaving out invoke() is a simpler API, 
> then we should probably reconsider all derived traps. As I mentioned 
> earlier, we can easily get rid of traps like has(), hasOwn(), keys() 
> etc., and they don't seem nearly as important to intercept as method 
> invocation.

But this makes proxies for special purpopes strictly harder to write, 
even with a base handler implementation. Please correct me if I'm mistaken.

So I think we are better off with get+invoke, but I'm still troubled by 
the double lookup. Any thoughts on parameterizing invoke by (id | func)?

/be
>
> Cheers,
> Tom
> David Bruant <mailto:bruant.d at gmail.com>
> September 20, 2013 10:57 AM
> Le 19/09/2013 10:53, Tom Van Cutsem a écrit :
>> 2013/9/18 David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>>
>>
>>     ... I just realized that in your examples, the private state is
>>     stored in a weakmap which requires target identity. If I recall,
>>     the original problem wasn't so much with weakmaps, but rather
>>     with Date instances which is slightly different.
>>     If solving weakmap lookups via |this| binding is worth solving,
>>     maybe we need to start considering solving weakmap lookups via
>>     the receiver of get and set traps, etc. As well as weakmap
>>     lookups via 1st, 2nd, 3rd, etc. argument.
>>
>>
>> We already have a solution to this: membranes do exactly the right 
>> wrapping/unwrapping for all arguments (including |this|, return 
>> values, and thrown exceptions).
> In a membrane, the method is wrapped and, when called, can unwrap 
> |this| as well as all arguments before making the call to the target. 
> This makes interaction with private state seamless. Membranes don't 
> need invoke (if anyone feel this isn't clear, I'm happy to write the 
> code). The problem being addressed by invoke is "isolated" 
> (non-membrane) proxies and how they interact with private state. Given 
> this is the problem being solved, why auto-unwrapping |this| and not 
> all arguments?
>
>>     What does make the 0th argument (this binding) so special?
>>
>>
>> The fact that many OO programmers don't consider |this| to be an argument
> If nothing else, Function.prototype.call makes very clear that |this| 
> is an argument. The "o.method()" notation is just convenient and 
> expressive syntactic sugar for "method.call(o)".
>
> Further, a long-standing invariant in JS has been the equivalence of 
> o.m(...args)  and m.call(o, ...args). The invoke trap allows allows to 
> break this invariant. I'm not sure this is for the best. This promotes 
> a given coding pattern, but at the detriment of another.
> A generic solution to how private state interacts with proxy wouldn't 
> promote any coding pattern, wouldn't break invariants.
>
> I have the impression of seeing the same sort of bias we had for 
> prototype being special in the original Proxy design 
> (Proxy.create(handler, prototype)). |this| doesn't need to be 
> specialized in the proxy design.
>
>> and depend on it being of a particular "type".
> A well-behaving proxy should be able to emulate a type (modulo 
> branding because of object identity)... well... I guess it depends on 
> what we call a "type". Is an object considered of a given type only if 
> it's strictly been the output of a given constructor? Can't a 
> well-behaving proxy emulate a type? What's the point of proxies if the 
> answer is no to the previous question?
>
>> The methods on many of JS's built-ins are a good example. But this is 
>> getting philosophical. The fact remains that we should make it easy 
>> for proxies to translate |proxy.method(...args)| into 
>> |target.method(...args)| calls.
> Why "should" we? The original motivation is private state. Are there 
> other needs? The fact that none was identified before the private 
> state issue came up suggests that there might not be.
>
> I'm still inclined to think a generic solution to private state and 
> proxies should be found. Given this solution, the invoke trap may end 
> up being plain redundant. That would be unfortunate.
> I realize private state isn't figured out for ES6, so I think this 
> issue should be left pending, the invoke trap included.
>
> David
> Tom Van Cutsem <mailto:tomvc.be at gmail.com>
> September 19, 2013 9:53 AM
> 2013/9/18 David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>>
>
>     ... I just realized that in your examples, the private state is
>     stored in a weakmap which requires target identity. If I recall,
>     the original problem wasn't so much with weakmaps, but rather with
>     Date instances which is slightly different.
>     If solving weakmap lookups via |this| binding is worth solving,
>     maybe we need to start considering solving weakmap lookups via the
>     receiver of get and set traps, etc. As well as weakmap lookups via
>     1st, 2nd, 3rd, etc. argument.
>
>
> We already have a solution to this: membranes do exactly the right 
> wrapping/unwrapping for all arguments (including |this|, return 
> values, and thrown exceptions).
>
>     What does make the 0th argument (this binding) so special?
>
>
> The fact that many OO programmers don't consider |this| to be an 
> argument and depend on it being of a particular "type". The methods on 
> many of JS's built-ins are a good example. But this is getting 
> philosophical. The fact remains that we should make it easy for 
> proxies to translate |proxy.method(...args)| into 
> |target.method(...args)| calls.
> David Bruant <mailto:bruant.d at gmail.com>
> September 18, 2013 9:54 PM
> Le 18/09/2013 20:52, Tom Van Cutsem a écrit :
> Are there other use cases than private state to rebinding |this| to 
> the target?
>
> ... I just realized that in your examples, the private state is stored 
> in a weakmap which requires target identity. If I recall, the original 
> problem wasn't so much with weakmaps, but rather with Date instances 
> which is slightly different.
> If solving weakmap lookups via |this| binding is worth solving, maybe 
> we need to start considering solving weakmap lookups via the receiver 
> of get and set traps, etc. As well as weakmap lookups via 1st, 2nd, 
> 3rd, etc. argument.
> What does make the 0th argument (this binding) so special?
>
> David
> Tom Van Cutsem <mailto:tomvc.be at gmail.com>
> September 18, 2013 7:52 PM
>
>     An alternative to p3 and p4 would be to find a solution for the
>     interaction between private state and proxies that also works with:
>         Date.getMonth.call(myProxy)
>     A way that would make p1 work in essence (which very naturally you
>     listed as the first idea ;-) ).
>     Such a solution would also make invoke unnecessary.
>
>
> My example has nothing to do with private state per se. It's more 
> generally about forwarding proxies reliably rebinding |this| to their 
> target.
>
> cheers,
> Tom


More information about the es-discuss mailing list