[[Invoke]] and implicit method calls

# Jason Orendorff (8 years ago)

There are some places in the spec where we might use [[Invoke]], but don't:

  • implicit .toString() and .valueOf() calls (except Object.prototype.toLocaleString)
  • implicit .toJSON() calls
  • calls to Proxy handler methods

This seems unfortunate. Proxies with default handlers will work in most places, thanks to the default [[Invoke]] trap, but not in ("" + proxyObject) etc.

Is this still likely to change?

The apparent reason [[Invoke]] is not used in these places is that they all do a [[Get]] first and check whether the property has a callable value. But in the algorithm for yield*, in 14.4.1.2, we have a similar check-before-calling situation, implemented using a different idiom:

(step 4.d.viii in the last algorithm of 14.4.1.2)

1. If HasProperty(iterator, "throw") is true, then
    a. Let innerResult be the result of Invoke(iterator, "throw", (received)).

Having a [[GetMethod]] hook instead of an [[Invoke]] hook might make it easier to use consistently everywhere.

# Allen Wirfs-Brock (8 years ago)

On Sep 9, 2013, at 2:37 PM, Jason Orendorff wrote:

There are some places in the spec where we might use [[Invoke]], but don't:

  • implicit .toString() and .valueOf() calls (except Object.prototype.toLocaleString)
  • implicit .toJSON() calls
  • calls to Proxy handler methods

This seems unfortunate. Proxies with default handlers will work in most places, thanks to the default [[Invoke]] trap, but not in ("" + proxyObject) etc.

Is this still likely to change?

I'll take a look at it. My first reaction is that this sounds like a good idea.

The apparent reason [[Invoke]] is not used in these places is that they all do a [[Get]] first and check whether the property has a callable value.

Actually, I don't think even considered those uses when I added [[Invoke]].

# Jason Orendorff (8 years ago)

On Mon, Sep 9, 2013 at 4:50 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

I'll take a look at it. My first reaction is that this sounds like a good idea.

A lot more can be found by searching for [[Call]]:

  • the call to @@toPrimitive from ToPrimitive (in 7.1.1)
  • the call to @@hasInstance from instanceofOperator (in 12.8.1)
  • the call to .toISOString from Date.prototype.toJSON (in 20.3.4.37)
  • the call to .join from Array.prototype.toString (in 22.1.3.27)
  • the call to .set from the Map constructor (in 23.1.1.1)
  • the call to .add from the Set constructor (in 23.2.1.1)

and at that point I stopped looking.

# Till Schneidereit (8 years ago)

On Tue, Sep 10, 2013 at 1:23 AM, Jason Orendorff <jason.orendorff at gmail.com>wrote:

On Mon, Sep 9, 2013 at 4:50 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

I'll take a look at it. My first reaction is that this sounds like a good idea.

A lot more can be found by searching for [[Call]]:

  • the call to @@toPrimitive from ToPrimitive (in 7.1.1)
  • the call to @@hasInstance from instanceofOperator (in 12.8.1)
  • the call to .toISOString from Date.prototype.toJSON (in 20.3.4.37)
  • the call to .join from Array.prototype.toString (in 22.1.3.27)
  • the call to .set from the Map constructor (in 23.1.1.1)
  • the call to .add from the Set constructor (in 23.2.1.1)

and at that point I stopped looking.

When implementing the [[Invoke]] trap in SpiderMonkey, I went with the assumption that it should apply to all calls of the forms receiver.fun() and receiver["fun"](). Jason pointed out that with (receiver) { fun() } should be caught, too. Doing anything else would make for a surprising restriction of proxies' capabilities compared to manually overriding methods, IMO.

# Tom Van Cutsem (8 years ago)

+1 for refactoring to use [[Invoke]] for most of these.

One reservation about the use of [[Invoke]] to call proxy traps (i.e. functions on handler objects): the alternative pattern of first probing with HasProperty destroys the "double lifting" pattern, which depends on the proxy only ever performing 1 type of operation (a property access) on its handler.

(double lifting is a pattern whereby the handler object is itself a proxy. It allows you to write very generic proxies that only need to implement a single "get" trap. See < soft.vub.ac.be/~tvcutsem/invokedynamic/assets/proxies.pdf> section

4.5 for details.)

If we refactor to use [[HasProperty]] + [[Invoke]] to call traps, the double lifting pattern requires implementing both the has() trap and the get() trap. Not fatal, but I just want to point it out.

, Tom

2013/9/10 Jason Orendorff <jason.orendorff at gmail.com>

# Brendan Eich (8 years ago)

I think we should preserve double-lifting via one trap in the meta-handler. No has-before-invoke. Allen?

# Domenic Denicola (8 years ago)

