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