[[Invoke]] vs [[Get]]
I just saw that Jason identified some additional open questions in 1:
-
What happens when we call a method on an object whose [[Prototype]] is a proxy with an invoke trap?
var p = new Proxy({}, {invoke: ...}); var q = Object.create(p); q.f();
Does this trigger p's invoke trap?
-
What happens when a proxy with an invoke trap is used in a with-block?
with (p) f();
-
When exactly do we check for an invoke trap, and when do we call it? How does that affect 11.2.3? people.mozilla.com/~jorendorff/es6-draft.html#sec-11.2.3
In an expression like p.f(), do we still evaluate "p.f" as in 11.2.3 step 1?
Seems like 1. should behave as the equivalent situation with a [[Get]]
handler does. 2. makes me shudder, but also seems like it pertains to
[[Get]] in just the same way. 3. seems somewhat difficult to deal with,
indeed. Maybe a new abstract operation GetFunction
or GetCallable
should be introduced, with step 1 of 11.2.3 reformulated in terms of that?
I'm not sure how best to factor that step, though. Another solution could
be to introduce the concept of a boolean InCallingContext
to 8.2.4.1,
GetValue
and change its step 5 like this:
b. If InCallingContext is false, then return the result of calling the [[Get]] internal method of base passing GetReferencedName(V) and GetThisValue(V) as the arguments. c. Else return the result of GetMethod passing GetReferencedName(V) and GetThisValue(V) as the arguments.
I'm not well-versed enough in reading the spec to know how well that fits into the big picture.
If you have a [[Get]] trap it seems wrong to skip it when you also have an [[Invoke]] trap. Maybe you should execute the [[Get]] first and feed its result into the [[Invoke]] one?
obj.method();
I'd expect the [[Get]] to always have its say and not to be just ignored in a possibly dynamic fashion.
2013/6/8 Till Schneidereit <tschneidereit at gmail.com>
At the last TC39 meeting, an agreement was reached for proxies to support an Invoke trap. I'm currently implementing this trap in SpiderMonkey[1] and realized that there's one conceptual issue that has to be decided in the spec:
Currently, the [[Get]] trap naturally handles method calls on the proxies, as these are gets followed by calls to the returned function value. With proxies also being able to trap [[Invoke]], things become less clear, with four possible options:
- for method calls, only the [[Invoke]] trap is called
- if an [[Invoke]] handler exists, it is called, otherwise a [[Get]] handler is called, if that exists
- like 2., but additionally, if the [[Invoke]] handler doesn't return a callable value, the [[Get]] handler is called
- like 3., but with reserved order of handler invocation: if a [[Get]] handler exists, it is called. If the result isn't callable, or [[Get]] isn't handled, an [[Invoke]] handler is called, if one exists
I would strongly advocate position 1: for method calls, only call [[Invoke]]. This is most consistent with the current API design.
The first of these options seems conceptually cleanest to me, but I do think that the other options (perhaps except for the last one) can be argued for to some extend, too: the abstract operation
Invoke
, via its step 5,GetMethod
, contains a call to the [[Get]] operation, after all.
Agreed, it is sometimes useful to have one trap depend on another. That's why we proposed the DelegatingHandler [1]. The DelegatingHandler's default implementation for invoke would call the [[Get]] trap. See the non-normative self-hosted implementation of DelegatingHandler for one possible implementation.
2013/6/8 Till Schneidereit <tschneidereit at gmail.com>
I just saw that Jason identified some additional open questions in [1]:
What happens when we call a method on an object whose [[Prototype]] is a proxy with an invoke trap?
var p = new Proxy({}, {invoke: ...}); var q = Object.create(p); q.f();
Does this trigger p's invoke trap?
Yes. "invoke", like "get", "set", "has" and "enumerate" should be one of the traps that can be called on a proxy used as a prototype.
What happens when a proxy with an invoke trap is used in a with-block?
with (p) f();
Well, I guess it should trigger the invoke trap, just like writing simply "f" would trigger the proxy's get trap.
- When exactly do we check for an invoke trap, and when do we call it? How does that affect 11.2.3? people.mozilla.com/~jorendorff/es6-draft.html#sec-11.2.3
In an expression like p.f(), do we still evaluate "p.f" as in 11.2.3 step 1?
No, we can't just blindly call GetValue in step 1 as that would lead to the "get" trap being invoked on a proxy (which is the wrong trap).
I believe Allen had previously experimented with an invoke trap (called "callProperty") in a fork of the ES6 draft. Allen, can you comment on how you would see [[Invoke]] specced?
On Jun 9, 2013, at 8:18 AM, Tom Van Cutsem wrote:
I believe Allen had previously experimented with an invoke trap (called "callProperty") in a fork of the ES6 draft. Allen, can you comment on how you would see [[Invoke]] specced?
I've already updated my working draft with [[Invoke]], but I'm on my way to the airport and can't create a digest of the changes right now.
However, they were derived from my earlier experiment which you can see zt meetings:callproperty_experiment.pdf
So of the details changed and bugs were fixed, but the general idea is the same.
On Sun, Jun 9, 2013 at 4:18 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
No, we can't just blindly call GetValue in step 1 as that would lead to the "get" trap being invoked on a proxy (which is the wrong trap).
Why can't we have the two traps trigger?
I would imagine that proxies are meant to be consistent with getters/setters? So in this example obj.f() triggers the getter, yet if I understand what you're advocating it wouldn't trigger a [[Get]] trap?
var obj = { get f() { console.info( "getter called." ); return console.info.bind(console); } }
obj.f( "getter also called" )
"getter called." "getter also called"
Surely if these names where to be consistent for proxies then a get trap followed by an invoke trap should be fired?
2013/6/9 Brian Di Palma <offler at gmail.com>
On Sun, Jun 9, 2013 at 4:18 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
No, we can't just blindly call GetValue in step 1 as that would lead to the "get" trap being invoked on a proxy (which is the wrong trap).
Why can't we have the two traps trigger?
I would imagine that proxies are meant to be consistent with getters/setters?
It seems like the right reasoning. The result of these operations should be equivalent:
obj.f();
let f = obj.f; f.call(obj);
Juan
On Jun 8, 2013, at 3:30 AM, Till Schneidereit wrote:
At the last TC39 meeting, an agreement was reached for proxies to support an Invoke trap.
Finally (I remember that long thread 2 years ago when I was advocating for it).
Curious what was the reasoning behind this now, since as I understand the invoke trap wont' be for "virtual" (non-existing methods), but still for real function objects returned from the invoke trap -- in order to keep extracting to a var invariant working. If the later should be preserved, then the [[Get]] should be called anyways as I see.
Dmitry
2013/6/9 Brian Di Palma <offler at gmail.com>
On Sun, Jun 9, 2013 at 4:18 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
No, we can't just blindly call GetValue in step 1 as that would lead to the "get" trap being invoked on a proxy (which is the wrong trap).
Why can't we have the two traps trigger?
Think of invoke() as:
invoke = get + call (i.e. lookup a method and call it immediately)
IOW, the "invoke" behavior already encompasses all of the "get" behavior. There is no need to duplicate the "get" behavior separately.
One of the primary reasons why invoke() was added was precisely to avoid calling the get-trap, because the get-trap must often allocate and return a (bound) function, only to have that function be immediately called.
I would imagine that proxies are meant to be consistent with getters/setters? So in this example obj.f() triggers the getter, yet if I understand what you're advocating it wouldn't trigger a [[Get]] trap?
var obj = { get f() { console.info( "getter called." ); return console.info.bind(console); } } obj.f( "getter also called" )
> "getter called." > "getter also called"
If you want "invoke" to also trigger the "get" trap, just have your handler subclass DelegatingHandler as explained in 1. Assuming obj refers to your object definition above:
var proxy = new Proxy(obj,
new class extends DelegatingHandler {
get(target, name, rcvr) {
console.info("get trap called");
return super.get(target, name, rcvr); // forward to obj
},
invoke: (target, name, args, rcvr) {
console.info("invoke trap called");
return super.invoke(target, name, args, rcvr); // forward to obj
}
});
proxy.f("getter also called");
// "invoke trap called"
// "get trap called"
// "getter called."
// "getter also called"
The DelegatingHandler class's inherited "invoke" trap will call back on (i.e. reuse) the "get" trap to provide an answer. IOW, it is implemented as:
invoke: function(target, name, args, rcvr) {
var callable = this.get(target, name, rcvr);
return Function.prototype.apply.call(callable, rcvr, args); // i.e. callable.apply(rcvr, args)
}
I hope this clarifies things.
2013/6/9 Dmitry Soshnikov <dmitry.soshnikov at gmail.com>
On Jun 8, 2013, at 3:30 AM, Till Schneidereit wrote:
At the last TC39 meeting, an agreement was reached for proxies to support an Invoke trap.
Finally (I remember that long thread 2 years ago when I was advocating for it).
Curious what was the reasoning behind this now, since as I understand the invoke trap wont' be for "virtual" (non-existing methods), but still for real function objects returned from the invoke trap -- in order to keep extracting to a var invariant working. If the later should be preserved, then the [[Get]] should be called anyways as I see.
The main reason behind adding invoke() was to make rebinding |this| easy and efficient upon forwarding, essentially to make examples such as the following work:
var date = new Date() var proxy = new Proxy(date, { invoke: function(target, name, args, rcvr) { return target[name].apply(target, args); // note, |this| bound to target inside forwarded method, not to |rcvr| (= the proxy itself) } });
proxy.getFullYear() // works fine, no "error: not a Date" exception
2013/6/9 Juan Ignacio Dopazo <dopazo.juan at gmail.com>
It seems like the right reasoning. The result of these operations should be equivalent:
obj.f(); let f = obj.f; f.call(obj);
These operations will still be equivalent for:
- a proxy that implements both "get" and "invoke" in a consistent way.
- a proxy that implements neither "get" nor "invoke" (the default will be to forward in both cases)
- a proxy that subclasses DelegatingHandler 1 and only overrides "get"
If a proxy implements "get" and "invoke" in ways that are not mutually consistent, then those operations will not be equivalent. But there are many, many other cases where proxies can already do similar such things.
On Sun, Jun 9, 2013 at 6:07 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
I've already updated my working draft with [[Invoke]], but I'm on my way to the airport and can't create a digest of the changes right now.
However, they were derived from my earlier experiment which you can see at meetings:callproperty_experiment.pdf
So of the details changed and bugs were fixed, but the general idea is the same.
Thanks, that is great! The introduction of [[Invoke]] as a trap in the MOP itself instead of only on proxies makes a lot of sense.
On Mon, Jun 10, 2013 at 1:18 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
2013/6/9 Dmitry Soshnikov <dmitry.soshnikov at gmail.com>
On Jun 8, 2013, at 3:30 AM, Till Schneidereit wrote:
At the last TC39 meeting, an agreement was reached for proxies to support an Invoke trap.
Finally (I remember that long thread 2 years ago when I was advocating for it).
Curious what was the reasoning behind this now, since as I understand the invoke trap wont' be for "virtual" (non-existing methods), but still for real function objects returned from the invoke trap -- in order to keep extracting to a var invariant working. If the later should be preserved, then the [[Get]] should be called anyways as I see.
The main reason behind adding invoke() was to make rebinding |this| easy and efficient upon forwarding, essentially to make examples such as the following work:
var date = new Date() var proxy = new Proxy(date, { invoke: function(target, name, args, rcvr) { return target[name].apply(target, args); // note, |this| bound to target inside forwarded method, not to |rcvr| (= the proxy itself) } });
proxy.getFullYear() // works fine, no "error: not a Date" exception
I see, yeah, it makes sense. Thanks, I'll take a detailed look on the updated wiki page.
Dmitry
At the last TC39 meeting, an agreement was reached for proxies to support an Invoke trap. I'm currently implementing this trap in SpiderMonkey1 and realized that there's one conceptual issue that has to be decided in the spec:
Currently, the [[Get]] trap naturally handles method calls on the proxies, as these are gets followed by calls to the returned function value. With proxies also being able to trap [[Invoke]], things become less clear, with four possible options:
The first of these options seems conceptually cleanest to me, but I do think that the other options (perhaps except for the last one) can be argued for to some extend, too: the abstract operation
Invoke
, via its step 5,GetMethod
, contains a call to the [[Get]] operation, after all.