Note that determining desired pattern is relevant to the new DOM promises design (tracking this at domenic/promises-unwrapping#25). Omitting a few steps not relevant to the issue under discussion, currently we do:

  • Let then be Get(potentialThenable, "then").
  • If IsCallable(then), call then.[[Call]](potentialThenable, (resolve, reject)).

It seems like we'd want to apply the same logic here as we do to valueOf etc., which is what I tried to model this after.

# Till Schneidereit (8 years ago)

On Tue, Sep 10, 2013 at 1:31 PM, Domenic Denicola < domenic at domenicdenicola.com> wrote:

Note that determining desired pattern is relevant to the new DOM promises design (tracking this at domenic/promises-unwrapping#25). Omitting a few steps not relevant to the issue under discussion, currently we do:

  • Let then be Get(potentialThenable, "then").
  • If IsCallable(then), call then.[[Call]](potentialThenable, (resolve, reject)).

It seems like we'd want to apply the same logic here as we do to valueOf etc., which is what I tried to model this after.

I guess for this case as well as for all or most of those Jason mentioned, something like [[MaybeInvoke]] is required. I.e., a version of the [[Invoke]] internal method1 that has step 5 replaced by "If Type(method) is not Object, return undefined".

Sections 9.2.4.11 and 9.3.11 would have to be dealt with similarly, by introducing non-typeerror-throwing [[MaybeInvoke]] versions.

Your above steps would then become

  • call potentialThenable.[[MaybeInvoke]]("then", (resolve, reject), potentialThenable)

(probably with some results handling, but I don't know enough about promises to include that, and it doesn't seem relevant.)

# Allen Wirfs-Brock (8 years ago)

On Sep 10, 2013, at 3:55 AM, Till Schneidereit wrote:

When implementing the [[Invoke]] trap in SpiderMonkey, I went with the assumption that it should apply to all calls of the forms receiver.fun() and receiver["fun"](). Jason pointed out that with (receiver) { fun() } should be caught, too. Doing anything else would make for a surprising restriction of proxies' capabilities compared to manually overriding methods, IMO.

Yes, that's already covered in the spec. See people.mozilla.org/~jorendorff/es6-draft.html#sec-12.2.3 Step 1.b of EvaluateCall

# Till Schneidereit (8 years ago)

On Tue, Sep 10, 2013 at 5:03 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

On Sep 10, 2013, at 3:55 AM, Till Schneidereit wrote:

When implementing the [[Invoke]] trap in SpiderMonkey, I went with the assumption that it should apply to all calls of the forms receiver.fun() and receiver["fun"](). Jason pointed out that with (receiver) { fun() } should be caught, too. Doing anything else would make for a surprising restriction of proxies' capabilities compared to manually overriding methods, IMO.

Yes, that's already covered in the spec. See people.mozilla.org/~jorendorff/es6-draft.html#sec-12.2.3 Step 1.b of EvaluateCall

I didn't mean to put the focus on with, sorry. I meant that trapping [[Invoke]] shouldn't leave any calls untrapped that the user can "trap" manually by overriding methods. For that to work, a version of [[Invoke]] seems to be required that doesn't throw if the method doesn't exist on the receiver, though.

# Jason Orendorff (8 years ago)

On Tue, Sep 10, 2013 at 6:31 AM, Domenic Denicola <domenic at domenicdenicola.com> wrote:

Note that determining desired pattern is relevant to the new DOM promises design (tracking this at domenic/promises-unwrapping#25). Omitting a few steps not relevant to the issue under discussion, currently we do:

  • Let then be Get(potentialThenable, "then").
  • If IsCallable(then), call then.[[Call]](potentialThenable, (resolve, reject)).

It seems like we'd want to apply the same logic here as we do to valueOf etc., which is what I tried to model this after.

I think the simplest thing would be to replace [[Invoke]] with a [[GetMethod]] trap, and the idiom would then be exactly as above, but change Get to GetMethod.

The last step of Invoke(O, P, args) would be replaced with:

  1. Let method = O.[[GetMethod]](P, O).
  2. ReturnIfAbrupt(method).
  3. If IsCallable(method) is false, throw a TypeError.
  4. Return method.[[Call]](O, args).

For ordinary objects, [[GetMethod]] would be exactly like [[Get]]. The default proxy.[[GetMethod]] algorithm would be slightly different: it would return a bound function object, bound to the target.

tomvc, I'm not sure why [[Has]] followed by [[Invoke]] is worse for double-lifting than [[Get]] followed by [[Call]], but this change would bring us back closer to the latter. Is that sufficient?

# Allen Wirfs-Brock (8 years ago)

On Sep 10, 2013, at 4:14 AM, Brendan Eich wrote:

I think we should preserve double-lifting via one trap in the meta-handler. No has-before-invoke. Allen?

Well, then we loose the flexibility for a meta-level proxy to control the this-binding used when invoking proxy traps on it. Perhaps this isn't an issue for meta-level proxies, but it was the primary motivation for introducing [[Invoke]].

Also, see below...

/be

Tom Van Cutsem <mailto:tomvc.be at gmail.com> September 10, 2013 3:58 AM +1 for refactoring to use [[Invoke]] for most of these.

One reservation about the use of [[Invoke]] to call proxy traps (i.e. functions on handler objects): the alternative pattern of first probing with HasProperty destroys the "double lifting" pattern, which depends on the proxy only ever performing 1 type of operation (a property access) on its handler.

(double lifting is a pattern whereby the handler object is itself a proxy. It allows you to write very generic proxies that only need to implement a single "get" trap. See <soft.vub.ac.be/~tvcutsem/invokedynamic/assets/proxies.pdf, soft.vub.ac.be/~tvcutsem/invokedynamic/assets/proxies.pdf> section 4.5 for details.)

If we refactor to use [[HasProperty]] + [[Invoke]] to call traps, the double lifting pattern requires implementing both the has() trap and the get() trap. Not fatal, but I just want to point it out.

Another possibility is to do [[Get]] + [[Invoke]]? However, in most cases would do two [[Get]]'s of the method property and there is the possibility that they would produce inconsistent results. But that's also true of [[HasProperty]]+[[Invoke]]

I beginning to think that the best solution is to add a [[InvokeFunction]] internal method/trap. It's just like [[Invoke]] except that it assumes that the [[Get]] step has already been performed (the function is passed in rather than the property key). This still allows the handler to intercede in mapping the this value or other arguments. In fact, perhaps, we could simply replace [[Invoke]] with [[InvokeFunction]] and turn all the current [[Invoke]] calls into [[Get]]+[[InvokeFunction]]. This would also address the conditional [[Invoke]] issues being discussed in this thread.

# Allen Wirfs-Brock (8 years ago)

On Sep 10, 2013, at 8:20 AM, Jason Orendorff wrote:

On Tue, Sep 10, 2013 at 6:31 AM, Domenic Denicola <domenic at domenicdenicola.com> wrote:

Note that determining desired pattern is relevant to the new DOM promises design (tracking this at domenic/promises-unwrapping#25). Omitting a few steps not relevant to the issue under discussion, currently we do:

  • Let then be Get(potentialThenable, "then").
  • If IsCallable(then), call then.[[Call]](potentialThenable, (resolve, reject)).

It seems like we'd want to apply the same logic here as we do to valueOf etc., which is what I tried to model this after.

I think the simplest thing would be to replace [[Invoke]] with a [[GetMethod]] trap, and the idiom would then be exactly as above, but change Get to GetMethod.

The last step of Invoke(O, P, args) would be replaced with:

  1. Let method = O.[[GetMethod]](P, O).
  2. ReturnIfAbrupt(method).
  3. If IsCallable(method) is false, throw a TypeError.
  4. Return method.[[Call]](O, args).

Having to create a new bound function on every method call seems too expensive. But what about:

  1. Let method = O.[Get].
  2. ReturnIfAbrupt(method).
  3. If IsCallable(method) is false, throw a TypeError.
  4. Return O.[[InvokeFunction]](method,O, args).

Using [[InvokeFunction]] the Proxy still mediates the [[Call]] to method but it is assume the [[Get]] step has already been performed and has produced method.

# Allen Wirfs-Brock (8 years ago)

On Sep 10, 2013, at 5:15 AM, Till Schneidereit wrote:

On Tue, Sep 10, 2013 at 1:31 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote: Note that determining desired pattern is relevant to the new DOM promises design (tracking this at domenic/promises-unwrapping#25). Omitting a few steps not relevant to the issue under discussion, currently we do:

  • Let then be Get(potentialThenable, "then").
  • If IsCallable(then), call then.[[Call]](potentialThenable, (resolve, reject)).

It seems like we'd want to apply the same logic here as we do to valueOf etc., which is what I tried to model this after.

I guess for this case as well as for all or most of those Jason mentioned, something like [[MaybeInvoke]] is required. I.e., a version of the [[Invoke]] internal method[1] that has step 5 replaced by "If Type(method) is not Object, return undefined".

Sections 9.2.4.11 and 9.3.11 would have to be dealt with similarly, by introducing non-typeerror-throwing [[MaybeInvoke]] versions.

However, it appears that using that approach you can't distinguish between a missing/non-callable property value and a method invocation that actually returned undefined.

I think if we had [[MaybeInvoke]] we would have to define it so that we could make that distinction. Perhaps by returning a a tuple [success, value]

However, I think the [[InvokeFunction]] is a better solution.

# Jason Orendorff (8 years ago)

On Tue, Sep 10, 2013 at 11:11 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Having to create a new bound function on every method call seems too expensive.

Only for proxy method calls, and it's easily optimized away in the default case (i.e. there is no .getMethod handler method).

(As an aside, exchanges like the above have gotten so frequent they are beginning to feel a bit pro forma. I don't mean to sweep all performance concerns into one pile here.)

But what about:

  1. Let method = O.[Get].
  2. ReturnIfAbrupt(method).
  3. If IsCallable(method) is false, throw a TypeError.
  4. Return O.[[InvokeFunction]](method,O, args).

Using [[InvokeFunction]] the Proxy still mediates the [[Call]] to method but it is assume the [[Get]] step has already been performed and has produced method.

This is appealing. There are a few things [[GetMethod]] can do that this can't: [[InvokeFunction]] can't be used to make a proxy that mimics the behavior of noSuchMethod, for example. I don't think it can mimic ActionScript 3 Proxy.callMethod either.

If [[InvokeFunction]] is deemed powerful enough, I'm all for it.

# Till Schneidereit (8 years ago)

On Tue, Sep 10, 2013 at 7:45 PM, Jason Orendorff <jason.orendorff at gmail.com>wrote:

On Tue, Sep 10, 2013 at 11:11 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Having to create a new bound function on every method call seems too expensive.

Only for proxy method calls, and it's easily optimized away in the default case (i.e. there is no .getMethod handler method).

(As an aside, exchanges like the above have gotten so frequent they are beginning to feel a bit pro forma. I don't mean to sweep all performance concerns into one pile here.)

But what about:

  1. Let method = O.[Get].
  2. ReturnIfAbrupt(method).
  3. If IsCallable(method) is false, throw a TypeError.
  4. Return O.[[InvokeFunction]](method,O, args).

Using [[InvokeFunction]] the Proxy still mediates the [[Call]] to method but it is assume the [[Get]] step has already been performed and has produced method.

This is appealing. There are a few things [[GetMethod]] can do that this can't: [[InvokeFunction]] can't be used to make a proxy that mimics the behavior of noSuchMethod, for example. I don't think it can mimic ActionScript 3 Proxy.callMethod either.

If [[InvokeFunction]] is deemed powerful enough, I'm all for it.

Wasn't a major point in favor of having an [[Invoke]] trap that it wouldn't require returning a function from the trap at all? We'd lose this quality with both of these proposals.

# Jason Orendorff (8 years ago)

On Tue, Sep 10, 2013 at 12:45 PM, Jason Orendorff <jason.orendorff at gmail.com> wrote:

(As an aside, exchanges like the above have gotten so frequent they are beginning to feel a bit pro forma. I don't mean to sweep all performance concerns into one pile here.)

Sorry, I didn't finish writing this.

I meant to make some clever oblique reference to the curious phenomenon that performance concerns on es-discuss are always

  1. totally sincere and legitimate
  2. brought against proposals that one doesn't care for anyway
  3. dismissed by whoever does care for the proposal (also in total sincerity)

In any case I didn't mean to suggest anything untoward about Allen's argument, it was just a bit of a joke about our culture around here.

Yeah, never mind. Sorry for the sidetrack.

# Allen Wirfs-Brock (8 years ago)

On Sep 10, 2013, at 10:45 AM, Jason Orendorff wrote:

On Tue, Sep 10, 2013 at 11:11 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Having to create a new bound function on every method call seems too expensive.

Only for proxy method calls, and it's easily optimized away in the default case (i.e. there is no .getMethod handler method).

(As an aside, exchanges like the above have gotten so frequent they are beginning to feel a bit pro forma. I don't mean to sweep all performance concerns into one pile here.)

But what about:

  1. Let method = O.[Get].
  2. ReturnIfAbrupt(method).
  3. If IsCallable(method) is false, throw a TypeError.
  4. Return O.[[InvokeFunction]](method,O, args).

Using [[InvokeFunction]] the Proxy still mediates the [[Call]] to method but it is assume the [[Get]] step has already been performed and has produced method.

This is appealing. There are a few things [[GetMethod]] can do that this can't: [[InvokeFunction]] can't be used to make a proxy that mimics the behavior of noSuchMethod, for example. I don't think it can mimic ActionScript 3 Proxy.callMethod either.

Well, we didn't have those capabilities before we added [[Invoke]] and neither were primary use cases that motivated [[Invoke]].

If [[InvokeFunction]] is deemed powerful enough, I'm all for it.

I was thinking we might only need to have [[InvokeFunction]] but if we had both it and [[Invoke]] then then we'd only have we'd only those those capability in situations that required a conditional invoke. However, that would still create an anomalous edge case, for example toJSON not triggering a noSuchMethod emulator. Should it?

I'm beginning to like a conditional option on [[Invoke]].

Consider: [[Invoke]](P, ArgumentsList, Receiver, conditional=false)

If conditional is false, it works just like the current [[Invoke]] spec.

If conditional is true and the [[Get]] value is not callable (this includes undefined for a missing property) result is: [false, [[Get]] result]. if conditional is true and the [[Get]] value is callable, result is [true, value returned from [[Call]]

The conditional form would only be used in odd cases like the toJSON call.

ES code can accomplish the same thing via Reflect.invoke with true as the 4th argument.

# Till Schneidereit (8 years ago)

On Tue, Sep 10, 2013 at 8:35 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

On Sep 10, 2013, at 10:45 AM, Jason Orendorff wrote:

On Tue, Sep 10, 2013 at 11:11 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Having to create a new bound function on every method call seems too expensive.

Only for proxy method calls, and it's easily optimized away in the default case (i.e. there is no .getMethod handler method).

(As an aside, exchanges like the above have gotten so frequent they are beginning to feel a bit pro forma. I don't mean to sweep all performance concerns into one pile here.)

But what about:

  1. Let method = O.[Get].
  2. ReturnIfAbrupt(method).
  3. If IsCallable(method) is false, throw a TypeError.
  4. Return O.[[InvokeFunction]](method,O, args).

Using [[InvokeFunction]] the Proxy still mediates the [[Call]] to method but it is assume the [[Get]] step has already been performed and has produced method.

This is appealing. There are a few things [[GetMethod]] can do that this can't: [[InvokeFunction]] can't be used to make a proxy that mimics the behavior of noSuchMethod, for example. I don't think it can mimic ActionScript 3 Proxy.callMethod either.

Well, we didn't have those capabilities before we added [[Invoke]] and neither were primary use cases that motivated [[Invoke]].

If [[InvokeFunction]] is deemed powerful enough, I'm all for it.

I was thinking we might only need to have [[InvokeFunction]] but if we had both it and [[Invoke]] then then we'd only have we'd only those those capability in situations that required a conditional invoke. However, that would still create an anomalous edge case, for example toJSON not triggering a noSuchMethod emulator. Should it?

I'm beginning to like a conditional option on [[Invoke]].

Consider: [[Invoke]](P, ArgumentsList, Receiver, conditional=false)

If conditional is false, it works just like the current [[Invoke]] spec.

If conditional is true and the [[Get]] value is not callable (this includes undefined for a missing property) result is: [false, [[Get]] result]. if conditional is true and the [[Get]] value is callable, result is [true, value returned from [[Call]]

The conditional form would only be used in odd cases like the toJSON call.

ES code can accomplish the same thing via Reflect.invoke with true as the 4th argument.

I like it. This gets around the issue of distinguishing a return value of undefined from "no callable property with that name found" and would be easy to implement for proxies, too.

# Allen Wirfs-Brock (8 years ago)

On Sep 10, 2013, at 10:48 AM, Till Schneidereit wrote:

Wasn't a major point in favor of having an [[Invoke]] trap that it wouldn't require returning a function from the trap at all? We'd lose this quality with both of these proposals.

No, the major motivation was to provide a way to deal with different styles of |this| mapping that are needed for different styles of proxies. However, the ability to implement a method code without producing a function is a desirable secondary benefit

# Brendan Eich (8 years ago)
# Tom Van Cutsem (8 years ago)

2013/9/10 Jason Orendorff <jason.orendorff at gmail.com>

I think the simplest thing would be to replace [[Invoke]] with a [[GetMethod]] trap, and the idiom would then be exactly as above, but change Get to GetMethod.

The last step of Invoke(O, P, args) would be replaced with:

  1. Let method = O.[[GetMethod]](P, O).
  2. ReturnIfAbrupt(method).
  3. If IsCallable(method) is false, throw a TypeError.
  4. Return method.[[Call]](O, args).

For ordinary objects, [[GetMethod]] would be exactly like [[Get]]. The default proxy.[[GetMethod]] algorithm would be slightly different: it would return a bound function object, bound to the target.

tomvc, I'm not sure why [[Has]] followed by [[Invoke]] is worse for double-lifting than [[Get]] followed by [[Call]], but this change would bring us back closer to the latter. Is that sufficient?

[[Has]] followed by [[Invoke]] would trigger 2 distinct traps on the handler. [[Get]] followed by [[Call]] only triggers the "get" trap on the handler. The [[Call]] is made on the returned callable, it does not trigger the handler's "apply" trap. The point I was making is that using the current design, double-lifting requires implementing only 1 trap.

Your proposed [[GetMethod]] would re-introduce the bound-function-per-call overhead that the current [[Invoke]] design got rid of (see also my downstream reply to Allen).

# Tom Van Cutsem (8 years ago)

2013/9/10 Allen Wirfs-Brock <allen at wirfs-brock.com>

On Sep 10, 2013, at 4:14 AM, Brendan Eich wrote:

I think we should preserve double-lifting via one trap in the meta-handler. No has-before-invoke. Allen?

Well, then we loose the flexibility for a meta-level proxy to control the this-binding used when invoking proxy traps on it. Perhaps this isn't an issue for meta-level proxies, but it was the primary motivation for introducing [[Invoke]].

This isn't an issue for meta-level proxies. One way of explaining why is that double-lifting is an instance of the "virtual objects" use case (not the "caretaker" use case), hence doesn't need |this|-rebinding.

# Tom Van Cutsem (8 years ago)

2013/9/10 Allen Wirfs-Brock <allen at wirfs-brock.com>

On Sep 10, 2013, at 10:48 AM, Till Schneidereit wrote:

Wasn't a major point in favor of having an [[Invoke]] trap that it wouldn't require returning a function from the trap at all? We'd lose this quality with both of these proposals.

No, the major motivation was to provide a way to deal with different styles of |this| mapping that are needed for different styles of proxies. However, the ability to implement a method code without producing a function is a desirable secondary benefit

That's not how I would characterize it.

Till's argument is the argument we had identified in favor of an invoke() trap almost from day 1 of the Proxy proposal. It was offset by the desire to keep the invoke = get+call invariant and to avoid non-extractable callable properties. Much later, we figured invoke() was needed to allow proxy handlers to cheaply rebind |this| when intercepting method calls. This tipped the balance in favor of adding invoke(). But the idea of avoiding the creation of a temporary function object is to me at least as important.

# Till Schneidereit (8 years ago)

On Wed, Sep 11, 2013 at 4:44 AM, Brendan Eich <brendan at mozilla.com> wrote:

Except ariya.ofilabs.com/201108/hall-of-api-shame-boolean- trap.htmlariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html .

So one thing we could potentially do is to say that a proxy trapping [[Invoke]] either always returns [invokeSuccess, result] and doesn't need the boolean argument - or it's just never conditional: if you trap [[Invoke]], you have to serve all method calls, and Proxy.[[Invoke]] would just return result. Given that proxies users would most likely not be used for these corner cases in the majority of cases, and quite deliberately so in the others, this seems fine to me.

# Tom Van Cutsem (8 years ago)
  1. The [[Invoke]] operation is meant to primarily trap method calls generated by user-code of the form |obj.m(...args)|. This is by far the most common case, and I believe we should not extend [[Invoke]] with additional arguments that are irrelevant to this primary use case. Rather than having a boolean argument, better to statically separate the cases into two separate internal methods.

  2. There are a number of places in the spec where [[Invoke]] should be called conditionally, if we know for sure the property is callable.

Currently the pattern for this is [[Get]]+[[Call]]. We cannot refactor to [[Has]] + [[Invoke]] in general, because [[Has]] will return true also for non-callable values.

If we believe these are call-sites where it is worth avoiding the allocation of a function, then having an additional internal method like [[GetMethod]] or [[InvokeConditional]] makes sense, but I doubt it's worth the added complexity.

  1. For proxy trap invocations I maintain we are still better off with [[Get]] + [[Call]] to keep double-lifting as simple as possible.

Cheers, Tom

2013/9/11 Till Schneidereit <till at tillschneidereit.net>

# Brendan Eich (8 years ago)

Right, this matches my memory of the evolution and priority-shifting rationale.

# Brendan Eich (8 years ago)

Tom Van Cutsem <mailto:tomvc.be at gmail.com> September 11, 2013 7:08 AM

  1. The [[Invoke]] operation is meant to primarily trap method calls generated by user-code of the form |obj.m(...args)|. This is by far the most common case, and I believe we should not extend [[Invoke]] with additional arguments that are irrelevant to this primary use case. Rather than having a boolean argument, better to statically separate the cases into two separate internal methods.

+a bazillion. Srsly!

  1. There are a number of places in the spec where [[Invoke]] should be called conditionally, if we know for sure the property is callable.

Currently the pattern for this is [[Get]]+[[Call]]. We cannot refactor to [[Has]] + [[Invoke]] in general, because [[Has]] will return true also for non-callable values.

This is the toString/valueOf legacy.

If we believe these are call-sites where it is worth avoiding the allocation of a function, then having an additional internal method like [[GetMethod]] or [[InvokeConditional]] makes sense, but I doubt it's worth the added complexity.

We have zero evidence based on legacy (i.e. what's deployed).

  1. For proxy trap invocations I maintain we are still better off with [[Get]] + [[Call]] to keep double-lifting as simple as possible.

Sorry if I missed it: what complicates things if we use [[Invoke]] to support double-lifting?

# Allen Wirfs-Brock (8 years ago)

On Sep 11, 2013, at 11:02 AM, Brendan Eich wrote:

Tom Van Cutsem <mailto:tomvc.be at gmail.com> September 11, 2013 7:08 AM

  1. The [[Invoke]] operation is meant to primarily trap method calls generated by user-code of the form |obj.m(...args)|. This is by far the most common case, and I believe we should not extend [[Invoke]] with additional arguments that are irrelevant to this primary use case. Rather than having a boolean argument, better to statically separate the cases into two separate internal methods.

+a bazillion. Srsly!

I'm not sold. Two internal methods means there must be two proxy traps that must always be implemented as a pair. It would simply be wrong for a handler to implement the invoke trap and not also implement the invokeConditional trap. And inheriting from DelegatingHandler or ForwardHandler doesn't help because if either invoke or invokeConditional is over-ridden then then other also needs to be over-ridden.

Having to implement two coordinated traps, in what is probably going to be a relatively rate use case (needing to over-ride [[Invoke]]), sounds to me like a probable footgun. On the other-hand, having a single trap with an additional argument more explicitly communicates that a handler needs to deal with both the conditional and unconditional situations.

obj.m(...arg) is clearly the (dynamically) dominant use case for [[Invoke]]. But, besides it, there are actually statically more places in the ES6 spec where [[InvokeConditional]] would be appropriate than where [[Invoke]] is appropriate (6 to 3). And that isn't counting the implementation to the MOP calls on Proxy objects which would also be appropriate for [[InvokeConditional]] (but more on that below).

The dominant ordinary object use case for [Invoke]] is going to inlined and optimized by implementations so an optional extra argument that isn't used in by obj.m(...arg) will make absolutely no difference. Neither is it likely to make a difference in the built-ins that need to use conditional invokes. For unconditional [[Invoke]] on proxy objects the conditional argument basically adds only a single (not taken) conditional.

The real cost seems to be the authoring experience. Two coordinated traps is going to be harder to author and more error prone than a single trap with a parameter.

  1. There are a number of places in the spec where [[Invoke]] should be called conditionally, if we know for sure the property is callable.

Currently the pattern for this is [[Get]]+[[Call]]. We cannot refactor to [[Has]] + [[Invoke]] in general, because [[Has]] will return true also for non-callable values.

This is the toString/valueOf legacy.

There are currently 6 such places (not counting the Proxy trap invocators): 3 in ToPrimitive (ie toSrting/valueOf) 1 in instanceof to access @@hasInstance (legacy conpat. for missing @@hasInstance) 1 in [ ].toString to conditionally invoke 'join' method 1 in JSON.stringify conditionally invoke 'toJSON'

If we believe these are call-sites where it is worth avoiding the allocation of a function, then having an additional internal method like [[GetMethod]] or [[InvokeConditional]] makes sense, but I doubt it's worth the added complexity.

We have zero evidence based on legacy (i.e. what's deployed).

The consistency we are looking for is that all method invocations through a proxy go through an invoke trap so the handler can enforce consistent method invocation semantics.

I think the simplest solution is the extra parameter on invoke. If I can't sell that, my next preference is [[InvokeFunction]] as I described in an earlier message:

I beginning to think that the best solution is to add a [[InvokeFunction]] internal method/trap. It's just like [[Invoke]] except that it assumes that the [[Get]] step has already been performed (the function is passed in rather than the property key). This still allows the handler to intercede in mapping the this value or other arguments. In fact, perhaps, we could simply replace [[Invoke]] with [[InvokeFunction]] and turn all the current [[Invoke]] calls into [[Get]]+[[InvokeFunction]]. This would also address the conditional [[Invoke]] issues being discussed in this thread.

[[InvokeFunction]] requires use of [[Get]] to access a method so it reintroduces the possibility that a handler might have to cons up a function. I suspect this is rare which is the conclusion we came to when [[Invoke]] was originally considered long ago.

  1. For proxy trap invocations I maintain we are still better off with [[Get]] + [[Call]] to keep double-lifting as simple as possible.

Sorry if I missed it: what complicates things if we use [[Invoke]] to support double-lifting?

however proxy trap invocation should really be [[Invoke]] for semantics consistency. It really is a method invocation and the meta-meta-level handler should be given the opportunity to apply any method invocation policy it may have. Either a conditional [[Invoke]] or [[Get]]+[[InvokeFunction]] would supply adequate semantics.

Either some form of conditional [[Invoke]] or a [[InvokeFunction]] trap would still support double-lifting via a single trap. In the case of conditional [[Invoke]], it is that trap that would be implemented at the the meta-meta-level. With [[InvokeFunction]] it is the [[Get]] trap that would be implemented.

# Brendan Eich (8 years ago)

Allen Wirfs-Brock wrote:

  1. The [[Invoke]] operation is meant to primarily trap method calls generated by user-code of the form |obj.m(...args)|. This is by far the most common case, and I believe we should not extend [[Invoke]] with additional arguments that are irrelevant to this primary use case. Rather than having a boolean argument, better to statically separate the cases into two separate internal methods.

+a bazillion. Srsly!

I'm not sold. Two internal methods

Which two?

We had [[Get]]. We add [[Invoke]]. Or do you mean a different two?

# Jason Orendorff (8 years ago)

On Wed, Sep 11, 2013 at 9:08 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

Currently the pattern for this is [[Get]]+[[Call]]. We cannot refactor to [[Has]] + [[Invoke]] in general, because [[Has]] will return true also for non-callable values.

If we believe these are call-sites where it is worth avoiding the allocation of a function, then having an additional internal method like [[GetMethod]] or [[InvokeConditional]] makes sense, but I doubt it's worth the added complexity.

But as Allen said, [[Invoke]] is not a performance hack. It's a correctness hack.

It permits proxies to customize their behavior around this, and even naive .invoke trap users would definitely want those customizations to apply for implicit .toString() and .then().

# Till Schneidereit (8 years ago)

On Thu, Sep 12, 2013 at 12:38 AM, Jason Orendorff <jason.orendorff at gmail.com

wrote:

On Wed, Sep 11, 2013 at 9:08 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

Currently the pattern for this is [[Get]]+[[Call]]. We cannot refactor to [[Has]] + [[Invoke]] in general, because [[Has]] will return true also for non-callable values.

If we believe these are call-sites where it is worth avoiding the allocation of a function, then having an additional internal method like [[GetMethod]] or [[InvokeConditional]] makes sense, but I doubt it's worth the added complexity.

But as Allen said, [[Invoke]] is not a performance hack. It's a correctness hack.

It permits proxies to customize their behavior around this, and even naive .invoke trap users would definitely want those customizations to apply for implicit .toString() and .then().

I agree, anything else would be surprising. But can't we make the .invoke trap work for both [[Invoke]] and [[InvokeConditional]]? It would just always be called, and the places where [[InvokeConditional]] is used in the spec would treat it as though the required method exists on the receiver.

# Allen Wirfs-Brock (8 years ago)

On Sep 11, 2013, at 3:07 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

  1. The [[Invoke]] operation is meant to primarily trap method calls generated by user-code of the form |obj.m(...args)|. This is by far the most common case, and I believe we should not extend [[Invoke]] with additional arguments that are irrelevant to this primary use case. Rather than having a boolean argument, better to statically separate the cases into two separate internal methods.

+a bazillion. Srsly!

I'm not sold. Two internal methods

Which two?

We had [[Get]]. We add [[Invoke]]. Or do you mean a different two?

what Tom was referring to above: "statically separate the cases into two separate internal methods".

I presume, in context, he means [[Invoke]] and [[ConditionalInvoke]] but it could also mean [[Invoke]] and [[InvokeFunction]]

[[Invoke]] internally does [[Get]]+[[Call]], throws if method is missing or not callable [[ConditionalInvoke]] internally does [[Get]]+if method callable [[Call]], it doesn't throw if method is missing or not callable [[InvokeFunction]] internally does a [[Call]] and is called after an external [[Get]], so [[Get]] + [[InvokeFunction]] is normal usage.

All of the possible [[Invoke]] forms are passed both the Proxy and the target as arguments (along with the call arg list) and may remap the this value or arguments that are passed to the actual function call.

Calling a method, only if it exists or only if the property value is callable can be accomplished via: [[ConditionalInvoke]] or [[Get]]+[[InvokeFunction]]

[[Get]]+[[Invoke]] is bad because it normally will do two [[Get]] calls for the property.

Having both [[Invoke]] and [[ConditionalInvoke]] or [[Invoke]] and [[InvokeFunction]] means that handlers must duplicate invocation handling in two places.

Combining [[Invoke]] and [[ConditionalInvoke]] into a single operation is possible via parameterization and would eliminate the duplication or [[Invoke]] could be eliminated and we only have [[InvokeFunction]]

# Brendan Eich (8 years ago)

Jason Orendorff <mailto:jason.orendorff at gmail.com> September 11, 2013 3:38 PM

But as Allen said, [[Invoke]] is not a performance hack. It's a correctness hack.

It permits proxies to customize their behavior around this, and even naive .invoke trap users would definitely want those customizations to apply for implicit .toString() and .then().

Sorry, how can this be correctness when ECMA-262 and implementations have used [[Get]] + [[Call]] (after some IsCallable conditioning) forever?

# Allen Wirfs-Brock (8 years ago)

On Sep 11, 2013, at 5:13 PM, Brendan Eich wrote:

Jason Orendorff <mailto:jason.orendorff at gmail.com> September 11, 2013 3:38 PM

But as Allen said, [[Invoke]] is not a performance hack. It's a correctness hack.

It permits proxies to customize their behavior around this, and even naive .invoke trap users would definitely want those customizations to apply for implicit .toString() and .then().

Sorry, how can this be correctness when ECMA-262 and implementations have used [[Get]] + [[Call]] (after some IsCallable conditioning) forever?

It has nothing to do with what has worked forever in the absence of Proxy. The issue is consistent behavior in the presence of Proxies.

Consider an Proxy-based object the exposes a toJSON method. The proxy might be implementing a virtual object or it might be implementing a caretaker or some hybrid of the two styles. In does matter, other than it means that the proxy might be doing a translation of the this value (or even argument values) when its methods are invoked.

Let's assume p is such an object.

Presumably, p.toJSON() and JSON.stringify(p) should produce the same result because stringify internally (and conditionally) calls toJSON. But if

p.toJSON() is implements as: result = p.[[Invoke]]("toJSON",(), p)

and the internal call of toJSON within stringify is implemented as: func = p.[Get]; if (func && isCallable(func) result = func.[call]; else result = undefined;

then the results may not be identical because in the second case P's handler has not had a change to intercede of the passing of the this value and arguments to the function.

# Waldemar Horwat (8 years ago)

On 09/11/2013 03:38 PM, Jason Orendorff wrote:

On Wed, Sep 11, 2013 at 9:08 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

Currently the pattern for this is [[Get]]+[[Call]]. We cannot refactor to [[Has]] + [[Invoke]] in general, because [[Has]] will return true also for non-callable values.

If we believe these are call-sites where it is worth avoiding the allocation of a function, then having an additional internal method like [[GetMethod]] or [[InvokeConditional]] makes sense, but I doubt it's worth the added complexity.

But as Allen said, [[Invoke]] is not a performance hack. It's a correctness hack.

It permits proxies to customize their behavior around this, and even naive .invoke trap users would definitely want those customizations to apply for implicit .toString() and .then().

Except that [[Invoke]] doesn't solve the correctness problem either. As we discussed at a prior meeting, it fails in the case of passing 'this' as one of the arguments.

 Waldemar
# Allen Wirfs-Brock (8 years ago)

On Sep 11, 2013, at 6:30 PM, Waldemar Horwat wrote:

On 09/11/2013 03:38 PM, Jason Orendorff wrote:

On Wed, Sep 11, 2013 at 9:08 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

Currently the pattern for this is [[Get]]+[[Call]]. We cannot refactor to [[Has]] + [[Invoke]] in general, because [[Has]] will return true also for non-callable values.

If we believe these are call-sites where it is worth avoiding the allocation of a function, then having an additional internal method like [[GetMethod]] or [[InvokeConditional]] makes sense, but I doubt it's worth the added complexity.

But as Allen said, [[Invoke]] is not a performance hack. It's a correctness hack.

It permits proxies to customize their behavior around this, and even naive .invoke trap users would definitely want those customizations to apply for implicit .toString() and .then().

Except that [[Invoke]] doesn't solve the correctness problem either. As we discussed at a prior meeting, it fails in the case of passing 'this' as one of the arguments.

Yes, but that's an orthogonal issue.

Using [[Invoke]] trap, a Proxy hander can easily perform shallow translation on arguments as well as on the passed this value. It can even try to do deep translation if it thinks it is appropriate.

But, the issue here isn't about the nature of those possible translations. It's about making sure that if there are any they are consistently applied regardless of how the method invocation was make.

# Jason Orendorff (8 years ago)

On Wed, Sep 11, 2013 at 8:30 PM, Waldemar Horwat <waldemar at google.com> wrote:

On 09/11/2013 03:38 PM, Jason Orendorff wrote:

But as Allen said, [[Invoke]] is not a performance hack. It's a correctness hack.

It permits proxies to customize their behavior around this, and even naive .invoke trap users would definitely want those customizations to apply for implicit .toString() and .then().

Except that [[Invoke]] doesn't solve the correctness problem either. As we discussed at a prior meeting, it fails in the case of passing 'this' as one of the arguments.

The specific correctness problem [[Invoke]] addresses is the ability for a proxy to maintain both

  1. proxy.method behaves like target.method; and

  2. proxy.method() behaves like target.method().

But you’re right, even with [[Invoke]], a proxy cannot further maintain that

  1. proxy.method.bind(proxy) behaves like target.method.bind(target)

or any number of other desirable properties. [[Invoke]] is skin deep.

I didn’t say [[Invoke]] solved “the correctness problem”, as if there’s only one. Proxies are correctness problem factories. I don’t know if there’s a single possible use of proxies with a comprehensive, easily expressible correctness criterion that actually holds. Proxies are all about “good enough”. Given that, skin-deep [[Invoke]] makes more sense. It causes the defaults to behave as expected in some rather basic, important cases.

There are going to be a lot of Proxy use cases where the programmer has not the slightest interest in any kind of algebraic law like the above. There will be a lot of proxy handlers that only have .get().

# Tom Van Cutsem (8 years ago)

2013/9/11 Brendan Eich <brendan at mozilla.com>

  1. For proxy trap invocations I maintain we are still better off with

[[Get]] + [[Call]] to keep double-lifting as simple as possible.

Sorry if I missed it: what complicates things if we use [[Invoke]] to support double-lifting?

Trap method calls on the handler are conditional: if the handler doesn't implement a trap, the proxy will forward to the target instead.

Currently we call handler.[[Get]] + verify whether the result is callable. In the case of double-lifting, this means the meta-handler only has to implement the "get" trap.

If we would have a hypothetical [[ConditionalInvoke]] with corresponding trap, then the meta-handler would just implement that trap instead. This would work.

Any other solution that uses 2 methods (e.g. [[Has]]+[[Invoke]], [[Get]]+[[Invoke]], [[Get]]+[[InvokeFunction]]) will require the meta-handler to implement 2 traps. Again, this is not fatal, but it does make the pattern uglier.

# Tom Van Cutsem (8 years ago)

2013/9/11 Allen Wirfs-Brock <allen at wirfs-brock.com>

I'm not sold. Two internal methods means there must be two proxy traps that must always be implemented as a pair. It would simply be wrong for a handler to implement the invoke trap and not also implement the invokeConditional trap. And inheriting from DelegatingHandler or ForwardHandler doesn't help because if either invoke or invokeConditional is over-ridden then then other also needs to be over-ridden.

The Proxy API is rife with such dependencies among traps. In fact, the dependency already exists between "get" and "invoke".

That is why you and I came up with < harmony:virtual_object_api>.

Assuming we have both "invoke" and "invokeConditional" traps, I would assume "invokeConditional" to be implemented in terms of "get", e.g. DelegatingHandler would implement it as:

invokeConditional: function(target, name, args, receiver = target) { var fun = this.get(target, name, receiver); if (!isCallablable(fun)) { return undefined; // or a distinguishing sentinel value } return Reflect.apply(fun, receiver, args); }

A handler extending DelegatingHandler would inherit appropriate versions of "get", "invoke" and "invokeConditional" that will all be mutually consistent.

[[InvokeFunction]] requires use of [[Get]] to access a method so it reintroduces the possibility that a handler might have to cons up a function. I suspect this is rare which is the conclusion we came to when [[Invoke]] was originally considered long ago.

It wouldn't be rare for virtual object abstractions: these can't return a pre-existing method on their target object from their "get" trap, hence will need to cons up a function.

  1. For proxy trap invocations I maintain we are still better off with [[Get]] + [[Call]] to keep double-lifting as simple as possible.

Sorry if I missed it: what complicates things if we use [[Invoke]] to support double-lifting?

however proxy trap invocation should really be [[Invoke]] for semantics consistency. It really is a method invocation and the meta-meta-level handler should be given the opportunity to apply any method invocation policy it may have. Either a conditional [[Invoke]] or [[Get]]+[[InvokeFunction]] would supply adequate semantics.

For double-lifting, the meta-handler is a virtual object that wants to ignore its target. It doesn't want to re-bind the |this|-value. Here's a gist containing the pattern: < gist.github.com/tvcutsem/6536442>

Either some form of conditional [[Invoke]] or a [[InvokeFunction]] trap would still support double-lifting via a single trap. In the case of conditional [[Invoke]], it is that trap that would be implemented at the the meta-meta-level. With [[InvokeFunction]] it is the [[Get]] trap that would be implemented.

Yes, conditional [[Invoke]] would also be sufficient (and would get rid of the function allocation per intercepted operation).

If we'd have [[InvokeFunction]], the metaHandler still needs to implement "get" and "invokeFunction". If it wouldn't override "invokeFunction", the default would be wrong (assuming the default is, as for all missing traps, to forward to the target).

# Tom Van Cutsem (8 years ago)

2013/9/12 Till Schneidereit <till at tillschneidereit.net>

I agree, anything else would be surprising. But can't we make the .invoke trap work for both [[Invoke]] and [[InvokeConditional]]? It would just always be called, and the places where [[InvokeConditional]] is used in the spec would treat it as though the required method exists on the receiver.

I don't understand. Could you expand? If Proxy.[[InvokeConditional]] triggers the "invoke" trap, presumably the trap still needs to somehow signal "the property wasn't callable"?

# Tom Van Cutsem (8 years ago)

2013/9/12 Allen Wirfs-Brock <allen at wirfs-brock.com>

what Tom was referring to above: "statically separate the cases into two separate internal methods".

I presume, in context, he means [[Invoke]] and [[ConditionalInvoke]] but it could also mean [[Invoke]] and [[InvokeFunction]]

Yes (I meant [[Invoke]] + [[ConditionalInvoke]]).

Jason and Allen's arguments convince me that we're better off refactoring all [[Get]]+[[Call]] occurrences to use some form of conditional [[Invoke]].

Out of all the circulating alternatives, I prefer [[Invoke]] + [[ConditionalInvoke]] as it allows virtual object proxies to avoid consing a function (also, [[ConditionalInvoke]], much more so than [[InvokeFunction]], neatly captures the intent of what's going on in the spec)

As for the consistency costs, we already have them. This only ups the ante for standardizing on a good library of subclassable Handler abstractions.

# Till Schneidereit (8 years ago)

On Thu, Sep 12, 2013 at 2:35 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

2013/9/12 Till Schneidereit <till at tillschneidereit.net>

I agree, anything else would be surprising. But can't we make the .invoke trap work for both [[Invoke]] and [[InvokeConditional]]? It would just always be called, and the places where [[InvokeConditional]] is used in the spec would treat it as though the required method exists on the receiver.

I don't understand. Could you expand? If Proxy.[[InvokeConditional]] triggers the "invoke" trap, presumably the trap still needs to somehow signal "the property wasn't callable"?

That latter assumption is exactly the one I wasn't sure about. Implementing the invoke trap could mean that you just trap all method calls, instead. On the surface, that seems like a fairly consistent assumption and prevents the inter-trap dependencies.

However, your argument that those dependencies aren't really anything new and most users should work with the virtual object api instead of fully implementing proxies on their own convinced me that giving up on [[InvokeConditional]] support in proxies isn't the right trade-off.

# Tom Van Cutsem (8 years ago)

MarkM and I talked about conditional invocation offline and we convinced ourselves to argue for the status-quo (i.e. continue to encode conditional invocations as [[Get]]+[[Call]]).

The most compelling argument we can think of is that [[Get]]+[[Call]] is also the pattern JavaScript programmers use to express conditional method calls today:

var f = obj[name]; if (f) { f.call(…); } else { … }

No matter whether or how we extend the MOP, such code exists and will continue to exist, and well-designed proxies that want to re-bind |this| must already deal with this pattern by implementing the "get" trap correctly.

We already have distinct "get" and "invoke" traps. Proxy writers must already understand the dependency between these traps. Let's not make things more complicated by adding a third, or by unnecessarily complicating the signature of "invoke".

Given the status-quo, a proxy that wants to rebind |this| to the target object must do so by overriding both "get" and "invoke". In the "invoke" trap, |this|-rebinding is cheap (i.e. no need for bound functions). In the "get" trap, it requires the handler to return a bound function (which costs). But the cost will not be paid for ordinary method calls, which are expected to be the common case.

# Brendan Eich (8 years ago)

Tom Van Cutsem <mailto:tomvc.be at gmail.com> September 13, 2013 6:36 PM MarkM and I talked about conditional invocation offline and we convinced ourselves to argue for the status-quo (i.e. continue to encode conditional invocations as [[Get]]+[[Call]]).

The most compelling argument we can think of is that [[Get]]+[[Call]] is also the pattern JavaScript programmers use to express conditional method calls today:

var f = obj[name]; if (f) { f.call(…); } else { … }

No matter whether or how we extend the MOP, such code exists and will continue to exist, and well-designed proxies that want to re-bind |this| must already deal with this pattern by implementing the "get" trap correctly.

Whew. Thanks.

We already have distinct "get" and "invoke" traps. Proxy writers must already understand the dependency between these traps. Let's not make things more complicated by adding a third, or by unnecessarily complicating the signature of "invoke".

Or by adding boolean parameters if we can avoid 'em.

Given the status-quo, a proxy that wants to rebind |this| to the target object must do so by overriding both "get" and "invoke". In the "invoke" trap, |this|-rebinding is cheap (i.e. no need for bound functions). In the "get" trap, it requires the handler to return a bound function (which costs).

Right! JS has first-class functions, any memoizing vs. recreating is up to the object, but some function must be found or created that binds the right |this|.

But the cost will not be paid for ordinary method calls, which are expected to be the common case.

Thanks again, to you and Mark.

# Jason Orendorff (8 years ago)

On Fri, Sep 13, 2013 at 11:36 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

MarkM and I talked about conditional invocation offline and we convinced ourselves to argue for the status-quo (i.e. continue to encode conditional invocations as [[Get]]+[[Call]]).

The most compelling argument we can think of is that [[Get]]+[[Call]] is also the pattern JavaScript programmers use to express conditional method calls today:

var f = obj[name]; if (f) { f.call(…); } else { … }

No matter whether or how we extend the MOP, such code exists and will continue to exist, and well-designed proxies that want to re-bind |this| must already deal with this pattern by implementing the "get" trap correctly.

Tom, it seems to me if there’s such a thing as “implementing the "get" trap correctly”, i.e. such that method calls work, that removes the main motivation for having [[Invoke]] in the first place. To recap what else [[Invoke]] achieves, in the light of that:

  • improved performance for proxies, because method calls go through one proxy handler trap rather than two, and no temporary function object is allocated

  • proxies can observe a bit about the caller (whether or not it's an [[Invoke]] call site), when a method is called

If that's all, it seems like we should definitely remove [[Invoke]] and the .invoke trap. The MOP was already complicated enough. The performance argument is a non-starter, and the other “feature” is entirely undesirable.

# Tom Van Cutsem (8 years ago)

2013/9/17 Jason Orendorff <jason.orendorff at gmail.com>

If that's all, it seems like we should definitely remove [[Invoke]] and the .invoke trap. The MOP was already complicated enough. The performance argument is a non-starter, and the other “feature” is entirely undesirable.

I'm trying to reconstruct Allen's motivation for including [[Invoke]], which had to do with proxies that forward method calls to target objects that depend on their this-binding to access private state.

Assume that we want to proxy the following target object:

var privateState = new WeakMap(); var target = { m: function() { return privateState.get(this); }; privateState.set(target, 42); assert( target.m() === 42 );

// I need otherTarget to make a point later var otherTarget = {}; privateState.set(otherTarget, 43); assert( target.m.call(otherTarget) === 43 ); // we can still re-bind |this|

Now consider a naive forwarding proxy, written as:

var p1 = new Proxy(target, { get: function(target, name, receiver) { return target[name]; } });

Now: p1.m() === undefined // bad, because |this| inside target.m is bound to proxy instead of target. But: p1.m.call(otherTarget) === 43 // good, clients can still rebind |this|.

To solve the first problem, the proxy can be refined to eagerly bind any method properties:

var p2 = new Proxy(target, { get: function(target, name, receiver) { var prop = target[name]; if (typeof prop === "function") return prop.bind(target); return prop; } });

Now: p2.m() === 42 // good But: p2.m.call(otherTarget) !== 43 // bad, cannot re-bind |this| on extracted properties

Adding an invoke() trap gives us a way to solve this:

var p3 = new Proxy(target, { get: function(target, name, receiver) { return target[name]; // no bind-on-extraction }, invoke: function(target, name, args, receiver) { return target[name].apply(target, args); } });

Now: p3.m() === 42 // good And: p3.m.call(otherTarget) === 43 // good

I guess we could indeed drop the invoke() trap, if we are willing to use the following, more intricate, pattern:

var p4 = new Proxy(target, { get: function(target, name, receiver) { var prop = target[name]; if (typeof prop === "function") { return function(...args) { var self = (this === p4 ? target : this); // re-bind |this| only if bound to the proxy, otherwise don't rebind return prop.apply(self, args); } } return prop; } });

Now: p4.m() === 42 // good And: p4.m.call(otherTarget) === 43 // good

AFAICT, performance arguments aside, the question of whether or not to include invoke() boils down to whether you prefer the p3 or p4 pattern.

# David Bruant (8 years ago)

Le 17/09/2013 15:36, Jason Orendorff a écrit :

  • improved performance for proxies, because method calls go through one proxy handler trap rather than two, and no temporary function object is allocated The performance argument is a non-starter

Why? (damned! how do I find myself defending invoke while I'm relatively against it...) Till Schneidereit's enthusiasm about it and the promised boost for Shumway suggests that the performance difference is significant to not be a non-starter. Actual measurements with analysis of state of implementation could be relevant here. cc'ing Till in hope he's done such measurements.

A question is whether the same performance benefit could be achieved without invoke or more accurately, how much more effort is it?

# Brendan Eich (8 years ago)

David Bruant <mailto:bruant.d at gmail.com> September 18, 2013 12:52 PM

Why? (damned! how do I find myself defending invoke while I'm relatively against it...) Till Schneidereit's enthusiasm about it and the promised boost for Shumway suggests that the performance difference is significant to not be a non-starter.

Shumway means AS3 => JS and AS3 indeed had an invoke trap (from ES4

memories).

Actual measurements with analysis of state of implementation could be relevant here. cc'ing Till in hope he's done such measurements.

Right, it's a bit rash to say performance counts for nothing. It is usually secondary, though -- and rightly so. Optimizations come along over time. Asymptotic complexity is a fair concern, and allocations cost -- but if a JS JIT can prove they won't be observed then it can eliminate them.

A question is whether the same performance benefit could be achieved without invoke or more accurately, how much more effort is it?

On invoke, the Performance Jury is still out.

# David Bruant (8 years ago)

Le 18/09/2013 14:28, Tom Van Cutsem a écrit :

2013/9/17 Jason Orendorff <jason.orendorff at gmail.com <mailto:jason.orendorff at gmail.com>>

If that's all, it seems like we should definitely remove [[Invoke]]
and the .invoke trap. The MOP was already complicated enough. The
performance argument is a non-starter, and the other "feature" is
entirely undesirable.

I'm trying to reconstruct Allen's motivation for including [[Invoke]], which had to do with proxies that forward method calls to target objects that depend on their this-binding to access private state.

Assume that we want to proxy the following target object:

var privateState = new WeakMap(); var target = { m: function() { return privateState.get(this); }; privateState.set(target, 42); assert( target.m() === 42 );

// I need otherTarget to make a point later var otherTarget = {}; privateState.set(otherTarget, 43); assert( target.m.call(otherTarget) === 43 ); // we can still re-bind |this|

Now consider a naive forwarding proxy, written as:

var p1 = new Proxy(target, { get: function(target, name, receiver) { return target[name]; } });

Now: p1.m() === undefined // bad, because |this| inside target.m is bound to proxy instead of target. But: p1.m.call(otherTarget) === 43 // good, clients can still rebind |this|.

To solve the first problem, the proxy can be refined to eagerly bind any method properties:

var p2 = new Proxy(target, { get: function(target, name, receiver) { var prop = target[name]; if (typeof prop === "function") return prop.bind(target); return prop; } });

Now: p2.m() === 42 // good But: p2.m.call(otherTarget) !== 43 // bad, cannot re-bind |this| on extracted properties

Adding an invoke() trap gives us a way to solve this:

var p3 = new Proxy(target, { get: function(target, name, receiver) { return target[name]; // no bind-on-extraction }, invoke: function(target, name, args, receiver) { return target[name].apply(target, args); } });

Now: p3.m() === 42 // good And: p3.m.call(otherTarget) === 43 // good

I guess we could indeed drop the invoke() trap, if we are willing to use the following, more intricate, pattern:

var p4 = new Proxy(target, { get: function(target, name, receiver) { var prop = target[name]; if (typeof prop === "function") { return function(...args) { var self = (this === p4 ? target : this); // re-bind |this| only if bound to the proxy, otherwise don't rebind return prop.apply(self, args); } } return prop; } });

Now: p4.m() === 42 // good And: p4.m.call(otherTarget) === 43 // good

AFAICT, performance arguments aside, the question of whether or not to include invoke() boils down to whether you prefer the p3 or p4 pattern.

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.

# Tom Van Cutsem (8 years ago)

2013/9/18 David Bruant <bruant.d at gmail.com>

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.

# Tom Van Cutsem (8 years ago)

2013/9/18 Tom Van Cutsem <tomvc.be at gmail.com>

AFAICT, performance arguments aside, the question of whether or not to include invoke() boils down to whether you prefer the p3 or p4 pattern.

I just figured out that the p3 example is "flawed" in the sense that:

p3.m.call(p3) // does not re-bind |this|, so returns undefined rather than 42

p4 handles this fine:

p4.m.call(p4) // 42

However, it's not clear to me that code such as the above needs implicit |this|-rebinding.

# Till Schneidereit (8 years ago)

(sending from the right address this time, with apologies for the double post to the CC'd people.)

On Wed, Sep 18, 2013 at 6:56 PM, Brendan Eich <brendan at mozilla.com> wrote:

David Bruant <mailto:bruant.d at gmail.com>

September 18, 2013 12:52 PM

Why? (damned! how do I find myself defending invoke while I'm relatively against it...) Till Schneidereit's enthusiasm about it and the promised boost for Shumway suggests that the performance difference is significant to not be a non-starter.

I'm not entirely sure my feelings about an invoke trap are accurately described by the word "enthusiastic". ;)

Shumway means AS3 => JS and AS3 indeed had an invoke trap (from ES4

memories).

Correct.

Actual measurements with analysis of state of implementation could be

relevant here. cc'ing Till in hope he's done such measurements.

Right, it's a bit rash to say performance counts for nothing. It is usually secondary, though -- and rightly so. Optimizations come along over time. Asymptotic complexity is a fair concern, and allocations cost -- but if a JS JIT can prove they won't be observed then it can eliminate them.

I haven't done any measurements, no. My interest in an invoke trap is mainly driven by Shumway's requirements. The fact that AS3 proxies have an invoke trap means that, as soon as any proxies are used in a vm instance at all, we have to guard all method calls in our JITted code at callsites where we can't prove that the receiver won't ever be a proxy. This affects a large percentage of Flash applications, because remoting (Flash's RPC solution) uses proxies to forward method calls on the client-side object to the server side as RPCs.

Note that I don't think that "AS3 has invoke trapping and Shumway's having a hard time without JS having it, too" is a good argument, and I'm not making it. If the invoke trap is seen as superfluous or even detrimental, JS shouldn't get it.

Frankly, I can't come up with very good non-Shumway-related arguments in favor of an invoke trap. It seems to me like being able to differentiate between method calls and property gets on the object protocol level makes sense. All I can come up with in terms of use cases is logging or forwarding of calls where your forwarding target doesn't give you the info needed to identify calls before the fact. Meaning remote procedure calls. Why you'd need to support both non-callable properties and methods on an RPC client, I don't know, however.

The fact remains that, without the invoke trap, proxy users have to get the information whether a method call is about to happen from other sources, even though the runtime would be perfectly able to provide it. Putting the call-if-defined wrinkle caused (or exposed) by other spec algorithms aside for a minute, I'd argue that not providing this information makes using proxies strictly more complex, and strictly less performant.

Taking that wrinkle into consideration again, I still think it might be handled by wrapping the "get function, call if get succeeded" steps used in some spec algorithms into an abstract operation. That operation could have a special case along the lines of "if the receiver is a proxy with an invoke trap, call that with the args list". That would mean that having an invoke trap causes all of these maybe-calls to be actual calls. Which seems ok to me.

# David Bruant (8 years ago)

Le 18/09/2013 20:52, Tom Van Cutsem a écrit :

2013/9/18 David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>>

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.

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?

# Jason Orendorff (8 years ago)

On Wed, Sep 18, 2013 at 11:52 AM, David Bruant <bruant.d at gmail.com> wrote:

Le 17/09/2013 15:36, Jason Orendorff a écrit :

The performance argument is a non-starter

Why? (damned! how do I find myself defending invoke while I'm relatively against it...) Till Schneidereit's enthusiasm about it and the promised boost for Shumway suggests that the performance difference is significant to not be a non-starter.

OK, I'll unpack this. There are two different performance arguments.

  1. "Method calls on proxies will be faster with the .invoke hook."

    I think implementations can optimize this transparently, if it matters. Also, I could be wrong, but I think implementers generally don't want TC39 to help them optimize things by adding extra features.

    Also, I think this doesn't matter. It's hard to prove a negative, but here's a data point: CPython has been around 22 years now and to this day allocates a new bound-method object for every single method call. Not just proxies. All objects. All methods! Granted, CPython doesn't aspire to JS-like performance—but they do care about speed, and this would be low-hanging fruit for them if it mattered. Unlike the proposal we're talking about, it could be done transparently and would affect all existing Python code. Why haven't they done it? A little inline cache. It’d be easy.

    It just doesn't matter. The most performance-critical stuff won't be able to afford going through a proxy, full stop. Everything else won't care if there's one trap or two. (The occasionally-alleged use cases involving Proxy handlers doing high-latency remote procedure calls seem like a bit of a stretch, given the async world that JS actually inhabits.)

  2. Till's use case.

    Till is translating ActionScript to JavaScript. ActionScript has something like invoke traps: help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/utils/Proxy.html

    From Till's perspective, any AS method call could be a proxy method call. It becomes much easier to translate them to fast JS code if we just add .invoke traps to JS as well.

    As much as I admire the Shumway project, this isn't a good reason to add a language feature.

I think both performance arguments are non-starters, not because I dismiss such arguments out of hand (I don't) but because I looked and they just don't go.

# Brendan Eich (8 years ago)

Jason Orendorff <mailto:jason.orendorff at gmail.com> September 18, 2013 5:38 PM

OK, I'll unpack this. There are two different performance arguments.

  1. "Method calls on proxies will be faster with the .invoke hook."

I think implementations can optimize this transparently, if it matters. Also, I could be wrong, but I think implementers generally don't want TC39 to help them optimize things by adding extra features.

Also, I think this doesn't matter. It's hard to prove a negative, but here's a data point: CPython has been around 22 years now and to this day allocates a new bound-method object for every single method call. Not just proxies. All objects. All methods! Granted, CPython doesn't aspire to JS-like performance—but they do care about speed, and this would be low-hanging fruit for them if it mattered. Unlike the proposal we're talking about, it could be done transparently and would affect all existing Python code. Why haven't they done it? A little inline cache. It’d be easy.

It just doesn't matter.

CPython refcounts, which means eager free. Bet if they used naive GC they'd have done something.

The most performance-critical stuff won't be able to afford going through a proxy, full stop. Everything else won't care if there's one trap or two. (The occasionally-alleged use cases involving Proxy handlers doing high-latency remote procedure calls seem like a bit of a stretch, given the async world that JS actually inhabits.)

Boris is working on Proxy-based form objects. You may be right that they are not on perf critical paths.

  1. Till's use case.

Till is translating ActionScript to JavaScript. ActionScript has something like invoke traps: help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/utils/Proxy.html

From Till's perspective, any AS method call could be a proxy method call. It becomes much easier to translate them to fast JS code if we just add .invoke traps to JS as well.

As much as I admire the Shumway project, this isn't a good reason to add a language feature.

Agreed, it's just another bean on the pile. There need not be one reason, or only one or a few big reasons.

I think both performance arguments are non-starters, not because I dismiss such arguments out of hand (I don't) but because I looked and they just don't go.

Your CPython argument is suspect, but the real data we seek is a JS-specific perf bake-off, more than Shumway but at least that: some significant macro-benchmark suite that exercises method calls and methods as funargs, and that is not synthetic or based on old/rare real world code.

# Boris Zbarsky (8 years ago)

On 9/18/13 10:11 PM, Brendan Eich wrote:

Boris is working on Proxy-based form objects.

Not working on. They've been shipping for multiple Firefox releases now.

Of more interest than forms is the fact that "window" and "document" are both proxies. So document.getElementById is a method call on a proxy, for example.

Also, foo.style is a proxy. Method calls on that are rare, but getter/setter invocations are common, of course.

Now in practice SpiderMonkey has some special-casing already for these proxies internally. For example, we know, with just a shape guard on the "target" that foo.style.display will in fact end up calling a getter on the proto and just bypass the proxy hooks altogether for that get. We have similar things for the Document object. Invoking DOM methods on it still has about 2x the overhead of invoking them on normal DOM objects, but I expect we'll be able to fix that.

For non-built-in stuff ("expandos"), of course, things are slow on document.

And "window" is a special snowflake in all sorts of ways, of course...

Your CPython argument is suspect, but the real data we seek is a JS-specific perf bake-off, more than Shumway but at least that: some significant macro-benchmark suite that exercises method calls and methods as funargs, and that is not synthetic or based on old/rare real world code.

Sure would be nice. ;)

# Allen Wirfs-Brock (8 years ago)

On Sep 17, 2013, at 9:36 AM, Jason Orendorff wrote:

On Fri, Sep 13, 2013 at 11:36 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

MarkM and I talked about conditional invocation offline and we convinced ourselves to argue for the status-quo (i.e. continue to encode conditional invocations as [[Get]]+[[Call]]).

The most compelling argument we can think of is that [[Get]]+[[Call]] is also the pattern JavaScript programmers use to express conditional method calls today:

var f = obj[name]; if (f) { f.call(…); } else { … }

No matter whether or how we extend the MOP, such code exists and will continue to exist, and well-designed proxies that want to re-bind |this| must already deal with this pattern by implementing the "get" trap correctly.

Tom, it seems to me if there’s such a thing as “implementing the "get" trap correctly”, i.e. such that method calls work, that removes the main motivation for having [[Invoke]] in the first place. To recap what else [[Invoke]] achieves, in the light of that:

  • improved performance for proxies, because method calls go through one proxy handler trap rather than two, and no temporary function object is allocated

  • proxies can observe a bit about the caller (whether or not it's an [[Invoke]] call site), when a method is called

If that's all, it seems like we should definitely remove [[Invoke]] and the .invoke trap. The MOP was already complicated enough. The performance argument is a non-starter, and the other “feature” is entirely undesirable.

I've actually become convinced that [[Get]] and [[Invoke]] are the correct primitives and and that the worry about "conditional invoke" was a false concern.

A method invocation such as: obj.foo(arg) is currently specified as being roughly equivalent to: obj.[[Invoke]]("foo", [arg], obj); and the ordinary implementation if [[Invoke]] decomposes into: let func = obj.[Get]; let result = func.[Call]

However, a proxy may do other things in its 'invoke' handler including replacing the this value and/or arguments passed to the [[Call]]. There are strong use cases for the variability of such translations which were the recent motivation for re-introducing [[Invoke]]. It don't think we need to go around the loop of reconsidering those use cases again. They are valid and we will end up at the same place.

The problem is that we see within the ES specification a few instances of a [[Get]]/[[Call]] sequence that look more typically like: let func = obj.[Get]; let result; if (typeof(func) == "function") result = func.[Call] else /* compute result some other way */

Our concern started with" "oh no, the [[Get]]/[[Call]] is inside of [[Invoke]] how can we get between them. This led to various proposals for new MOP operations that split up [[Invoke]] or allowed a conditional test to be injected into [[Invoke]]. I think this is the wrong way to look at the problem. We were being mislead by legacy [[Get]]/[[Call]] pattern and not looking at the actual conceptual intent of these code sequences. In pure ECMAScrpt and dealing at the level of object abstractions, this is what such use cases are really trying to expression:

If (typeof(obj.foo) == "function") result = obj.foo(arg); else //something else ...;

or, in prose: If the 'foo' property of obj is a method, invoke that method on obj with arg as the argument.

or in pseudo-ES-pseudo code: let func = obj.[Get]; let result; if (typeof(func) == "function") result = obj.[Invoke] else /* compute result some other way */

In other words, the appropriate conversion of the pattern we observed in the ES spec. isn't [[Get]]+test+conditional call to [[Call]]. It is [[Get]]+test+conditional call to [[Invoke]].

From this thread, there are two concerns I anticipate. The first is that a double property lookup of "foo" is being performed. Conceptually I don't have a problem with that as the double property access accurately represents the object level concept that is being expressed. However, there might also be a perforce concern about the double lookup, particularly for its usage in ToPrimitive. I believe this is a non-problem as implementations can easily avoid performing the double lookup it is an actual performance issue. A typical specified usage of this pattern can be implemented something like:

// If (typeof(obj.valueOf) == "function") return obj.valueOf(); if (obj is not an ordinary object) goto slowpath; //in practice some sort of guard like this is likely to be used in front of every MOP operation //A more specified test would be if object does not use both the ordinary [[Get]] and [[Invoke]] implementations let func = InlinedOrdinaryGet(obj,"valueOf"); //and if "valueOf" resolves to a getter, invoke it twice,yuck. Perhaps poison optimization if any ordinary obj valueOf accessors defined if (func is an ordinary function object) return inlinedOrdinaryCall(obj); else if (func has a [[Call]] internal method) return func.[Call]; //exotic function needs full MOP [[Call]] dispatch else goto nextcase; slowpath: //func is not an ordinary object so need to dispatch MOP calls on it //only slow path does double lookup let func = obj.[Get]; if (func has a [[Call]] internal method) return obj.[[Invoke]]("valueOf",[ ], obj); //full MOP [[Invoke]] dispatch nextcase: ...

In other words, the cases in the specification for ordinary objects can be implemented roughly like current implementations. No double lookup need be performed and any extra guards are exactly the guards that are needed to support the existence of proxies or other exotic objects that over-ride [[Get]], [[Invoke]], or [[Call]].

I'm not particularly concerned about double lookup performance for similar use cases code in JS code. For ordinary objects, I expect normal PIC mechanism to eliminate most of the double lookup overhead and any hot code paths.

However, a concern that was mentioned is that JS programmers routinely express the conditional method call pattern as: If (typeof(func=obj.foo) == "function") result = func.call(obj, arg); rather than If (typeof(obj.foo) == "function") result = obj.foo(arg);

I appreciate this concern. However, without having or using [[Invoke]] this pattern may run into the same sort of bugs that motivated us to recently add [[Invoke]]. We need to educate JS programmers that with ES6 and the availability of Proxies and other features that obj.foo() is not exactly the same thing as obj.foo.call(foo) and that the latter formulation should generally be avoided except in expert situations. the spread operator makes it particularly easy to use () instead of apply The new "call" function is () and except for very special circumstances where a different this values is being supplied obj.method() is how methods should be invoked.

I really don't think we need to debate this much longer. We just need to stay the course with [[Invoke]] and I can update the spec. to replace [[Get]]+[[Invoke]] rather than [[Get]]+[[Call]] for this conditional situations. I may also added add a note suggesting that the extra [[Get]] can be eliminated.

# Tom Van Cutsem (8 years ago)

2013/9/18 Till Schneidereit <tschneidereit at gmail.com>

The fact remains that, without the invoke trap, proxy users have to get the information whether a method call is about to happen from other sources, even though the runtime would be perfectly able to provide it. Putting the call-if-defined wrinkle caused (or exposed) by other spec algorithms aside for a minute, I'd argue that not providing this information makes using proxies strictly more complex, and strictly less performant.

This is a good point.

Taking that wrinkle into consideration again, I still think it might be handled by wrapping the "get function, call if get succeeded" steps used in some spec algorithms into an abstract operation. That operation could have a special case along the lines of "if the receiver is a proxy with an invoke trap, call that with the args list". That would mean that having an invoke trap causes all of these maybe-calls to be actual calls. Which seems ok to me.

I've been thinking about this and I don't think it's a good solution.

Take, for instance:

var target = {}; var proxy = new Proxy(target, { } ); JSON.stringify(proxy); // conditionally invokes toJSON

Due to the current [[Get]]+[[Call]] behavior, the stringify call will check whether target.toJSON is callable, which it isn't (it's undefined), so stringify falls back to the default implementation (as it should).

Now, if a conditional invoke would always trigger a proxy's invoke() trap, and the proxy would forward these calls, this will lead to a TypeError (because the proxy tries to call target.toJSON()).

This hardly seems desirable.

# Tom Van Cutsem (8 years ago)

2013/9/18 David Bruant <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.

# Tom Van Cutsem (8 years ago)

2013/9/19 Allen Wirfs-Brock <allen at wirfs-brock.com>

I really don't think we need to debate this much longer. We just need to stay the course with [[Invoke]] and I can update the spec. to replace [[Get]]+[[Invoke]] rather than [[Get]]+[[Call]] for this conditional situations. I may also added add a note suggesting that the extra [[Get]] can be eliminated.

Refactoring [[Get]]+[[Call]] to [[Get]]+[[Invoke]] seems fine by me. It better expresses the intent, and the change should only be observable to proxies.

Re. the fact that |proxy.method.call(proxy)| would not re-bind |this|: I've come to think that if |this|-rebinding is crucial to your proxy's use case, you probably need to go "all the way" and just use membranes. Membranes contain all the necessary logic to rebind |this| as well as any other parameters.

That said, I believe we could do strictly without invoke(), but given that method invocation is so primary to JS, I believe we make the right choice by exposing it in the MOP.

By comparison, we also added a has() trap to trap the in-operator, while we could have also just triggered a series of more fundamental traps to figure out the result, and the in-operator is far less common than method invocation in a typical JS program.

, Tom

# Brendan Eich (8 years ago)

Tom Van Cutsem <mailto:tomvc.be at gmail.com> September 19, 2013 9:53 AM 2013/9/19 Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>>

I really don't think we need to debate this much longer.  We just
need to stay the course with [[Invoke]] and I can update the spec.
to replace [[Get]]+[[Invoke]] rather than [[Get]]+[[Call]] for
this conditional situations. I may also added add a note
suggesting that the extra [[Get]] can be eliminated.

Refactoring [[Get]]+[[Call]] to [[Get]]+[[Invoke]] seems fine by me. It better expresses the intent, and the change should only be observable to proxies.

Is this so? Wouldn't an ordinary object with a getter be able to observe the two lookups? Indeed wouldn't the spec require this?

# Tom Van Cutsem (8 years ago)

2013/9/19 Brendan Eich <brendan at mozilla.com>

Refactoring [[Get]]+[[Call]] to [[Get]]+[[Invoke]] seems fine by me. It

better expresses the intent, and the change should only be observable to proxies.

Is this so? Wouldn't an ordinary object with a getter be able to observe the two lookups? Indeed wouldn't the spec require this?

You're right. The change would lead to observably different behavior for conditionally invoked getters.

Not sure how much of a backwards compat issue that would be. Allen previously wrote upstream in this thread:

There are currently 6 such places (not counting the Proxy trap invocators):

3 in ToPrimitive (ie toSrting/valueOf) 1 in instanceof to access @@hasInstance (legacy conpat. for missing @@hasInstance) 1 in [ ].toString to conditionally invoke 'join' method 1 in JSON.stringify conditionally invoke 'toJSON'

How likely is it that these are getters with side-effects?

# Allen Wirfs-Brock (8 years ago)

On Sep 19, 2013, at 8:05 AM, Brendan Eich wrote:

Tom Van Cutsem <mailto:tomvc.be at gmail.com> September 19, 2013 9:53 AM 2013/9/19 Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>>

I really don't think we need to debate this much longer. We just need to stay the course with [[Invoke]] and I can update the spec. to replace [[Get]]+[[Invoke]] rather than [[Get]]+[[Call]] for this conditional situations. I may also added add a note suggesting that the extra [[Get]] can be eliminated.

Refactoring [[Get]]+[[Call]] to [[Get]]+[[Invoke]] seems fine by me. It better expresses the intent, and the change should only be observable to proxies.

Is this so? Wouldn't an ordinary object with a getter be able to observe the two lookups? Indeed wouldn't the spec require this?

Yes, the two lookups are observable via a getter. I mentioned that in a comment when I should how the ToPrimitive 'valueOf' invocation might be optimized. However, my argument was that [[Get]]+[[Invoke]] better reflects the abstract intent and that intent implicitly includes two lookup. I think this difference (from ES<=5.1) is unlikely to be significance for the few specific existing cases where this occurs in the spec.

# Allen Wirfs-Brock (8 years ago)

On Sep 19, 2013, at 8:25 AM, Tom Van Cutsem wrote:

2013/9/19 Brendan Eich <brendan at mozilla.com> Refactoring [[Get]]+[[Call]] to [[Get]]+[[Invoke]] seems fine by me. It better expresses the intent, and the change should only be observable to proxies.

Is this so? Wouldn't an ordinary object with a getter be able to observe the two lookups? Indeed wouldn't the spec require this?

You're right. The change would lead to observably different behavior for conditionally invoked getters.

Not sure how much of a backwards compat issue that would be. Allen previously wrote upstream in this thread:

There are currently 6 such places (not counting the Proxy trap invocators): 3 in ToPrimitive (ie toSrting/valueOf) 1 in instanceof to access @@hasInstance (legacy conpat. for missing @@hasInstance) 1 in [ ].toString to conditionally invoke 'join' method 1 in JSON.stringify conditionally invoke 'toJSON'

How likely is it that these are getters with side-effects?

Seems very unlikely. The @@hasInstance access is new so it isn't a backwards compat issue. BTW, these are static counts. For example, a typical call to ToPrimitive will only make a single such conditional invoke.

Arguably allowing a String or Number wrapper to be transparently proxied and hence work like a unproxied wrapper WRT ToPrimitive is one the reasons we need to do this.

# Mark S. Miller (8 years ago)

Not sure I understand this. Would the same [[Get]] + [[Invoke]] behavior be caused by the following JS pattern:

if (base[index]) { return base[index](...args); }

as opposed to the [[Get]] + [[Call]] behavior caused by the pattern

if ((f = base[index])) { f.call(base, ...args); }

(aside from the difference between .call and .[[Call]]) ?

Is it a problem that in the first pattern, the two invocations of base[index] might return different things?

# Brendan Eich (8 years ago)

What if we made invoke take either an identifier to call, or a got function to call? Then get+invoke would pass the "got" function.

# André Bargull (8 years ago)

Back to option 2: Redesign [[Invoke]] from esdiscuss/2013-August/032718 ?

  • André
# Tom Van Cutsem (8 years ago)

2013/9/19 Mark S. Miller <erights at google.com>

Not sure I understand this. Would the same [[Get]] + [[Invoke]] behavior be caused by the following JS pattern:

if (base[index]) { return base[index](...args); }

as opposed to the [[Get]] + [[Call]] behavior caused by the pattern

if ((f = base[index])) { f.call(base, ...args); }

(aside from the difference between .call and .[[Call]]) ?

Is it a problem that in the first pattern, the two invocations of base[index] might return different things?

Not necessarily a problem, but yes, that is the observable difference (or the two accesses might return the same function, but meanwhile mutate some other state).

# David Bruant (8 years ago)

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.

# Tom Van Cutsem (8 years ago)

2013/9/20 David Bruant <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.

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.

# Brendan Eich (8 years ago)

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:

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)?

# Tom Van Cutsem (8 years ago)

2013/9/20 Brendan Eich <brendan at mozilla.com>

Tom Van Cutsem <mailto:tomvc.be at gmail.com>

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.

Yes, it's a trade-off: if we get rid of all "derived" traps (only retain the "fundamental" ones), this makes it impossible to have inconsistencies between traps (this is Allen's main concern), but it also means proxies have less direct control over intercepted operations. Also, the guiding principle that MarkM and I used was to introduce derived traps whenever this would allow a proxy to intercept an operation with less allocations.

To make matters more concrete, consider the has() trap:

"foo" in proxy // triggers has(target, "foo"), returns a boolean

Strictly speaking, we could do without the has() trap and instead call the getOwnPropertyDescriptor() trap, and then test whether it returns a descriptor or undefined (+ climb the prototype if it returns undefined). But this interface potentially requires consing a property descriptor only for it to be tested against undefined. So the has() operation: a) allows more direct interception of the in-operator and b) with potentially less allocations.

If you think about it, these very same arguments apply to the invoke() trap.

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)?

Parameterizing invoke() by (id | func) to me is almost the same as adding a boolean "conditional" flag: most handlers will need to branch based on the type of the argument, because you cannot do:

invoke: function(target, idOrFunction, args, receiver) { return target[idOrFunction].apply(receiver, args); }

Users would have to:

invoke: function(target, idOrFunction, args, receiver) { if (typeof idOrFunction === "string") { return target[idOrFunction].apply(receiver, args); } else { return idOrFunction.apply(receiver, args); } }

Given that ...

a) membrane proxies work fine with the get+call pattern b) a non-membrane proxy can be made to work with the get+call pattern, if it implements sufficient logic in its "get" trap c) conditional method calls in the wild (i.e. outside the spec) will continue to use the get+call pattern

... I'm inclined to advocate the status-quo: just do get+call for conditional method calls, keep invoke() for ordinary method calls.

# Jason Orendorff (8 years ago)

On Fri, Sep 20, 2013 at 8:50 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

To make matters more concrete, consider the has() trap:

"foo" in proxy // triggers has(target, "foo"), returns a boolean

Strictly speaking, we could do without the has() trap and instead call the getOwnPropertyDescriptor() trap, and then test whether it returns a descriptor or undefined (+ climb the prototype if it returns undefined). But this interface potentially requires consing a property descriptor only for it to be tested against undefined. So the has() operation: a) allows more direct interception of the in-operator and b) with potentially less allocations.

OK, taking all that at face value, what's the justification for .hasOwn()?

a) allows more direct interception of Object.prototype.hasOwnProperty() b) potentially less allocations each time someone calls .hasOwnProperty().

I think .hasOwn() should be removed.

If you think about it, these very same arguments apply to the invoke() trap.

It's on the same spectrum, I agree.

# Tom Van Cutsem (8 years ago)

2013/9/20 Jason Orendorff <jason.orendorff at gmail.com>

OK, taking all that at face value, what's the justification for .hasOwn()?

a) allows more direct interception of Object.prototype.hasOwnProperty() b) potentially less allocations each time someone calls .hasOwnProperty().

I think .hasOwn() should be removed.

I assume you're making the case to remove all derived traps then, not just hasOwn()?

The question is where to draw the line. get() and set() are also derived traps: they can be defined in terms of getOwnPropertyDescriptor/defineProperty, but here the "overhead" of always having to work with descriptors really becomes manifest. Do you think they should also be removed?

# Mark S. Miller (8 years ago)

I think the problem is not the existence of the invoke trap but the advice we've talked about for using it. And I think this lesson applies to all derived traps.

To maintain consistency, we should always advise overriding fundamental traps first, and then consistent behavior for derived traps comes along for the ride. We should only advise overriding derived traps for one of the following two purposes:

  1. To optimize the implementation of the behavior that would have followed anyway from overriding the fundamental traps. Since this should be observably equivalent to not overriding these derived traps, consistency between fundamental and derived is maintained.

  2. When one intends to create an inconsistency between fundamental and derived, purposely breaking a consistency that usually holds in the language. These cases should be exceedingly rare, and most apparent occurrences of these cases should be viewed with great suspicion, as they are probably design mistakes.

Note that these two reasons are uncomfortably coupled: #1 only preserves equivalence given that #2 is not encountered in #1's target.

I think the only conceptual difference between invoke and the other derived traps is that invoke is our only doubly-derived trap.

# Allen Wirfs-Brock (8 years ago)

On Sep 20, 2013, at 4:20 AM, Brendan Eich wrote:

...

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.

It think it is useful to consider the double lookup issue from the perspective of what a JS programmer might code.

If naive JS programmer might do a conditional method call like:

if ("name" in obj) obj.name();

This has a double lookup which would be observable if the "name" property is a getter or if obj is a proxy. However, assuming that we have the [[Invoke]] MOP operation, things works as expected if obj is a transparently forwarding Proxy.

A a programmer who was a bit more sophisticated might code:

 if (typeof obj.name == "function") obj.name();

but they still have the double lookukp and things still work ok is obj is a transparent proxy.

It is only when you get to the sophistication level of:

 if (typeof (func=obj.name) == "function") func.call(obj. is is);

that the double lookup goes away. However, as currently specified, you won't get the expected result if obj is a forwarding proxy.

                               Any thoughts on parameterizing invoke by (id | func)?

Waldamar raised this concern about the difference between [[Get]]+[[Call]] and [[Invoke]] at the June TC39 meeting.

In a private email to Tom I suggested the paramaterized [[Invoke]] solution

On Jul 30, 2013, at 10:11 AM, Allen Wirfs-Brock wrote:

I wonder if we could respecify F.p.call/apply in a manner that would make David's getMonth.call use case work (at least some of the time).

[[Invoke]](P, ArgumentsList, Receiver) is current specified such that P must be a property key. What if we modify that so that P can be either a property key or a callable and that when a callable is passed the [[Get]] steps are skipped and P itself is used as the method.

Then call/apply could be respecified in terms of [[Invoke]] and ForwardingHandler could do the appropriate handler substitution.

Of course, this is only an asymptotically better solution, as it only improves the handler of Proxy objects in the this position. It wouldn't, may itself, fix Array.isArray.

What do you think?

allen

I still like this solution. At the JS level of abstraction it makes all of the explicit JS forms above work consistently with transparently forwarding proxies.

Probably the novel aspect WRT to this thread is the idea that [[Invoke]] parameterized by a function instead of a property key should be used i the implementations of Function.prototype.call/apply.

As an alternative, we could have both [[Invoke]] and [[InvokeFunction]] but I really prefer having one [[Invoke]] MOP operation and trap rather than two.

# Jason Orendorff (8 years ago)

On Fri, Sep 20, 2013 at 1:17 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

2013/9/20 Jason Orendorff <jason.orendorff at gmail.com>

I think .hasOwn() should be removed.

I assume you're making the case to remove all derived traps then, not just hasOwn()?

No, my question about the justification for .hasOwn() is serious and stands by itself.

The question is where to draw the line.

I agree.

This kind of goes without saying. We could invent lots more of these to make various idioms faster. There are places in the spec where we check [[Has]] and if that's true we follow up with a [[Get]]. Well hey, we could make that a method, [[IfHasGet]]. But we won't, because that would be silly.

Indeed we seem to be going the opposite direction, thank goodness. Some operations that were internal methods in ES5 are plain (albeit symbol-named) methods in ES6: [[HasInstance]], [[DefaultValue]]. [[CanPut]] is gone altogether (it only appears in one place in the ES6 draft, and I think that’ll just be deleted). These are good changes.

But we regressed on [[HasOwn]] and I don't understand why.

(I see [[Invoke]] as a regression too. Till and Allen’s latest approach isn’t bad, but it still seems insufficiently motivated.)

# Allen Wirfs-Brock (8 years ago)

On Sep 20, 2013, at 1:53 PM, Jason Orendorff wrote:

... (I see [[Invoke]] as a regression too. Till and Allen’s latest approach isn’t bad, but it still seems insufficiently motivated.)

The main reason I'm still supportive of [[Invoke]] (possibly with the option of passing a function instead of a property key) is because it allows (and that I think you pointed out) a proxy to implements some or all of its methods directly in its 'invoke" trap rather than as discrete functions.

If we didn't want to allow for that possibility I think we would only need a [[InvokeFunction]] MOP operation that doesn't ((in the ordinary implementation) include an internal [[Get]] step.

BTW, I would want to use [[InvokeFunction]] for both directly obj.method() and in the internals of F.p.call/apply

# David Bruant (8 years ago)

Le 20/09/2013 13:02, Tom Van Cutsem a écrit :

2013/9/20 David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>>

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. ... I had forgotten about relationships. My bad. What's the status on relationships?

# Mark S. Miller (8 years ago)

Still expected for ES7. Of course, you can use WeakMaps for private state manually in ES6 in the meantime.

# Brandon Benvie (8 years ago)

On 9/20/2013 12:19 PM, Jason Orendorff wrote:

what's the justification for .hasOwn()?

a) allows more direct interception of Object.prototype.hasOwnProperty() b) potentially less allocations each time someone calls .hasOwnProperty().

I think .hasOwn() should be removed.

Actually, taking the precedent of removing "getPropertyDescriptor", it's "has" that would be removed. The implementation of [[Has]] can easily walk the [[GetPrototype]] chain calling "hasOwn" on each until true or [[GetPrototype]] returns null. In fact, some agreed for a short time to remove the "has" trap specifically for this reason, but it was added back.

esdiscuss/2012-November/026274

# Brandon Benvie (8 years ago)

On 9/20/2013 6:27 PM, Brandon Benvie wrote:

Actually, taking the precedent of removing "getPropertyDescriptor", it's "has" that would be removed. The implementation of [[Has]] can easily walk the [[GetPrototype]] chain calling "hasOwn" on each until true or [[GetPrototype]] returns null. In fact, some agreed for a short time to remove the "has" trap specifically for this reason, but it was added back.

Better link: esdiscuss/2012-November/026286

# Brendan Eich (8 years ago)

I'm not sure about hasOwn, Jason's focusing on it and I'll wait for Tom's reply.

But we have get and set, which are derived. I think keeping has to match them makes the most sense.

On invoke, I'm with Tom: use invoke only for user-code obj.method() and equivalent (objmethod_string) calls. For internal calls, use get+call.

# Brendan Eich (8 years ago)

Allen Wirfs-Brock wrote:

The main reason I'm still supportive of [[Invoke]] (possibly with the option of passing a function instead of a property key) is because it allows (and that I think you pointed out) a proxy to implements some or all of its methods directly in its 'invoke" trap rather than as discrete functions.

Only if the client code doesn't extract a method as a funarg. There's no way around get and invoke having to be consistent.

JS has first class functions, methods are not second class, so invoke cannot relieve a proxy from reifying methods from get.

Given this, having the legacy internal calls continue to use get+call seems fine to me. A proxy implementing toString, e.g., can make it work using these traps just as well as via get+invoke, and without double lookup or boolean-trap-smelling (id | func) parameterization of invoke.

# Allen Wirfs-Brock (8 years ago)

On Sep 20, 2013, at 4:27 PM, Brandon Benvie wrote:

On 9/20/2013 12:19 PM, Jason Orendorff wrote:

what's the justification for .hasOwn()?

a) allows more direct interception of Object.prototype.hasOwnProperty() b) potentially less allocations each time someone calls .hasOwnProperty().

I think .hasOwn() should be removed.

Actually, taking the precedent of removing "getPropertyDescriptor", it's "has" that would be removed. The implementation of [[Has]] can easily walk the [[GetPrototype]] chain calling "hasOwn" on each until true or [[GetPrototype]] returns null. In fact, some agreed for a short time to remove the "has" trap specifically for this reason, but it was added back.

esdiscuss/2012-November/026274

[[Get]] and [[Set]] enable an exotic object to define its own property lookup semantics that isn't necessary the same as a simple "follow the [[Prototype]] chain until a matching own property is found". For example, they could use some sort of multiple inheritance resolution algorithm. [[HasProperty]] is needed to allow an external client to check for the existence of a property using the same algorithm as [[Get]] and [[Set]].

Of course, it is the responsibility of the implementor of the exotic object to actually use the same lookup algorithm for [[Get]], [[Set]], [HasProperty]], and [[Invoke]].

# Allen Wirfs-Brock (8 years ago)

On Sep 20, 2013, at 5:31 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

The main reason I'm still supportive of [[Invoke]] (possibly with the option of passing a function instead of a property key) is because it allows (and that I think you pointed out) a proxy to implements some or all of its methods directly in its 'invoke" trap rather than as discrete functions.

Only if the client code doesn't extract a method as a funarg. There's no way around get and invoke having to be consistent.

JS has first class functions, methods are not second class, so invoke cannot relieve a proxy from reifying methods from get.

Given this, having the legacy internal calls continue to use get+call seems fine to me. A proxy implementing toString, e.g., can make it work using these traps just as well as via get+invoke, and without double lookup or boolean-trap-smelling (id | func) parameterization of invoke.

In that case, why not just use [[Get]]+[[InvokeFunction]] in all cases (including obj.m(()) and not have the current [[Invoke]] at all. It allows the proxy handler to correctly intercede on all method invocations including conditional cones.

Slogan: [[InvokeFunctiction]] is the new [[Call]].

(except that [[Call]] still needs to exist as an implementation mechanism and if we eliminate the current [[Invoke]] I would repurpose that name for what I'm currently calling [[InvokeFunction]])

# Tom Van Cutsem (8 years ago)

2013/9/20 Allen Wirfs-Brock <allen at wirfs-brock.com>

BTW, I would want to use [[InvokeFunction]] for both directly obj.method() and in the internals of F.p.call/apply

As I mentioned to your reply at the time, I believe the latter would break the expectations of existing code.

Code that uses F.p.call/apply to apply a function it acquired through manual lookup typically expects it's going to execute the original behavior, and nothing else:

var original_push = Array.prototype.push; ... original_push.call(unknownObject, ...args); // programmer expects this to either do the specced push() behavior or throw a TypeError (assuming original definition of 'call')

JS fundamentally decouples property lookup from method call and thus has the ability to express non-polymorphic function calls. We shouldn't virtualize [[Call]]. If a proxy wants to intercept method calls, it can return a wrapper function from its "get" trap and override "invoke". I'm pretty sure virtualizing [[Call]] will be a bridge too far.

# Tom Van Cutsem (8 years ago)

2013/9/21 Allen Wirfs-Brock <allen at wirfs-brock.com>

On Sep 20, 2013, at 4:27 PM, Brandon Benvie wrote:

On 9/20/2013 12:19 PM, Jason Orendorff wrote:

I think .hasOwn() should be removed.

Actually, taking the precedent of removing "getPropertyDescriptor", it's "has" that would be removed. The implementation of [[Has]] can easily walk the [[GetPrototype]] chain calling "hasOwn" on each until true or [[GetPrototype]] returns null. In fact, some agreed for a short time to remove the "has" trap specifically for this reason, but it was added back.

esdiscuss/2012-November/026274

[[Get]] and [[Set]] enable an exotic object to define its own property lookup semantics that isn't necessary the same as a simple "follow the [[Prototype]] chain until a matching own property is found". For example, they could use some sort of multiple inheritance resolution algorithm. [[HasProperty]] is needed to allow an external client to check for the existence of a property using the same algorithm as [[Get]] and [[Set]].

Indeed, we decided in favor of keeping has() for the reasons Allen stated: so that proxies can virtualize the proto-chain-walk consistently for property access (get/set) as well as property lookup (in-operator), and enumeration (for-in loop).

Put another way: with only the fundamental traps, all proto-chain-walking happens external to the proxy, so the proxy cannot virtualize it.

# Tom Van Cutsem (8 years ago)

2013/9/21 Allen Wirfs-Brock <allen at wirfs-brock.com>

On Sep 20, 2013, at 5:31 PM, Brendan Eich wrote:

Given this, having the legacy internal calls continue to use get+call seems fine to me. A proxy implementing toString, e.g., can make it work using these traps just as well as via get+invoke, and without double lookup or boolean-trap-smelling (id | func) parameterization of invoke.

In that case, why not just use [[Get]]+[[InvokeFunction]] in all cases (including obj.m(()) and not have the current [[Invoke]] at all. It allows the proxy handler to correctly intercede on all method invocations including conditional cones.

Yes, except:

a) proxies must then still allocate a temporary function object in the get trap. Thus, the primary reason for having a derived trap (less allocations), disappears. b) every method call on a proxy triggers at least 2 traps ("get" + "invoke"). Another selling point of invoke() was that method calls go through one trap only. c) double-lifting needs 2 traps (get + invoke) rather than just get.

At that point, we're better off dropping [[InvokeFunction]] entirely.

As Brendan mentioned, methods are extractable in JS. A new MOP operation doesn't relieve proxies from honoring that contract.

I believe we made the right trade-offs so far and still stand by the status-quo.

# Allen Wirfs-Brock (8 years ago)

On Sep 21, 2013, at 2:51 AM, Tom Van Cutsem wrote:

2013/9/20 Allen Wirfs-Brock <allen at wirfs-brock.com> BTW, I would want to use [[InvokeFunction]] for both directly obj.method() and in the internals of F.p.call/apply

As I mentioned to your reply at the time, I believe the latter would break the expectations of existing code.

There is an even stronger expectation among existing code that obj.m() is equivalent to var f=obj.m; f.call(obj) with the possibility of other computation being inserted between initializing f and calling it.

More concretely, existing code expects that obj.valueOf() is equivalent to obj.valueOf.call()

but if obj is a transparent forwarding proxy on a map instance (or most other built-ins) the first form will work as expected and the second form will throw unless something like [[InvokeFunction]] is used within F.p.call.

The re-specification of call/apply in terms of [[InvokeFunction]] would be semantically identical to the current specification in all cases where the passed this value is not a Proxy. (or some other exotic object that redefined [[InvokeFuntion]])

Using [[InvokeFunction]] within F.p.call/apply would still retain their existing behavior for all normal situations where the this value is not a Proxy, so there would be no observable difference for existing code that is not designed to deal with proxies. Plus that existing code would continue to observe the obj.m() :: obj.m.call(m) equivalnce even when obj is a Proxy.

Code that uses F.p.call/apply to apply a function it acquired through manual lookup typically expects it's going to execute the original behavior, and nothing else:

var original_push = Array.prototype.push; ... original_push.call(unknownObject, ...args); // programmer expects this to either do the specced push() behavior or throw a TypeError (assuming original definition of 'call')

Using this specific example, you are saying the programmer expects the above to throw if unknownObject is a transparently forwarding proxy over an Array instance. I doubt if that is the actual expectation. I think the expectation is that the orginal_push will be invoked as if was the function retrieved via unknownObject.push. In other words, it is not trying to change the normal this binding semantics of obj.push(), it is only trying to force a local bind of obj.push to a specific value (ie, circumvent obj.[Get]).

I'm skeptical that there are many (any?) existing situations where F.o.call/apply is used with the failure expectation WRT transparent proxies that is implicit in your example. Do you know of any?

For situations that really want to do a naked [[Call]] without any proxy-based intercession the way to do it would be Reflect.call(original_push,unknownObject,...args) rather than original_push.call.(unknownObject,...args).

JS fundamentally decouples property lookup from method call and thus has the ability to express non-polymorphic function calls. We shouldn't virtualize [[Call]].

Are you suggesting that we should not have a Proxy "apply" trap?

But, more to your point, the existence of the this argument to call/apply means that they are inherently polymorphic calls in the sense you are talking about. If the author of a function doesn't want it to be polymorphic on its this value then they should not write any references to |this|.

If a proxy wants to intercept method calls, it can return a wrapper function from its "get" trap and override "invoke". I'm pretty sure virtualizing [[Call]] will be a bridge too far.

If this is the solution, then ForwardingHandler should do exactly that within its default "get" trap. However, that is going to also be wrong some of the time. You can't generically say that all [[Get]]'s that return a function value can replace that value with some other function and not expect to break something.

# Allen Wirfs-Brock (8 years ago)

On Sep 21, 2013, at 2:53 AM, Tom Van Cutsem wrote:

2013/9/21 Allen Wirfs-Brock <allen at wirfs-brock.com>

On Sep 20, 2013, at 5:31 PM, Brendan Eich wrote:

Given this, having the legacy internal calls continue to use get+call seems fine to me. A proxy implementing toString, e.g., can make it work using these traps just as well as via get+invoke, and without double lookup or boolean-trap-smelling (id | func) parameterization of invoke.

In that case, why not just use [[Get]]+[[InvokeFunction]] in all cases (including obj.m(()) and not have the current [[Invoke]] at all. It allows the proxy handler to correctly intercede on all method invocations including conditional cones.

Yes, except:

a) proxies must then still allocate a temporary function object in the get trap. Thus, the primary reason for having a derived trap (less allocations), disappears.

??? Not in the normal cases where function already exist for the method properties. They would only have to be allocated for the more exceptional situation where the handler is trying to directly implemented method behavior within the handler (via a switch statement, etc)

or was there something else you had in mind with the above assertion?

b) every method call on a proxy triggers at least 2 traps ("get" + "invoke"). Another selling point of invoke() was that method calls go through one trap only.

Yes, we do have the added overhead, of two traps but with the benefit is that we get a more consistent semantics that is a better match to current ES expectations.

If we really are worried about the overhead of two traps we could have a derived "invoke" trap that does "get"+"invokeFunction" but I'm not sure the complexity is worth it (or that there would actually be any saving, isn't the the default implementation of "invoke" just going to essentially trigger the other two traps?)

c) double-lifting needs 2 traps (get + invoke) rather than just get.

I don't think so, using [[invokeFunction]] instead of [[Invoke]] eliminates the need for two:

the current dispatch mechanism for Proxy Mop operations is essentially let trap = handler.[Get]; ... trap.[Call];

if [[InvokeFunction]] was available this would become

let trap = handler.[[Get]]("handlerName");
...
handler.[[InvokeFunction]](trap, handler,args);

The default behavior of [[InvokeFunction]] on a Proxy that does not have a corresponding "invokeFunction" trap defined is equivalent to a [[Call]] of the passed function so the behavior would be exactly the same. All the metameta handler needs to define is "get".

At that point, we're better off dropping [[InvokeFunction]] entirely.

As Brendan mentioned, methods are extractable in JS. A new MOP operation doesn't relieve proxies from honoring that contract.

It still appears to me that [[Get]]+[[InvokeFunction]] and respec'ing F.p.call/apply in terms of [[InvokeFunction]] is the closest semantic match to this expected behavior plus has the least anomalies WRT transparent proxying of built-ins with private state (and user defined classes that we WeakMaps for private state).

I believe we made the right trade-offs so far and still stand by the status-quo.

Which status-quo? We have some fundamental semantic consistency issues involving Proxies and method invocation.

We have a number of alternatives that have been discussed and they all seem to have both pros and cons associated with them. I also believe that done of them perfectly resolves all of the semantic consistency issues and at the same time preserves perfect actual or conceptual backwards compatibility. Personally, I'm finding it difficult to remember all of the various pros and cons that we have individually discussed for each alternative. I suspect we need to put together a side-by-side for each alternative so we can compare them.

# Jason Orendorff (8 years ago)

On Fri, Sep 20, 2013 at 6:27 PM, Brandon Benvie <bbenvie at mozilla.com> wrote:

Actually, taking the precedent of removing "getPropertyDescriptor", it's "has" that would be removed.

I think the logic of the current design is: primitives we keep; high-level operations that correspond to everyday syntax built into the language from of old (get/set/has/enumerate), we keep. But .hasOwn, like .getPropertyDescriptor, is neither.

# Kevin Smith (8 years ago)

I've been only loosely following this thread, but I think Allen's proposal has merit. If I understand correctly, it is extremely simple: provide proxies with a hook into [[Call]], based on the this value.

# Tom Van Cutsem (8 years ago)

2013/9/21 Allen Wirfs-Brock <allen at wirfs-brock.com>

On Sep 21, 2013, at 2:51 AM, Tom Van Cutsem wrote:

2013/9/20 Allen Wirfs-Brock <allen at wirfs-brock.com>

BTW, I would want to use [[InvokeFunction]] for both directly obj.method() and in the internals of F.p.call/apply

As I mentioned to your reply at the time, I believe the latter would break the expectations of existing code.

There is an even stronger expectation among existing code that obj.m() is equivalent to var f=obj.m; f.call(obj) with the possibility of other computation being inserted between initializing f and calling it.

More concretely, existing code expects that obj.valueOf() is equivalent to obj.valueOf.call()

but if obj is a transparent forwarding proxy on a map instance (or most other built-ins) the first form will work as expected and the second form will throw unless something like [[InvokeFunction]] is used within F.p.call.

Or if obj is a transparent forwarding proxy done right, i.e. whose "get" trap returns a wrapper function that re-binds |this| and then forwards (a membrane does this automatically).

The re-specification of call/apply in terms of [[InvokeFunction]] would be semantically identical to the current specification in all cases where the passed this value is not a Proxy. (or some other exotic object that redefined [[InvokeFuntion]])

Using [[InvokeFunction]] within F.p.call/apply would still retain their existing behavior for all normal situations where the this value is not a Proxy, so there would be no observable difference for existing code that is not designed to deal with proxies. Plus that existing code would continue to observe the obj.m() :: obj.m.call(m) equivalnce even when obj is a Proxy.

Code that uses F.p.call/apply to apply a function it acquired through manual lookup typically expects it's going to execute the original behavior, and nothing else:

var original_push = Array.prototype.push; ... original_push.call(unknownObject, ...args); // programmer expects this to either do the specced push() behavior or throw a TypeError (assuming original definition of 'call')

Using this specific example, you are saying the programmer expects the above to throw if unknownObject is a transparently forwarding proxy over an Array instance. I doubt if that is the actual expectation. I think the expectation is that the orginal_push will be invoked as if was the function retrieved via unknownObject.push. In other words, it is not trying to change the normal this binding semantics of obj.push(), it is only trying to force a local bind of obj.push to a specific value (ie, circumvent obj.[Get]).

Given that Array.isArray(proxyForArray) is specced to return false, I don't see why the above code should work transparently on proxies-for-arrays, while a simple Array.isArray test would fail.

I'm skeptical that there are many (any?) existing situations where F.o.call/apply is used with the failure expectation WRT transparent proxies that is implicit in your example. Do you know of any?

For situations that really want to do a naked [[Call]] without any proxy-based intercession the way to do it would be Reflect.call(original_push,unknownObject,...args) rather than original_push.call.(unknownObject,...args).

Indeed, except that I expressed concerns about the expectations of existing ES5 code, for which it is too late to opt-in to the new Reflect.apply primitive.

JS fundamentally decouples property lookup from method call and thus has the ability to express non-polymorphic function calls. We shouldn't virtualize [[Call]].

Are you suggesting that we should not have a Proxy "apply" trap?

No, not at all. For proxies that represent/wrap functions, it's necessary to virtualize [[Call]].

My argument is that we should leave the [[Call]] behavior of ordinary functions alone.

Re-specifying Function.prototype.{apply,call} in terms of a new [[InvokeFunction]] MOP would mean that proxies can intercept the call behavior of ordinary functions.

But, more to your point, the existence of the this argument to call/apply means that they are inherently polymorphic calls in the sense you are talking about. If the author of a function doesn't want it to be polymorphic on its this value then they should not write any references to |this|.

If a proxy wants to intercept method calls, it can return a wrapper function from its "get" trap and override "invoke". I'm pretty sure virtualizing [[Call]] will be a bridge too far.

If this is the solution, then ForwardingHandler should do exactly that within its default "get" trap. However, that is going to also be wrong some of the time. You can't generically say that all [[Get]]'s that return a function value can replace that value with some other function and not expect to break something.

Absolutely. Proxies enable many different possible wrappers. There is no "one true way" of building wrappers, so obviously the default behavior will be wrong on some occasions.

I'm willing to consider changing the behavior of ForwardingHandler.get to auto-wrap functions and re-bind their this-value. I think you may be right that for users subclassing ForwardingHandler, they probably want the this-rebinding to happen for them by default.

# Tom Van Cutsem (8 years ago)

2013/9/21 Allen Wirfs-Brock <allen at wirfs-brock.com>

On Sep 21, 2013, at 2:53 AM, Tom Van Cutsem wrote:

2013/9/21 Allen Wirfs-Brock <allen at wirfs-brock.com>

On Sep 20, 2013, at 5:31 PM, Brendan Eich wrote:

Given this, having the legacy internal calls continue to use get+call seems fine to me. A proxy implementing toString, e.g., can make it work using these traps just as well as via get+invoke, and without double lookup or boolean-trap-smelling (id | func) parameterization of invoke.

In that case, why not just use [[Get]]+[[InvokeFunction]] in all cases (including obj.m(()) and not have the current [[Invoke]] at all. It allows the proxy handler to correctly intercede on all method invocations including conditional cones.

Yes, except:

a) proxies must then still allocate a temporary function object in the get trap. Thus, the primary reason for having a derived trap (less allocations), disappears.

??? Not in the normal cases where function already exist for the method properties. They would only have to be allocated for the more exceptional situation where the handler is trying to directly implemented method behavior within the handler (via a switch statement, etc)

or was there something else you had in mind with the above assertion?

I was thinking primarily about virtual object use-cases, where the method properties don't necessarily already exist as functions.

But also membranes, for instance. A membrane must make sure no direct reference to the method leaks to the caller, and thus must return a wrapper from the "get" trap regardless of whether we include "invokeFunction".

b) every method call on a proxy triggers at least 2 traps ("get" + "invoke"). Another selling point of invoke() was that method calls go through one trap only.

Yes, we do have the added overhead, of two traps but with the benefit is that we get a more consistent semantics that is a better match to current ES expectations.

If we really are worried about the overhead of two traps we could have a derived "invoke" trap that does "get"+"invokeFunction" but I'm not sure the complexity is worth it (or that there would actually be any saving, isn't the the default implementation of "invoke" just going to essentially trigger the other two traps?)

c) double-lifting needs 2 traps (get + invoke) rather than just get.

I don't think so, using [[invokeFunction]] instead of [[Invoke]] eliminates the need for two:

the current dispatch mechanism for Proxy Mop operations is essentially let trap = handler.[Get]; ... trap.[Call];

if [[InvokeFunction]] was available this would become

let trap = handler.[[Get]]("handlerName");
...
handler.[[InvokeFunction]](trap, handler,args);

The default behavior of [[InvokeFunction]] on a Proxy that does not have a corresponding "invokeFunction" trap defined is equivalent to a [[Call]] of the passed function so the behavior would be exactly the same. All the metameta handler needs to define is "get".

Ok, I stand corrected. But that default behavior surprised me. The default behavior of all other traps is to forward the intercepted operation to the target. The way you describe things, this would not be true of "invokeFunction".

At that point, we're better off dropping [[InvokeFunction]] entirely.

As Brendan mentioned, methods are extractable in JS. A new MOP operation doesn't relieve proxies from honoring that contract.

It still appears to me that [[Get]]+[[InvokeFunction]] and respec'ing F.p.call/apply in terms of [[InvokeFunction]] is the closest semantic match to this expected behavior plus has the least anomalies WRT transparent proxying of built-ins with private state (and user defined classes that we WeakMaps for private state).

Except that respec'ing F.p.call/apply changes the [[Call]] behavior of existing non-proxy functions. Am I the only one to think this is potentially a serious hazard?

I believe we made the right trade-offs so far and still stand by the status-quo.

Which status-quo? We have some fundamental semantic consistency issues involving Proxies and method invocation.

We have a number of alternatives that have been discussed and they all seem to have both pros and cons associated with them. I also believe that done of them perfectly resolves all of the semantic consistency issues and at the same time preserves perfect actual or conceptual backwards compatibility. Personally, I'm finding it difficult to remember all of the various pros and cons that we have individually discussed for each alternative. I suspect we need to put together a side-by-side for each alternative so we can compare them.

Indeed, there are trade-offs and there is no silver bullet.

The status-quo, which I advocated, entails:

  • we keep the invoke() trap, with its current signature: invoke(target, propertyName, argsArray, receiver)
  • conditional method calls in the spec are still expressed as [[Get]] + [[Call]], following the common JS idiom

After your comments, I would add:

  • we change ForwardingHandler.get to automatically re-bind |this| by wrapping function-valued data properties of its target

The only drawback of the status-quo, that I see, is that proxies that do not subclass ForwardingHandler must exercise more care in their "get" trap if they want to intercept the this-binding. Most other alternatives make it easier for proxy authors to avoid this issue, but at the expense of making the JS MOP strictly more complex/less intuitive.

# Tom Van Cutsem (8 years ago)

2013/9/22 Jason Orendorff <jason.orendorff at gmail.com>

On Fri, Sep 20, 2013 at 6:27 PM, Brandon Benvie <bbenvie at mozilla.com> wrote:

Actually, taking the precedent of removing "getPropertyDescriptor", it's "has" that would be removed.

I think the logic of the current design is: primitives we keep; high-level operations that correspond to everyday syntax built into the language from of old (get/set/has/enumerate), we keep. But .hasOwn, like .getPropertyDescriptor, is neither.

To me hasOwn() is as much a primitive as e.g. Object.keys().

The only odd thing about it is that it lives on Object.prototype rather than as a static method on Object.

I don't see the inconsistency, unless you would also want to remove Object.keys() because it can be expressed in terms of gOPN + gOPD.

# Till Schneidereit (8 years ago)

On Mon, Sep 23, 2013 at 10:33 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

Indeed, there are trade-offs and there is no silver bullet.

The status-quo, which I advocated, entails:

  • we keep the invoke() trap, with its current signature: invoke(target, propertyName, argsArray, receiver)
  • conditional method calls in the spec are still expressed as [[Get]] + [[Call]], following the common JS idiom

After your comments, I would add:

  • we change ForwardingHandler.get to automatically re-bind |this| by wrapping function-valued data properties of its target

The only drawback of the status-quo, that I see, is that proxies that do not subclass ForwardingHandler must exercise more care in their "get" trap if they want to intercept the this-binding. Most other alternatives make it easier for proxy authors to avoid this issue, but at the expense of making the JS MOP strictly more complex/less intuitive.

FWIW, the arguments in favor of keeping the status quo as described by Tom have convinced me.

The issue triggering this entire thread is that some spec algorithms don't use [[Invoke]], so the .invoke trap isn't triggered for them. I'm now convinced that seeing that as an issue is ascribing too much power to the .invoke trap. Let's state its behavior this way: "the .invoke trap is triggered by direct method calls on the proxy object in the form of proxy.method() or proxy["method"](). It is not triggered by invocations of a function that was extracted from a proxy object." It makes a lot of sense that there might be spec algorithms (just as other code that might use the proxy object) that call functions after extracting them from their receiver. Which goes to show that properly implementing proxies isn't easy, not that the .invoke trap is somehow broken.

# Allen Wirfs-Brock (8 years ago)

On Sep 23, 2013, at 1:29 AM, Tom Van Cutsem wrote:

2013/9/21 Allen Wirfs-Brock <allen at wirfs-brock.com>

...

More concretely, existing code expects that obj.valueOf() is equivalent to obj.valueOf.call()

but if obj is a transparent forwarding proxy on a map instance (or most other built-ins) the first form will work as expected and the second form will throw unless something like [[InvokeFunction]] is used within F.p.call.

Or if obj is a transparent forwarding proxy done right, i.e. whose "get" trap returns a wrapper function that re-binds |this| and then forwards (a membrane does this automatically).

I think it is unreasonable to expect anybody to get this right. An arbitrary data property might be either a method (and hence should be wrapped) or just a data store containing a function value that should not be wrapped. You might use a white-list within the "get" trap to identify the actual methods that need wrappers. But what about properties that are dynamically added to the object. Should they be treated as data or methods? What about inherited properties? You also need to include them in the white-list, but what happenswhen the proto links are mutated?

... Using this specific example, you are saying the programmer expects the above to throw if unknownObject is a transparently forwarding proxy over an Array instance. I doubt if that is the actual expectation. I think the expectation is that the orginal_push will be invoked as if was the function retrieved via unknownObject.push. In other words, it is not trying to change the normal this binding semantics of obj.push(), it is only trying to force a local bind of obj.push to a specific value (ie, circumvent obj.[Get]).

Given that Array.isArray(proxyForArray) is specced to return false, I don't see why the above code should work transparently on proxies-for-arrays, while a simple Array.isArray test would fail.

The problem with Array.isArray is that it doesn't do a polymorphic dispatch on the value it is test. It's current definition was adequate for its original purpose when we didn't have proxies or formalized subclassing. It's problematic now.

The way to fix Array.isArray is to add a @@isArray own property to all exotic array objects when they are created and for Array.isArray to be redefined as: Array.isArray = (array) => array[@@isArray] === true;

(Array.isArray and @@create for exotic array objects are both built-ins so we don't need to directly expose @@isArray to ES code. Hence no forgery issues.

I'm skeptical that there are many (any?) existing situations where F.o.call/apply is used with the failure expectation WRT transparent proxies that is implicit in your example. Do you know of any?

For situations that really want to do a naked [[Call]] without any proxy-based intercession the way to do it would be Reflect.call(original_push,unknownObject,...args) rather than original_push.call.(unknownObject,...args).

Indeed, except that I expressed concerns about the expectations of existing ES5 code, for which it is too late to opt-in to the new Reflect.apply primitive.

Yes, but I'm arguing that it there is little such code. Can you identify any? What I'm trying to do is make both existing and new code would as expected in the presence of transparent forwarding proxies (particularly those that target built-ins)

JS fundamentally decouples property lookup from method call and thus has the ability to express non-polymorphic function calls. We shouldn't virtualize [[Call]].

Are you suggesting that we should not have a Proxy "apply" trap?

No, not at all. For proxies that represent/wrap functions, it's necessary to virtualize [[Call]].

My argument is that we should leave the [[Call]] behavior of ordinary functions alone.

Yes, and my proposal does that. It is proposing a change to F.p.call/apply not to [[Call]] on ordinary function objects. [[Invoke]] introduces a (sometimes) observable difference between obj.m() and obj.call("m"). That's the problem we need to fix.

Re-specifying Function.prototype.{apply,call} in terms of a new [[InvokeFunction]] MOP would mean that proxies can intercept the call behavior of ordinary functions.

Only when the this value explicitly passed to apply/call is a proxy. This is exactly the behavior we need to preserved consistency between obj.m() and obj.call("m").

...

Absolutely. Proxies enable many different possible wrappers. There is no "one true way" of building wrappers, so obviously the default behavior will be wrong on some occasions.

I'm willing to consider changing the behavior of ForwardingHandler.get to auto-wrap functions and re-bind their this-value. I think you may be right that for users subclassing ForwardingHandler, they probably want the this-rebinding to happen for them by default.

I think you should try to define it since you are arguing that such warppering is the solution. However, as I described above, I don't think you can define a sufficiently generic wrappering semantics.

# Allen Wirfs-Brock (8 years ago)

On Sep 23, 2013, at 1:33 AM, Tom Van Cutsem wrote:

2013/9/21 Allen Wirfs-Brock <allen at wirfs-brock.com> ...

c) double-lifting needs 2 traps (get + invoke) rather than just get.

I don't think so, using [[invokeFunction]] instead of [[Invoke]] eliminates the need for two:

the current dispatch mechanism for Proxy Mop operations is essentially let trap = handler.[Get]; ... trap.[Call];

if [[InvokeFunction]] was available this would become

let trap = handler.[[Get]]("handlerName");
...
handler.[[InvokeFunction]](trap, handler,args);

The default behavior of [[InvokeFunction]] on a Proxy that does not have a corresponding "invokeFunction" trap defined is equivalent to a [[Call]] of the passed function so the behavior would be exactly the same. All the metameta handler needs to define is "get".

Ok, I stand corrected. But that default behavior surprised me. The default behavior of all other traps is to forward the intercepted operation to the target. The way you describe things, this would not be true of "invokeFunction".

Except the above isn't showing the control flow for the default path through a Proxy mop operation it is showing the path when a trap is defined. As it has done is replaced the [[Call]] to the trap (which passes the handler as the this value) with an [[InvokeFunction]] on the handler. Since the Proxy object that is the handler (for example, gist.github.com/tvcutsem/6536442 ) does not define a "invokeFunctiuon" trap it takes its default which is to forward [[InvokeFunction]] to its target (dummy in the example).. That is an ordinary object so it will get default [[InvokeFunction]] behavior which is just to do a [[Call]] using the explicitly passed this value and arguments.

(BTW, this is somewhat tricky to work out, but I believe it all hangs together. But we should keep looking at this double-lifting scenario to make sure I haven't missed something).

At that point, we're better off dropping [[InvokeFunction]] entirely.

As Brendan mentioned, methods are extractable in JS. A new MOP operation doesn't relieve proxies from honoring that contract.

It still appears to me that [[Get]]+[[InvokeFunction]] and respec'ing F.p.call/apply in terms of [[InvokeFunction]] is the closest semantic match to this expected behavior plus has the least anomalies WRT transparent proxying of built-ins with private state (and user defined classes that we WeakMaps for private state).

Except that respec'ing F.p.call/apply changes the [[Call]] behavior of existing non-proxy functions. Am I the only one to think this is potentially a serious hazard?

Only if the this argument is a proxy and presumably no existing use of call/apply was written with the expectation that the this value it was passing might be a proxy. It think it is much more likely that they expect the obj.m() :: obj.m.call(m) equivalent than anything else. ...

Indeed, there are trade-offs and there is no silver bullet.

The status-quo, which I advocated, entails:

  • we keep the invoke() trap, with its current signature: invoke(target, propertyName, argsArray, receiver)
  • conditional method calls in the spec are still expressed as [[Get]] + [[Call]], following the common JS idiom

After your comments, I would add:

  • we change ForwardingHandler.get to automatically re-bind |this| by wrapping function-valued data properties of its target

The only drawback of the status-quo, that I see, is that proxies that do not subclass ForwardingHandler must exercise more care in their "get" trap if they want to intercept the this-binding. Most other alternatives make it easier for proxy authors to avoid this issue, but at the expense of making the JS MOP strictly more complex/less intuitive.

You already have my challenge to try to define such a handler. I'm actually more concerned about inconsistent behavior for both internal (eg, valueOf) and user coded conditional calls.

# Jason Orendorff (8 years ago)

On Mon, Sep 23, 2013 at 3:40 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

To me hasOwn() is as much a primitive as e.g. Object.keys().

The only odd thing about it is that it lives on Object.prototype rather than as a static method on Object.

I don't see the inconsistency, unless you would also want to remove Object.keys() because it can be expressed in terms of gOPN + gOPD.

But Tom, there already is no trap for Object.keys()! It's specified in terms of [[OwnPropertyKeys]] and [[GetOwnProperty]].

# Brendan Eich (8 years ago)

Allen Wirfs-Brock wrote:

I'm actually more concerned about inconsistent behavior for both internal (eg, valueOf) and user coded conditional calls.

Why? Lots of traps have to be implemented consistently, why is any invoke used for explicit method calls, but get+call for implicit conversions and user-scripted funarg extraction followed by later call, any different?

# Brendan Eich (8 years ago)

Till Schneidereit wrote:

The issue triggering this entire thread is that some spec algorithms don't use [[Invoke]], so the .invoke trap isn't triggered for them. I'm now convinced that seeing that as an issue is ascribing too much power to the .invoke trap. Let's state its behavior this way: "the .invoke trap is triggered by direct method calls on the proxy object in the form of proxy.method() or proxy["method"](). It is not triggered by invocations of a function that was extracted from a proxy object." It makes a lot of sense that there might be spec algorithms (just as other code that might use the proxy object) that call functions after extracting them from their receiver. Which goes to show that properly implementing proxies isn't easy, not that the .invoke trap is somehow broken.

+1, well put.

# Allen Wirfs-Brock (8 years ago)

On Sep 23, 2013, at 12:53 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

I'm actually more concerned about inconsistent behavior for both internal (eg, valueOf) and user coded conditional calls.

Why? Lots of traps have to be implemented consistently, why is any invoke used for explicit method calls, but get+call for implicit conversions and user-scripted funarg extraction followed by later call, any different?

It's a matter of internal vs external consistency. The implementor of a Proxy handler needs to internally consistently implement the 'get', 'set', 'has', 'invoke', etc. traps in order to manifest an object that exhibits the normally expected object semantics. But there is no fool proof (I contend) way for the proxy implementor to achieve 'invoke' consistency with 'get+F.p.call

JS programmer have been trained that:

obj.m(args)

is equivalent to:

let f = obj.m; f.call(obj, args)

Sometimes they place additional code between the property access and the call. That code might make the call conditional. It might memorize f and obj in order to defer the call to an arbitrary point in the future.

Prior to proxies this was always valid because the semantics of both, at the MOP level were: f = obj.[Get]; f.[Call]. But with proxies and [[Invoke]], this equivalent is no longer the case. And with the current definition of F.p.call/apply there is nothing that the client code can do to reestablish the consistency.

This can be fixed, if obj.m(args) has the semantics of [[Get]]+[[InvokeFunction]] and F.p.call/apply also uses [[InvokeFunction]] (noting that is the vast majority of cases [[InvokeFunction]] is exactly the same as [[Call]]. The JS programmers expected equivalence is maintained yet transparent proxies still have an opportunity to do forward of this values.

BTW, it also restore left to right evaluation of the call operator which regressed when the call operator was implemented using [[Invoke]].

# Brendan Eich (8 years ago)

Allen Wirfs-Brock <mailto:allen at wirfs-brock.com> September 23, 2013 3:06 PM

It's a matter of internal vs external consistency. The implementor of a Proxy handler needs to internally consistently implement the 'get', 'set', 'has', 'invoke', etc. traps in order to manifest an object that exhibits the normally expected object semantics. But there is no fool proof (I contend) way for the proxy implementor to achieve 'invoke' consistency with 'get+F.p.call

Now it depends on what you mean by "fool proof".

JS programmer have been trained that:

obj.m(args)

is equivalent to:

let f = obj.m; f.call(obj, args)

Sometimes they place additional code between the property access and the call. That code might make the call conditional. It might memorize f and obj in order to defer the call to an arbitrary point in the future.

Prior to proxies this was always valid because the semantics of both, at the MOP level were: f = obj.[Get]; f.[Call]. But with proxies and [[Invoke]], this equivalent is no longer the case.

Vacuously true, and why we resisted invoke.

And with the current definition of F.p.call/apply there is nothing that the client code can do to reestablish the consistency.

To use either F.p.call or .apply, you must do obj.[Get]. For function objects, [[Call]] is well-specified and what F.p.{c,a} use.

For proxies, we need something akin to [[Call]]. Is that the point of [[InvokeFunction]]?

This can be fixed, if obj.m(args) has the semantics of [[Get]]+[[InvokeFunction]] and F.p.call/apply also uses [[InvokeFunction]] (noting that is the vast majority of cases [[InvokeFunction]] is exactly the same as [[Call]].

Ok, but then Tom asked whether function objects targeted by direct proxies having their [[Call]] overridden was a problem?

The JS programmers expected equivalence is maintained yet transparent proxies still have an opportunity to do forward of this values.

BTW, it also restore left to right evaluation of the call operator which regressed when the call operator was implemented using [[Invoke]].

If you can rename [[InvokeFunction]] to [[Invoke]] so my head stops exploding, I think we have a deal. But need Tom on board.

# Allen Wirfs-Brock (8 years ago)

On Sep 23, 2013, at 3:50 PM, Brendan Eich wrote:

Allen Wirfs-Brock <mailto:allen at wirfs-brock.com> September 23, 2013 3:06 PM

It's a matter of internal vs external consistency. The implementor of a Proxy handler needs to internally consistently implement the 'get', 'set', 'has', 'invoke', etc. traps in order to manifest an object that exhibits the normally expected object semantics. But there is no fool proof (I contend) way for the proxy implementor to achieve 'invoke' consistency with 'get+F.p.call

Now it depends on what you mean by "fool proof".

Takes into account dynamically added properties (particularly inherited), [[Prototype]] mutation, subclassing,etc.

JS programmer have been trained that:

obj.m(args)

is equivalent to:

let f = obj.m; f.call(obj, args)

Sometimes they place additional code between the property access and the call. That code might make the call conditional. It might memorize f and obj in order to defer the call to an arbitrary point in the future.

Prior to proxies this was always valid because the semantics of both, at the MOP level were: f = obj.[Get]; f.[Call]. But with proxies and [[Invoke]], this equivalent is no longer the case.

Vacuously true, and why we resisted invoke.

And with the current definition of F.p.call/apply there is nothing that the client code can do to reestablish the consistency.

To use either F.p.call or .apply, you must do obj.[Get]. For function objects, [[Call]] is well-specified and what F.p.{c,a} use.

For proxies, we need something akin to [[Call]]. Is that the point of [[InvokeFunction]]?

Exactly. [[InvokeFunction]] is the Proxy this aware version of [[Call]].

Rather than obj.m(args) turning into obj.[Invoke] where [[Invoke]] internally does [[Get]]+[[Call]] I'm suggesting that turns into: f = obj.[Get] obj.[InvokeFunction]

(I've left out the extra Receiver parameters that [[Get]], [[Invoke]], and [[InvokeFunction]] all can accept.)

[[Invoke]] would go away and the call operator would simply be implemented as [[Get]]+[[InvokeFunction]].

In addition, F.p.call/apply would also use [[InvokeFunction]] instead of [[Call]] whenever the passed this value is a Proxy.

This can be fixed, if obj.m(args) has the semantics of [[Get]]+[[InvokeFunction]] and F.p.call/apply also uses [[InvokeFunction]] (noting that is the vast majority of cases [[InvokeFunction]] is exactly the same as [[Call]].

Ok, but then Tom asked whether function objects targeted by direct proxies having their [[Call]] overridden was a problem?

Ultimately [[InvokeFunction]] performs a [[Call]] on some function object. [[InvokeFunction]] doesn't replace [[Call]] as the primitive function invocation hook.

The JS programmers expected equivalence is maintained yet transparent proxies still have an opportunity to do forward of this values.

BTW, it also restore left to right evaluation of the call operator which regressed when the call operator was implemented using [[Invoke]].

If you can rename [[InvokeFunction]] to [[Invoke]] so my head stops exploding, I think we have a deal. But need Tom on board.

Yes, I want to do that renaming. But for now we are still talking about the differences between [[Invoke]] and [[InvokeFunction]] so I can't do the renaming yet. But a key point is that only one of them ends up in the spec. and whichever it is will be named [[Invoke]].

# Kevin Smith (8 years ago)

Your line of thinking has convinced me that invoke as it currently stands doesn't really fly. However, I have an issue with your proposal. Take this fragment:

(1) function f() { doSomethingWith(this); }
(2) f.call(obj);

Presently, the expression at (2) grants the function f access to obj. If I understand correctly, under your proposal the expression at (2), in the case where obj is a proxy, additionally grants obj access to f. Is that right?

# Allen Wirfs-Brock (8 years ago)

On Sep 23, 2013, at 6:14 PM, Kevin Smith wrote:

Hi Allen,

Your line of thinking has convinced me that invoke as it currently stands doesn't really fly. However, I have an issue with your proposal. Take this fragment:

(1) function f() { doSomethingWith(this); }
(2) f.call(obj);

Presently, the expression at (2) grants the function f access to obj. If I understand correctly, under your proposal the expression at (2), in the case where obj is a proxy, additionally grants obj access to f. Is that right?

In the case where obj is a Proxy f.call(obj) would give f's handler access to f.

# Allen Wirfs-Brock (8 years ago)

On Sep 23, 2013, at 6:32 PM, Allen Wirfs-Brock wrote:

On Sep 23, 2013, at 6:14 PM, Kevin Smith wrote:

Hi Allen,

Your line of thinking has convinced me that invoke as it currently stands doesn't really fly. However, I have an issue with your proposal. Take this fragment:

(1) function f() { doSomethingWith(this); } (2) f.call(obj);

Presently, the expression at (2) grants the function f access to obj. If I understand correctly, under your proposal the expression at (2), in the case where obj is a proxy, additionally grants obj access to f. Is that right?

In the case where obj is a Proxy f.call(obj) would give f's handler access to f.

If you wanted to make the call in a way that did not reveal the function to the handler you would code step 2 as: Reflect.apply(f, obj, [ ]);

That directly calls [[Call]] on f without going through the proxy

# Mark S. Miller (8 years ago)

What does "f's handler" refer to? If obj is a proxy and f is not, then obj has a proxy and f does not.

# Allen Wirfs-Brock (8 years ago)

Sorry, I meant obj's handler

# Mark S. Miller (8 years ago)

Ok, I've only been skimming the thread but I obviously missed something crucial. How would

f.call(obj)

cause obj's handler to be invoked at all, much less be given access to f? And why?

# Tom Van Cutsem (8 years ago)

2013/9/23 Allen Wirfs-Brock <allen at wirfs-brock.com>

On Sep 23, 2013, at 1:29 AM, Tom Van Cutsem wrote:

I'm willing to consider changing the behavior of ForwardingHandler.get to auto-wrap functions and re-bind their this-value. I think you may be right that for users subclassing ForwardingHandler, they probably want the this-rebinding to happen for them by default.

I think you should try to define it since you are arguing that such warppering is the solution. However, as I described above, I don't think you can define a sufficiently generic wrappering semantics.

You are right. I accepted your challenge and started thinking about how to wrap the method in the "get" trap.

The most trivial solution is to just wrap every target function in a bound function, like so:

get: function(target, name, receiver) { var prop = target[name]; if (typeof prop === "function") { return prop.bind(target); } return prop; }

However, functions are objects so may have properties. The bound function would not expose the properties of its wrapped function. Gratuitous wrapping also breaks identity if code depends on the identity of a function. And the direct proxy would even throw a TypeError if the target's property is non-configurable non-writable.

To solve all of that, you'd need proper membranes. But using membranes to fix the this-rebinding problem seems like total overkill.

So forget ForwardingHandler.get doing auto-wrapping. The generic solution is membranes. Anything else needs application-specific consideration.

# André Bargull (8 years ago)

Short summary:

The current (rev18) [[Invoke]] design allows this code to work (use case A): js``` var p = new Proxy(new Map, {}); // Map.prototype.get is not generic, requires proper Map instance p.get(key);


But it does not allow to use (use case B):
js```
var p = new Proxy(new Map, {});
Map.prototype.get.call(p, key);

To make sure use case B works as well, [[InvokeFunction]] and changing Function.prototype.{call, apply} has been proposed. Proxies will also receive a new trap for [[InvokeFunction]]. Function.prototype.call will need to be changed as follows:

Change last step (step 4) from: 4. Return result of func.[[Call]] (thisArg, argList).

To: 4. If Type(thisArg) is Object then 4.a Return result of thisArg.[[InvokeFunction]] (func, argList). 5. Else 5.a Return result of func.[[Call]] (thisArg, argList).

(Note: The condition in step 4 may explicitly test for Proxies and only dispatch [[InvokeFunction]] if that's the case -- but this is not too important for now.)

The following objections have been raised so far: (1) Calling Function.prototype.call on a function object no longer ensures the function object will be called. (2) Calling Function.prototype.call on a function object let's the function object escape to the proxy object.

Example for (1): js``` function thrower() { throw new Error } var p = new Proxy({}, {invokeFunction(f, args) { /* do nothing */ }}); // no exception will be thrown here thrower.call(p);


Example for (2):
See Kevin's example below.

In response to these objections, it was said that `Reflect.call()` will 
need to be used to ensure the original [[Call]] behaviour takes place.


- André
# Tom Van Cutsem (8 years ago)

2013/9/24 Mark S. Miller <erights at google.com>

Ok, I've only been skimming the thread but I obviously missed something crucial. How would

f.call(obj)

cause obj's handler to be invoked at all, much less be given access to f? And why?

This is exactly the hazard I alluded to in earlier mails. Thanks to Kevin for the more tangible example, and to André for the accurate summary.

It seems we are faced with the following dilemma:

  1. There is code in the wild of the form:

var f = obj.method; // later: f.call(obj);

If obj is a transparent forwarding proxy, the expectation is for this call to work. Here is a call that is intended to be polymorphic, but because of funarg extraction, loses the intended polymorphism. [[InvokeFunction]] would fix this.

  1. There is code in the wild of the form:

var f = /* some closely held function, or an original built-in known only to work on objects of a certain type */ // later: f.call(obj);

The expectation of this code is that the f function is called (and only its code runs), regardless of whether obj is a proxy or not. In particular, if obj is a proxy, we do not want to transfer control to the proxy, and we most certainly don't want to expose f to the proxy. This code really means to make a non-polymorphic function call.

Both pieces of code are using Function.prototype.call, with opposite expectations. Obviously we cannot fix both. The status-quo breaks pattern .#1 but keeps pattern #2 intact.

Reflect.apply is indeed "the new [[Call]]" but only addresses the issue after the fact. Can we break pattern #2?

I believe Caja uses pattern #2 but MarkM or someone else from the Caja team should confirm.

# Mark S. Miller (8 years ago)

Thanks to André and Tom, I think I now understand the problem. I think the only solution lies in this comment from Tom:

On Tue, Sep 24, 2013 at 1:23 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

[...] The generic solution is membranes. Anything else needs application-specific consideration.

But right above it he says

To solve all of that, you'd need proper membranes. But using membranes to

fix the this-rebinding problem seems like total overkill.

A solution that works is better than one that doesn't. We always knew that patterns of proxies short of membranes would have abstraction leakage. This is one. What is wrong with the stance: Live with it or use membranes,

In answer to Tom's later question, Caja code does use the pattern

f.call(thing);

a lot. Or even

// At initialization time, before the primordials may be corrupted. See // <conventions:safe_meta_programming

var bind = Function.prototype.bind; var uncurryThis = bind.bind(bind.call); var fUncurried = uncurryThis(Thing.prototype.f);

// At runtime, after the primordials may be corrupted, but reliably // still in the lexical scope of our fUncurried binding var reliableFResult = fUncurried(thing, ...args);

which presents all the same issues more indirectly. It does this usually for purposes of integrity on the meaning and results of the call -- that it is reliably according to the original f function's behavior, and no longer according to thing. When we want thing to be in control of the meaning of the operation, we do thing.f(...args). Usually, the entire reason for this pattern is to take this control away from thing.

Whatever problem InvokeFunction is trying to solve, solve it with membranes, or, as Tom says, in an application-specific manner rather than a generic mechanism.

# Kevin Smith (8 years ago)

A solution that works is better than one that doesn't. We always knew that patterns of proxies short of membranes would have abstraction leakage. This is one. What is wrong with the stance: Live with it or use membranes,

I think I agree with this stance. Does that imply that we should drop the invoke trap, then?

# Mark S. Miller (8 years ago)

I would have no objections to dropping it. For me, the Invoke trap is merely another derived trap whose main use is to avoid allocations needed when relying only on more fundamental traps. See < esdiscuss/2013-September/033501>.

These extra allocations are cheaper than the confusion caused by trying to use the Invoke traps for other purposes.

# Till Schneidereit (8 years ago)

On Tue, Sep 24, 2013 at 4:49 PM, Mark S. Miller <erights at google.com> wrote:

I would have no objections to dropping it. For me, the Invoke trap is merely another derived trap whose main use is to avoid allocations needed when relying only on more fundamental traps. See < esdiscuss/2013-September/033501>. These extra allocations are cheaper than the confusion caused by trying to use the Invoke traps for other purposes.

On Tue, Sep 24, 2013 at 7:43 AM, Kevin Smith <zenparsing at gmail.com> wrote:

A solution that works is better than one that doesn't. We always knew that patterns of proxies short of membranes would have abstraction leakage. This is one. What is wrong with the stance: Live with it or use membranes,

I think I agree with this stance. Does that imply that we should drop the invoke trap, then?

I think this is a false dilemma. The problems with an .invokeFunction trap don't have much to do with the .invoke trap. In fact, I'd argue that the two solve similar, but distinct issues: .invokeFunction gives proxies the ability to intervene whenever a function is called with the proxy as the receiver. .invoke gives it the ability to return a result instead of a function being called with the proxy as a receiver.

Fundamental issues with trapping [[InvokeFunction]] (which I think have been clearly demonstrated) don't really affect trapping [[Invoke]]. Neither does .invoke solve the issues .invokeFunction is meant to solve, but it doesn't need to in order to be useful.

# Kevin Smith (8 years ago)

The way I see it, invoke (as currently specified) introduces new, slightly incompatible function-call semantics, and doesn't really solve the problem of transparent forwarding to targets with private state. Better off without.

# Allen Wirfs-Brock (8 years ago)

On Sep 24, 2013, at 7:37 AM, Mark S. Miller wrote:

Thanks to André and Tom, I think I now understand the problem. I think the only solution lies in this comment from Tom:

On Tue, Sep 24, 2013 at 1:23 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote: [...] The generic solution is membranes. Anything else needs application-specific consideration.

But right above it he says

To solve all of that, you'd need proper membranes. But using membranes to fix the this-rebinding problem seems like total overkill.

A solution that works is better than one that doesn't. We always knew that patterns of proxies short of membranes would have abstraction leakage. This is one. What is wrong with the stance: Live with it or use membranes,

In answer to Tom's later question, Caja code does use the pattern

f.call(thing);

a lot. Or even

Mark,

In the wild, how often do you think F.p.call is used with these sorts of integrity guarantees in mind? Does anybody other than Caja do it? If we are really only talking about Caja, it presumably could adapt to using Reflect.apply.

# Mark S. Miller (8 years ago)

I do not know and I do not know what to search on to find out. However, I would guess it is used enough to be a real concern. We documented it at < conventions:safe_meta_programming>

for reasons that are not Caja specific. That page followed from Axel's < www.2ality.com/2011/11/uncurrying-this.html> which is also not Caja

specific. Both have been public advice posted in places prominent to the JS community for a long time. There was also an earlier discussion on es-discuss about exactly this integrity issue, that was again not Caja specific.

But anyway, another point of my email is that I don't understand what problem you're trying to solve. The reason for using this pattern, rather than the much simpler thing.f(...args) is to take control away from thing. If you give control back to thing anyway, what's the point? If you want thing to have control, why not write thing.f(...args) ?

# Mark Miller (8 years ago)

One bit of data: A google search on "uncurryThis" says it gives 21,500 results. We should expect that many uses of this and similar patterns do not use it by this name, so this should be taken, in some sense[1], as a lower bound indication of the breadth of interest in such patterns.

[1] Insert appropriate qualifiers about the noise in such "results" numbers.

# Allen Wirfs-Brock (8 years ago)

On Sep 24, 2013, at 10:18 AM, Mark Miller wrote:

One bit of data: A google search on "uncurryThis" says it gives 21,500 results. We should expect that many uses of this and similar patterns do not use it by this name, so this should be taken, in some sense[1], as a lower bound indication of the breadth of interest in such patterns.

[1] Insert appropriate qualifiers about the noise in such "results" numbers.

Interestingly, I only get 1580 google hits for "uncurryThis". Leave off the quotes gets me to 21,500.

# Allen Wirfs-Brock (8 years ago)

On Sep 24, 2013, at 10:06 AM, Mark S. Miller wrote:

I do not know and I do not know what to search on to find out. However, I would guess it is used enough to be a real concern. We documented it at conventions:safe_meta_programming for reasons that are not Caja specific. That page followed from Axel's www.2ality.com/2011/11/uncurrying-this.html which is also not Caja specific. Both have been public advice posted in places prominent to the JS community for a long time. There was also an earlier discussion on es-discuss about exactly this integrity issue, that was again not Caja specific.

But anyway, another point of my email is that I don't understand what problem you're trying to solve. The reason for using this pattern, rather than the much simpler thing.f(...args) is to take control away from thing. If you give control back to thing anyway, what's the point? If you want thing to have control, why not write thing.f(...args) ?

The main issue is the use case exemplified by:

let temp = thing.f; //do other stuff using temp temp.call(thing,...args)

I have previously suggested that the fix for this if [[Invoke]] is used to implement the call operator is:

let temp = thing.f; //do other stuff temp thing.f(...args)

In addition, I proposed that where this use case appears in the ES spec ([[Get]]+stuff+[[Call]]) it should be replaced with [[Get]]+stuff+[[Invoke]].

The object to this is that it observably does two property lookups of "f" while [[Get]]+[[Call]] only does one. Personally, I think this is probably a insignificant change from the ES5 semantics, but trying to avoid it is one of the forces pushing towards using [[InvokeFunction]].

A separate concern is that even though in ES6 thing.f(...ags) is probably better than temp.call(thing,...args) it isn't the pattern existing JS programmers use.

# Brendan Eich (8 years ago)

Based on all the great input (thanks to André for his summary, and Mark for pointing out "works always" beats "half-works/half-broken"), and talking to Allen 1:1, I'm back to status quo ante: we are better off being conservative designers by deferring invoke.

If we need to trial it as a strawman extension in SpiderMonkey, we can do that, and we'll report back.

# Brandon Benvie (8 years ago)

On 9/24/2013 11:38 AM, Brendan Eich wrote:

Based on all the great input (thanks to André for his summary, and Mark for pointing out "works always" beats "half-works/half-broken"), and talking to Allen 1:1, I'm back to status quo ante: we are better off being conservative designers by deferring invoke.

If we need to trial it as a strawman extension in SpiderMonkey, we can do that, and we'll report back.

It seems unfortunate that we have to rely on a membrane for something as simply as new Proxy(new Date, {}).getDate() to work. [[Invoke]] as currently specced gets us somewhere at least.

# Brendan Eich (8 years ago)

Brandon Benvie <mailto:bbenvie at mozilla.com> September 24, 2013 11:44 AM

It seems unfortunate that we have to rely on a membrane for something as simply as new Proxy(new Date, {}).getDate() to work. [[Invoke]] as currently specced gets us somewhere at least.

Somewhere half-right and half-wrong, depending on target object details over which a single trap cannot hope to abstract, is still half-wrong. Membranes based on built-in libraries should be easy enough to use.

That does mean we have to get the built-in libraries right. Something best done on github (see promises), but then potentially absorbed by the spec in short order (whichever spec is in the right rapid-er release stage to absorb the library).

Tom, are you happy with the state of the proxy-supporting libraries for ES6?

# Till Schneidereit (8 years ago)

On Tue, Sep 24, 2013 at 8:38 PM, Brendan Eich <brendan at mozilla.com> wrote:

Based on all the great input (thanks to André for his summary, and Mark for pointing out "works always" beats "half-works/half-broken"), and talking to Allen 1:1, I'm back to status quo ante: we are better off being conservative designers by deferring invoke.

If we need to trial it as a strawman extension in SpiderMonkey, we can do that, and we'll report back.

FWIW, our implementation isn't too far off. I have some feedback to integrate still, and probably some rebasing by now, but getting it into the tree shouldn't be too hard. I don't however, know who would use it and give us feedback. Other than Shumway, that is, where the use case is very clear and won't give us much new information.

# Brendan Eich (8 years ago)

You will get some migration from noSuchMethod, I predict, among hobbyists and fans willing to write SpiderMonkey-specific code (or who wrote it in the past, possibly a while ago, and who want to keep it going).

# Till Schneidereit (8 years ago)

Well, yes, but only if we commit to keeping it around. Which we'd probably not want to do if .invoke is removed from the spec for good.

# Allen Wirfs-Brock (8 years ago)

On Sep 24, 2013, at 11:50 AM, Brendan Eich wrote:

Brandon Benvie <mailto:bbenvie at mozilla.com> September 24, 2013 11:44 AM

It seems unfortunate that we have to rely on a membrane for something as simply as new Proxy(new Date, {}).getDate() to work. [[Invoke]] as currently specced gets us somewhere at least.

Somewhere half-right and half-wrong, depending on target object details over which a single trap cannot hope to abstract, is still half-wrong. Membranes based on built-in libraries should be easy enough to use.

I think this is a key point. Things like 'new Proxy(new Date, {}).getDate()' just don't work as expected with direct proxies and we have not been able to fix that while maintaining other important semantic requirements. If JS programmer have an expectation that they can usefully write such code they are going to be disappointed. Direct proxies seem to be a fine primitive for implementing membranes and some virtual objects. They aren't good for things like this Date example.

# Kevin Smith (8 years ago)

I think this is a key point. Things like 'new Proxy(new Date, {}).getDate()' just don't work as expected with direct proxies and we have not been able to fix that while maintaining other important semantic requirements. If JS programmer have an expectation that they can usefully write such code they are going to be disappointed. Direct proxies seem to be a fine primitive for implementing membranes and some virtual objects. They aren't good for things like this Date example.

I think that transparently interceding in front of an object with private state is a questionable endeavor in any case.

# Brendan Eich (8 years ago)

Till Schneidereit wrote:

Well, yes, but only if we commit to keeping it around. Which we'd probably not want to do if .invoke is removed from the spec for good.

It's hard to say "for good" -- we did ban namespaces a la ES4/AS3 from Harmony, but sometimes we do better by "deferring" rather than "removing for good". This is the case with invoke, IMHO.

# Tom Van Cutsem (8 years ago)

2013/9/24 Brandon Benvie <bbenvie at mozilla.com>

It seems unfortunate that we have to rely on a membrane for something as simply as new Proxy(new Date, {}).getDate() to work. [[Invoke]] as currently specced gets us somewhere at least.

You don't necessarily need a full membrane if you want that specific call to work. If a proxy's "get" trap just returns a bound-function (with |this| bound to target), this will work fine. I would postulate that this solution easily covers the 90% case. Yes, methods can be objects with properties in JS, and bound functions won't proxy those, but that isn't the common case.

JS programmers have been using function-wrappers since forever (including bound functions since ES5). Bound functions aren't perfect forwarding wrappers either. People seem to cope.

Perfect is the enemy of the good and all that ;-)

# Tom Van Cutsem (8 years ago)

2013/9/24 Brendan Eich <brendan at mozilla.com>

Brandon Benvie <mailto:bbenvie at mozilla.com>

September 24, 2013 11:44 AM

It seems unfortunate that we have to rely on a membrane for something as simply as new Proxy(new Date, {}).getDate() to work. [[Invoke]] as currently specced gets us somewhere at least.

Somewhere half-right and half-wrong, depending on target object details over which a single trap cannot hope to abstract, is still half-wrong. Membranes based on built-in libraries should be easy enough to use.

That does mean we have to get the built-in libraries right. Something best done on github (see promises), but then potentially absorbed by the spec in short order (whichever spec is in the right rapid-er release stage to absorb the library).

Tom, are you happy with the state of the proxy-supporting libraries for ES6?

I presume by proxy-supporting libraries, you primarily mean the Handler hierarchy at < harmony:virtual_object_api>.

I have mixed feelings. It's all technically sound w.r.t. the spec, but I have the feeling the proposed handlers are still too generic because they are not driven by concrete use cases.

This is a part of the Proxy API that we could consider postponing beyond ES6, encouraging the community to come up with their own abstractions.

Experimentation should be easy enough: this is all library code, easily self-hosted. Here's my prototype implementation on GitHub: < tvcutsem/harmony-reflect/blob/master/handlers.js>.

# Till Schneidereit (8 years ago)

On Wed, Sep 25, 2013 at 10:58 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

2013/9/24 Brendan Eich <brendan at mozilla.com>

Brandon Benvie <mailto:bbenvie at mozilla.com>

September 24, 2013 11:44 AM

It seems unfortunate that we have to rely on a membrane for something as simply as new Proxy(new Date, {}).getDate() to work. [[Invoke]] as currently specced gets us somewhere at least.

Somewhere half-right and half-wrong, depending on target object details over which a single trap cannot hope to abstract, is still half-wrong. Membranes based on built-in libraries should be easy enough to use.

That does mean we have to get the built-in libraries right. Something best done on github (see promises), but then potentially absorbed by the spec in short order (whichever spec is in the right rapid-er release stage to absorb the library).

Tom, are you happy with the state of the proxy-supporting libraries for ES6?

I presume by proxy-supporting libraries, you primarily mean the Handler hierarchy at < harmony:virtual_object_api>.

I have mixed feelings. It's all technically sound w.r.t. the spec, but I have the feeling the proposed handlers are still too generic because they are not driven by concrete use cases.

This is a part of the Proxy API that we could consider postponing beyond ES6, encouraging the community to come up with their own abstractions.

Note that .invoke is not in that part, though: once proxies ship (in more than one browser) with all method calls invoking the .get handler, that ship has sailed. Suddenly not invoking the .get handler, anymore, will break applications.

# Tom Van Cutsem (8 years ago)

2013/9/25 Till Schneidereit <till at tillschneidereit.net>

Note that .invoke is not in that part, though: once proxies ship (in more than one browser) with all method calls invoking the .get handler, that ship has sailed. Suddenly not invoking the .get handler, anymore, will break applications.

Unless we add the trap with a default logic of calling the "get" trap when no "invoke" trap is present on the handler. That would be deviating from the current default behavior, but it would be possible.

# Till Schneidereit (8 years ago)

On Wed, Sep 25, 2013 at 3:31 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

2013/9/25 Till Schneidereit <till at tillschneidereit.net>

Note that .invoke is not in that part, though: once proxies ship (in more than one browser) with all method calls invoking the .get handler, that ship has sailed. Suddenly not invoking the .get handler, anymore, will break applications.

Unless we add the trap with a default logic of calling the "get" trap when no "invoke" trap is present on the handler. That would be deviating from the current default behavior, but it would be possible.

Ok, that'd work. It seems kind of hacky to me, but I can't immediately think of any real arguments against it.

# Anne van Kesteren (8 years ago)

On Wed, Sep 25, 2013 at 8:18 AM, Till Schneidereit <till at tillschneidereit.net> wrote:

Note that .invoke is not in that part, though: once proxies ship (in more than one browser) with all method calls invoking the .get handler, that ship has sailed. Suddenly not invoking the .get handler, anymore, will break applications.

Per [[Invoke]] in people.mozilla.org/~jorendorff/es6-draft.html#sec-9.1.11 [[Get]] is used though.

# Till Schneidereit (8 years ago)

On Wed, Sep 25, 2013 at 4:08 PM, Anne van Kesteren <annevk at annevk.nl> wrote:

On Wed, Sep 25, 2013 at 8:18 AM, Till Schneidereit <till at tillschneidereit.net> wrote:

Note that .invoke is not in that part, though: once proxies ship (in more than one browser) with all method calls invoking the .get handler, that ship has sailed. Suddenly not invoking the .get handler, anymore, will break applications.

Per [[Invoke]] in people.mozilla.org/~jorendorff/es6-draft.html#sec-9.1.11 [[Get]] is used though.

That's for the abstract operation. The proxy handler for .invoke is specified in people.mozilla.org/~jorendorff/es6-draft.html#sec-9.3.11