Adding [[Invoke]] to address issues with methods called on Proxies
Correction: [[CallProperty]] would just be trapped as the single "callProperty" (since forwarding would do the [[Get]] on the target).
Another correction: the problem with noSuchMethod is actually a completely different problem. In order to implement noSuchMethod currently, a Proxy needs to always return a function from "get" which has captured the property name in a closure. This would continue being true if [[Invoke]] was added. Since [[CallProperty]] is simply passed a property name and is responsible for the [[Get]], a Proxy implementing a "callProperty" would be able to work like noSuchMethod.
A superior third alternative is [[CallProperty]](receiver, property) (and, in retrospect, likely what awb intended) which would allow for both noSuchMethod and correctly trapping super. This fits with [[CallProperty]] being the combination of [[Get]] with calling the method.
or [[CallProperty]](receiver, property, arguments)
I haven't had a chance to work through all the details yet, but this is something I want to explore as a solution to the Proxy forwarded identify issue. It does have to work with both normal property access calls and with super calls. Also, it needs to be consistent with the Proxy semantics of [[Get]]/[[Put]] when they trigger accessors.
Err yes, sorry. I was assuming away the last "arguments" argument in al three cases!
I am not sure I understand but ... if you can solve the receiver object,
what is exactly the difference/problem again with noSuchMethod and the
this
receiver ? 'cause noSuchMethod has been always used (regardless
problems with getters and setters and stuff nobody used with
noSuchMethod a part from specifications) same way PHP __call($name,
$arguments = array()) has been used since ever ... what am I missing that
is so different with [[invoke]] ? Thanks for explaining.
Sorry, I wish I had thought through to the logical conclusion and presented this as one email instead of four. The difference is that [[Invoke]] accepts a function object, which implies the [[Get]] has already happened. The issue with noSuchMethod is not the receiver, it's the combination of [[Get]] and [[Call]] into one operation. Currently, proxies need to [[Get]] and a function object, and then [[Call]] that function object on the receiver. Since the [[Get]] has to happen first, without knowledge of what is happen to the receiver, that proxy then has to return a function for all [[Get]]s, instead of returning undefined for actual [[Get]]s and calling a trap for [[Get]]+[[Call]].
Or to make a long story short, noSuchMethod requires a trap that combines [[Get]]+[[Call]]. [[CallProperty]] is that trap.
Responding in this thread since it's a more appropriate place.
1.) It complicates the object model, in that it adds a new internal method to objects, but it also clarifies it. It falls under the same category as [[GetP]] and [[SetP]]: they complicate the object model, but they also clarify it and they are only observable to Proxies. All three are only observable to Proxies. Proxies are the thing that ultimately complicates the object model and these are fallout from it, but most of us agree that Proxies are worth it.
2.) The abstract method Invoke already exists in the spec. This is a thing that the ES6 spec, with its deferring to prototypal inheritance for some things previously limited to internal properties, needs. Adding [[CallProperty]] would probably be a near net-zero change (perhaps a net-reduction) in total spec complexity, since Invoke already exists in the spec and is largely subsumed by [[CallProperty]].
Brandon Benvie wrote:
Responding in this thread since it's a more appropriate place.
1.) It complicates the object model, in that it adds a new internal method to objects, but it also clarifies it. It falls under the same category as [[GetP]] and [[SetP]]: they complicate the object model, but they also clarify it and they are only observable to Proxies. All three are only observable to Proxies. Proxies are the thing that ultimately complicates the object model and these are fallout from it, but most of us agree that Proxies are worth it.
Again, why add something that is "only observable to Proxies"? It is an exception, and it bring much more complication than straight rule. Such an exception is future-unfriendly.
And I do not agree that it clarifies the object model. On the contrary. It makes it inconsistent.
var quux = foo.bar; quux.call(foo, baz);
was always equivalent of foo.bar(baz). Now, it is not.
[[CallProperty]] is bad because it is alternative to [[Get]], in certain situations. What is the problem with, instead, doing it (imo) more properly, by reifying [[ChooseThis]](receiver, property) or something similar? This will not break the above equivalence, since it will be present in all oop calls and inside .apply and .call as well.
2.) The abstract method Invoke already exists in the spec. This is a thing that the ES6 spec, with its deferring to prototypal inheritance for some things previously limited to internal properties, needs. Adding [[CallProperty]] would probably be a near net-zero change (perhaps a net-reduction) in total spec complexity, since Invoke already exists in the spec and is largely subsumed by [[CallProperty]].
Special case adds more complexity than extract (imo). Spec efficiency does not beat complicating semantics.
On Tue, Jan 29, 2013 at 10:52 AM, Herby Vojčík <herby at mailbox.sk> wrote:
Brandon Benvie wrote:
Responding in this thread since it's a more appropriate place.
1.) It complicates the object model, in that it adds a new internal method to objects, but it also clarifies it. It falls under the same category as [[GetP]] and [[SetP]]: they complicate the object model, but they also clarify it and they are only observable to Proxies. All three are only observable to Proxies. Proxies are the thing that ultimately complicates the object model and these are fallout from it, but most of us agree that Proxies are worth it.
Again, why add something that is "only observable to Proxies"? It is an exception, and it bring much more complication than straight rule. Such an exception is future-unfriendly.
And I do not agree that it clarifies the object model. On the contrary. It makes it inconsistent.
var quux = foo.bar; quux.call(foo, baz);
was always equivalent of foo.bar(baz). Now, it is not.
[[CallProperty]] is bad because it is alternative to [[Get]], in certain situations. What is the problem with, instead, doing it (imo) more properly, by reifying [[ChooseThis]](receiver, property) or something similar?
Hi Herby,
Perhaps I missed it, but could you expand on the [[ChooseThis]] idea? Or provide a link to an earlier discussion if it's already adequately covered? Thanks.
Mark S. Miller wrote:
On Tue, Jan 29, 2013 at 10:52 AM, Herby Vojčík <herby at mailbox.sk <mailto:herby at mailbox.sk>> wrote:
Brandon Benvie wrote: Responding in this thread since it's a more appropriate place. 1.) It complicates the object model, in that it adds a new internal method to objects, but it also clarifies it. It falls under the same category as [[GetP]] and [[SetP]]: they complicate the object model, but they also clarify it and they are only observable to Proxies. All three are only observable to Proxies. Proxies are the thing that ultimately complicates the object model and these are fallout from it, but most of us agree that Proxies are worth it. Again, why add something that is "only observable to Proxies"? It is an exception, and it bring much more complication than straight rule. Such an exception is future-unfriendly. And I do not agree that it clarifies the object model. On the contrary. It makes it inconsistent. var quux = foo.bar; quux.call(foo, baz); was always equivalent of foo.bar(baz). Now, it is not. [[CallProperty]] is bad because it is alternative to [[Get]], in certain situations. What is the problem with, instead, doing it (imo) more properly, by reifying [[ChooseThis]](receiver, property) or something similar?
Hi Herby,
Perhaps I missed it, but could you expand on the [[ChooseThis]] idea? Or provide a link to an earlier discussion if it's already adequately covered? Thanks.
I replied it in another thread, but it was only on post, and it is included here as well (I just called in [[OopBind]] but it was more or less just [[ChooseThis]]):
[snip]
... and complicates object model. My main problem with [[CallProperty]] is the discrepancy between [[Get]] (used in [[Get]]+[[Call]]) and [[CallProperty]] (my assumption that by [[CallProperty]] you mean operation that replaces [[Get]]+[[Call]]). I'd solve it otherwise. Let's say that foo.bar(baz) is split to _three_ operations: [[Get]]+[[OopBind]]+[[Call]], where [[OopBind]] resolves this. Then, proxies should only be able to trap [[OopBind]] (but it will still be the same combination, involving trappable [[Get]] at the beginning and ending with (direct) [[Call]]).
[snip]
It is basically the idea that OOP call is three-part: [[Get]] (trappable) [[ChooseThis]] (by default choosing the base of Ref; trappable) [[Call]] (in this context (receiver being proxy) untrappable; if a function itself is proxy, it's already someone else's problem)
.apply and .call would do the last two.
[snip]
It is basically the idea that OOP call is three-part: [[Get]] (trappable) [[ChooseThis]] (by default choosing the base of Ref; trappable) [[Call]] (in this context (receiver being proxy) untrappable; if a function itself is proxy, it's already someone else's problem)
.apply and .call would do the last two.
I now see that things are more complicated and that it is not as simple as it looked. Proxies want to choose what this to use not based on function itself, but based on property, I presume, so [[ChooseThis]] would need property. But it then doesn't help the
var quux = foo.bar; quux.call(foo, baz);
case, since quux.call have no information that the property was 'bar' :-( Pity.
This slightly lowers usability of this solution (one can use [[MethodName]] as a property is .call and .apply, but ... that's not nice). I think it is still better than [[CallProperty]] that races with [[Get]] and is specific for proxies and method invocation case, but.
Or is so that certain kind of proxies would always use own identity and another kind always target identity, not based on which property am I calling? In that case, this solution is fine.
2013/1/29 Brandon Benvie <brandon at brandonbenvie.com>
Responding in this thread since it's a more appropriate place.
1.) It complicates the object model, in that it adds a new internal method to objects, but it also clarifies it. It falls under the same category as [[GetP]] and [[SetP]]: they complicate the object model, but they also clarify it and they are only observable to Proxies. All three are only observable to Proxies.
I disagree:
By calling Reflect.get(target, name, receiver) you can now call the [[GetP]] internal method of a normal target object and provide your own |this| binding, allowing you to properly forward property access when target[name] is an accessor. No proxies in sight.
Proxies are the thing that ultimately complicates the object model and these are fallout from it, but most of us agree that Proxies are worth it.
I think this is a strange way of characterizing proxies. The object model is there, and proxies merely expose it to JS programmers. Proxies aren't themselves supposed to complicate the object model further.
Of course, we had to do a lot of refactoring to the existing object model (cf. the introduction of [[GetP]]/[[SetP]] because exposing the ES5.1 object model directly turned out to be painful (cf. the old getPropertyDescriptor trap exposing [[GetProperty]]).
The introduction of [[Invoke]] would be a similar such refactoring. As you note yourself: the idea of invoking a method is already present in the spec, it's just not made explicit.
so we bind everything and problem solved ? I like that ... :-)
too bad bind creates really slow invokable objects :-/
thanks for the explanation, appreciated
I agree with you. I think I worded poorly what I was trying to express. Proxies were the catalyst for the above refactorings, which I think were beneficial in their own right in clarifying the spec. The complexity existed as an implied functionality of the spec. GetP and SetP clarified it by making it explicit. I believe CallProperty does the same.
On 29 January 2013 21:14, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
2013/1/29 Brandon Benvie <brandon at brandonbenvie.com>
Proxies are the thing that ultimately complicates the object model and these are fallout from it, but most of us agree that Proxies are worth it.
I think this is a strange way of characterizing proxies. The object model is there, and proxies merely expose it to JS programmers. Proxies aren't themselves supposed to complicate the object model further.
I suppose you mean that proxies aren't supposed to complicate pre-existing aspects of the object model. Their mere existence of course is a major complication in itself. (And I admit here and now that I have those blasphemous moments where I actually develop serious doubts that they "are worth it"...)
2013/1/30 Andreas Rossberg <rossberg at google.com>
I suppose you mean that proxies aren't supposed to complicate pre-existing aspects of the object model. Their mere existence of course is a major complication in itself.
I won't deny that :-) Proxies bring about many interleaving-hazards and break a lot of "momentary invariants" (using Mark's terminology < esdiscuss/2011-May/014150>).
The issues with private state (either WeakMaps or private symbols) and Proxies seems to largely boil down to the lack of the ability to bind |this| in forwarding method invocations to the target. This is the same limitation that prevents Proxies from being able to fully replace noSuchMethod. In either case, the internal state of the object is |this| sensitive and methods called on the Proxy can either malfunction or, in the case of WeakMap stored state, are completely unable to work.
A solution to this would be to add an [[Invoke]] internal method and a corresponding "invoke" Proxy trap. awb has referred to this as [CallProperty], but I think it might actually be better to use [Invoke] so that super calls can be trapped. The difference between the two is visible to a Proxy: [[Invoke]] would be "get" (usually) and then "invoke", [[CallProperty]] would be "callProperty" and then potentially a "get" (when forwarded). [[Invoke]] would also allow .call and .apply to be trapped as "invoke" instead of not trapped at all.
Outside of Proxies, this change wouldn't be observable.