Well stated. Comments below.

Tom Van Cutsem <mailto:[email protected]>
September 20, 2013 12:02 PM
2013/9/20 David Bruant <[email protected] <mailto:[email protected]>>

    Further, a long-standing invariant in JS has been the equivalence
    of o.m(...args)  and m.call(o, ...args). The invoke trap allows
    allows to break this invariant. I'm not sure this is for the best.
    This promotes a given coding pattern, but at the detriment of another.


I'm well aware. I originally advocated against invoke() for precisely this reason :-)

    I'm still inclined to think a generic solution to private state
    and proxies should be found. Given this solution, the invoke trap
    may end up being plain redundant. That would be unfortunate.
    I realize private state isn't figured out for ES6, so I think this
    issue should be left pending, the invoke trap included.


My point of view is that we have already found the "generic solution" to private state: WeakMaps. WeakMaps are able to store private state, and they don't interact with proxies at all. I realize WeakMaps have syntactic and implementation-level usability issues, and I'm hopeful the relationships strawman can overcome these. But relationships semantically still build upon WeakMaps, and they were tested to work across membranes in all cases.

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

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

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

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

Coming back to invoke(), I reviewed my notes from the May meeting (where we decided to add the trap):

A good point made by Yehuda is that the "get" trap already allows a proxy to re-bind |this| for intercepted accessors. That is, in ES5, you had the "invariant" that if obj.x triggers a getter, the getter's |this| is always bound to obj. With proxies, this is no longer true. The "get" trap gives access to the thisBinding, and allows the proxy to override. invoke() simply extends the power to re-bind |this| from just accessor calls to general method calls.

Dave Herman also reminded us that on platforms with __noSuchMethod__, the invoke = get + call invariant is already weakened. In fact, as we've discussed on several occasions on this list, proxies can't faithfully emulate __noSuchMethod__ without invoke().

And finally, if all we gain by leaving out invoke() is a simpler API, then we should probably reconsider all derived traps. As I mentioned earlier, we can easily get rid of traps like has(), hasOwn(), keys() etc., and they don't seem nearly as important to intercept as method invocation.

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

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

/be

Cheers,
Tom
David Bruant <mailto:[email protected]>
September 20, 2013 10:57 AM
Le 19/09/2013 10:53, Tom Van Cutsem a écrit :
2013/9/18 David Bruant <[email protected] <mailto:[email protected]>>

    ... I just realized that in your examples, the private state is
    stored in a weakmap which requires target identity. If I recall,
    the original problem wasn't so much with weakmaps, but rather
    with Date instances which is slightly different.
    If solving weakmap lookups via |this| binding is worth solving,
    maybe we need to start considering solving weakmap lookups via
    the receiver of get and set traps, etc. As well as weakmap
    lookups via 1st, 2nd, 3rd, etc. argument.


We already have a solution to this: membranes do exactly the right wrapping/unwrapping for all arguments (including |this|, return values, and thrown exceptions).
In a membrane, the method is wrapped and, when called, can unwrap |this| as well as all arguments before making the call to the target. This makes interaction with private state seamless. Membranes don't need invoke (if anyone feel this isn't clear, I'm happy to write the code). The problem being addressed by invoke is "isolated" (non-membrane) proxies and how they interact with private state. Given this is the problem being solved, why auto-unwrapping |this| and not all arguments?

    What does make the 0th argument (this binding) so special?


The fact that many OO programmers don't consider |this| to be an argument
If nothing else, Function.prototype.call makes very clear that |this| is an argument. The "o.method()" notation is just convenient and expressive syntactic sugar for "method.call(o)".

Further, a long-standing invariant in JS has been the equivalence of o.m(...args) and m.call(o, ...args). The invoke trap allows allows to break this invariant. I'm not sure this is for the best. This promotes a given coding pattern, but at the detriment of another. A generic solution to how private state interacts with proxy wouldn't promote any coding pattern, wouldn't break invariants.

I have the impression of seeing the same sort of bias we had for prototype being special in the original Proxy design (Proxy.create(handler, prototype)). |this| doesn't need to be specialized in the proxy design.

and depend on it being of a particular "type".
A well-behaving proxy should be able to emulate a type (modulo branding because of object identity)... well... I guess it depends on what we call a "type". Is an object considered of a given type only if it's strictly been the output of a given constructor? Can't a well-behaving proxy emulate a type? What's the point of proxies if the answer is no to the previous question?

The methods on many of JS's built-ins are a good example. But this is getting philosophical. The fact remains that we should make it easy for proxies to translate |proxy.method(...args)| into |target.method(...args)| calls.
Why "should" we? The original motivation is private state. Are there other needs? The fact that none was identified before the private state issue came up suggests that there might not be.

I'm still inclined to think a generic solution to private state and proxies should be found. Given this solution, the invoke trap may end up being plain redundant. That would be unfortunate. I realize private state isn't figured out for ES6, so I think this issue should be left pending, the invoke trap included.

David
Tom Van Cutsem <mailto:[email protected]>
September 19, 2013 9:53 AM
2013/9/18 David Bruant <[email protected] <mailto:[email protected]>>

    ... I just realized that in your examples, the private state is
    stored in a weakmap which requires target identity. If I recall,
    the original problem wasn't so much with weakmaps, but rather with
    Date instances which is slightly different.
    If solving weakmap lookups via |this| binding is worth solving,
    maybe we need to start considering solving weakmap lookups via the
    receiver of get and set traps, etc. As well as weakmap lookups via
    1st, 2nd, 3rd, etc. argument.


We already have a solution to this: membranes do exactly the right wrapping/unwrapping for all arguments (including |this|, return values, and thrown exceptions).

    What does make the 0th argument (this binding) so special?


The fact that many OO programmers don't consider |this| to be an argument and depend on it being of a particular "type". The methods on many of JS's built-ins are a good example. But this is getting philosophical. The fact remains that we should make it easy for proxies to translate |proxy.method(...args)| into |target.method(...args)| calls.
David Bruant <mailto:[email protected]>
September 18, 2013 9:54 PM
Le 18/09/2013 20:52, Tom Van Cutsem a écrit :
Are there other use cases than private state to rebinding |this| to the target?

... I just realized that in your examples, the private state is stored in a weakmap which requires target identity. If I recall, the original problem wasn't so much with weakmaps, but rather with Date instances which is slightly different. If solving weakmap lookups via |this| binding is worth solving, maybe we need to start considering solving weakmap lookups via the receiver of get and set traps, etc. As well as weakmap lookups via 1st, 2nd, 3rd, etc. argument.
What does make the 0th argument (this binding) so special?

David
Tom Van Cutsem <mailto:[email protected]>
September 18, 2013 7:52 PM

    An alternative to p3 and p4 would be to find a solution for the
    interaction between private state and proxies that also works with:
        Date.getMonth.call(myProxy)
    A way that would make p1 work in essence (which very naturally you
    listed as the first idea ;-) ).
    Such a solution would also make invoke unnecessary.


My example has nothing to do with private state per se. It's more generally about forwarding proxies reliably rebinding |this| to their target.

cheers,
Tom
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to