Proxies: get+fn vs. invoke

Mark S. Miller erights at google.com
Wed Oct 20 09:16:44 PDT 2010


On Wed, Oct 20, 2010 at 7:10 AM, Dmitry A. Soshnikov <
dmitry.soshnikov at gmail.com> wrote:

>  OK, I'd like nevertheless to continue the discussion with possible
> decisions.
>
> Here is a description of how [[Get]] method can work of a trapping proxy
> object (I took the basis from the proxy semantics page --
> http://wiki.ecmascript.org/doku.php?id=harmony:proxies_semantics#semantics_of_proxies
> ):
>
> [[Get]] (P)
>
> When the [[Get]] internal method of a trapping proxy O is called with
> property name P, the following steps are taken:
>
>    1. Let handler be the value of the [[Handler]] internal property of O.
>    2. Let get be the result of calling the [[Get]] internal method of
> handler with argument “get”.
>    3. If get is undefined, return the result of performing the default
> behavior, by consulting handler’s “getPropertyDescriptor” trap.
>    4. If IsCallable(get) is false, throw a TypeError exception.
>    5. Let getResult be the result of calling the [[Call]] internal method
> of get providing handler as the this value, O as the first argument and P as
> the second argument.
>    6. if getResult is undefined and [[HasProperty]](P) is false and [[Get]]
> is activated with production CallExpression : MemberExpression Arguments
> then
>        6.a Let noSuchMethod be the result of calling the [[Get]] internal
> method of handler with argument “noSuchMethod”.
>        6.b If IsCallable(noSuchMethod) is true then
>            6.b.I Let argList be the result of evaluating Arguments,
> producing an internal list of argument values.
>            6.b.II Return the result of calling the [[Call]] internal method
> of noSuchMethod providing handler as the this value, O as the first argument
> and P as the second argument, argList as the third argument
>    7. Return getResult.
>
> Thoughts?
>
> P.S.: And I want to ask to vote everyone on this list about whether this
> additional hook for proxy handlers is needed. My voice is first -- "yes".
>
> P.S.[2] In case of "no", we should back to all broken invariants and find
> the solutions.
>

I vote "no", but I do have some sympathy for some of the goals. The only
proposal along these lines I've seen that I like[*] is to provide an
additional flag parameter to the get trap. When false, or if the get trap
ignores the flag parameter, everything operates as in the current Proxies
proposal. When a method call is performed on a proxy, (proxy.name(args) or
proxy[expr](args)), then the get trap is invoked with the flag set to true.
Given that a handler is only accessible from proxies, when a handler's get
trap is invoked with the flag set to true, the following invariants are
guaranteed:

* The value returned by the get trap will be [[Call]]ed with its this
binding will be identical to the rcvr parameter of the get trap.
* The value returned by the get trap will only be [[Call]]ed, and will not
otherwise escape.

IIRC, this proposal died on overhead it would impose on non-proxy calls on
JSC. As the JSC implementation evolves, perhaps this constraint may ease.
Let's keep our eyes open. But if not, I still vote "no".


[*] I forget from who. If someone knows, please post. Thanks.



>
> P.S.[3]: I understand that the committee discussed it before (as Brendan
> mentioned). But as we saw, _the things have changed_ and we have many holes
> in the only scheme "get+fn". So in addition to this scheme it will be good
> to JS has noSuchMethod hook for proxies. Thus, if the "get" will return a
> function, the complete scheme "get+fn" is still working (and we get right
> the step 7 after 6), i.e. we lose nothing, but gain in addition a convenient
> hook which avoids broken invariants. And since the things have changed, I
> think the decision (even it was already discussed some-when before) should
> be reconsidered too.
>
> Thanks,
> Dmitry.
>
>
>
> On 18.10.2010 12:25, Dmitry A. Soshnikov wrote:
>
> On 18.10.2010 8:30, Tom Van Cutsem wrote:
>
> I understand you are arguing for noSuchMethod in addition to the existing
> get trap, and I think we all agree that proxies could support both get +
> noSuchMethod.
>
>
> Yes. At least that already all agree is a progress. I glad to hear it,
> since I'm also interested in the JS design and want to have it convenient
> and elegant in it's abilities (how is that was?: "Languages are tools, they
> should evolve to serve their users", for what I add -- but not to bother
> their users with undetermined results in respect of broken invariants).
>
>  The point that Brendan and I seem to have converged to is that enabling
> *both* of them allows the creation of (meta-)programs with semantics that
> we'd rather avoid (the ability to create invoke-only methods).
>
>
> Tom, I specially described several times (and in the recent letter --
> described in detail) the conceptual difference between these two approaches.
> Once again, try to see on it from the _notifying_ a hook about the missing
> method _event_, but not working with some mystic "non-existing" (but which
> actually always exists) method.
>
> I repeat once again:
>
> function handleNoSuchMethodEven(base, name, args) {
>   console.log(name, args);
> }
>
> if (typeof foo.bar != "function") {
>   handleNoSuchMethodEven(foo, "bar", [1, 2, 3]);
> }
>
> if (typeof foo.baz != "function") {
>   handleNoSuchMethodEven(foo, "baz", [4, 5, 6]);
> }
>
> And for not to repeat every time this desugared code, this hook should be
> added. But notice, that we deal with just a _check for presence_. Still
> `foo.bar` and `foo.baz` as were before `undefined` properties (i.e. *really*
> non-existing "methods"), so far they stay the same -- correctly `undefined`
> after the check applied and the handler is called. Thus, all invariants are
> working.
>
> Also, let me notice, still, I'm not asking to add the hook. I propose to
> add the hook with providing sound arguments. And still, since I'm interested
> in the JS design and want to see it logical and with some convenient
> powerful tools which will "serve their users", I logically insist on adding
> this hook. Then we'll have both schemes.
>
> Besides the committee which I'll ask to vote (considering the _current
> state_ of proxies i.e. considering all the issues I found during was playing
> with proxies), I can bring a huge "army" of JS programmers (the users for
> which JS should server!) behind me to vote too.
>
> So please understand me correctly -- I here with only positive and
> constructive thoughts which forward to the improvement of JS. And since it's
> just the beginning of ES6, it's very good that we found these issues _now_
> (and can take actions), but not _after_ the spec will be publish.
>
> Once again, I am very glad that you and Brendan are are discussing it
> professionally and objectively since now we see the issues. And I hope on
> the same cooperation with accepting the decision.
>
> Dmitry.
>
>  Cheers,
> Tom
>
> 2010/10/15 Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com>
>
>>  On 14.10.2010 22:57, Tom Van Cutsem wrote:
>>
>>  ... All do work. I.e. any missing property, for you, is a method. Do
>>> whatever you want with it. Call e.g. your noSuchMethod function inside it.
>>> - Hm, but how can I test whether a some method (or a property) exists on
>>> my object?
>>>
>>> Obviously, the approach:
>>>
>>> if (!o.n) {
>>>   o.n = function () {};
>>> }
>>>
>>> or even so:
>>>
>>> if (typeof o.n != "function") {
>>>   o.n = function () {};
>>> }
>>>
>>> won't work. Why should I get always a "function" for every reading of a
>>> (non-existing) property?
>>>
>>
>>  Ok, I finally see what issue you are addressing. I will try to summarize
>> (for you to see if I get it right)
>> - o is a proxy that proxies for another object o2, but in addition, it
>> wants to treat missing methods on o2 specially (e.g. return a no-op function
>> to prevent errors or return a method of some other object)
>> - its get method would look something like:
>> get: function(r, name) {
>>   var prop = target[name];
>>   if (prop) return prop;
>>   // else deal with the missing method, probably by returning a function
>> }
>> - your feature-test using !o.n would fail because o.n returns a function,
>> so the then-branch of the if-statement will not trigger.
>>
>>
>>  Yes.
>>
>>
>>  - what you would like to do is to return 'undefined' from the 'get' trap
>> if the missing property is only accessed, and return a function only when
>> the property is invoked.
>>
>>  First: good point. AFAICT, this can't be done using the current proxy
>> API, and adding a flag to `get` or another trap would make this possible.
>>
>>  It is, however, debatable whether it is appropriate to override `o.n`
>> with the external function just because it does not exist on o2. After all,
>> the proxy can handle missing methods. Presumably, the code in the
>> else-branch is going to make use of `o.n` (either as a funarg, or it may
>> call it as a method `o.n(...)`. This will not crash, the proxy will deal
>> with it. It's not clear that overriding the `n` method with the function of
>> the then-branch is the right thing to do. Normally such feature-testing is
>> done to make sure that later calls to `o.n(...)` won't crash. When using
>> proxies that deal with missing methods, calling `o.n(...)` won't crash the
>> code, so why should the method be replaced?
>>
>>
>>
>>  That's the main thing and the issue -- you say: "When using proxies that
>> deal with missing methods". However, you miss the very major word -- "only":
>> "When using proxies that deal *only* with missing methods" and then
>> addition: "...because we can't have at the same time dealing with missing
>> properties and missing methods" (thus, "missing methods" means "missing
>> properties with a call expressions at call-sites").
>>
>> The use case is:
>>
>> 1. One lib provides some object `foo`;
>>
>> 2. In terms and principles of an abstraction I _shouldn't_ care _how_ this
>> `foo` is implemented and which internal structure it has (i.e. whether it's
>> a proxy (with possible implementation of noSuchMethod) or not -- _does not
>> matter_ for me as a user of the lib);
>>
>> 3. I invent a good patch for the lib and in particular for the object
>> `foo`. I inform about it an author of the lib (or possibly don't inform,
>> he'll new it later himself, when the patch will be de-facto standard --
>> yeah, hello, `Function.prototype.bind`). However, the author will implement
>> it _not soon_ (the simplest example -- patches for array extras are existed
>> for years in any framework, but only now authors of the engines provide
>> them, in order to conform ES5 spec).
>>
>> 4. I don't wanna wait 5 years, I write my own patch. Using the best
>> practice patterns, I provide a check for the native implementation and do
>> not create my own if the native already implemented (actually, the casual
>> and everyday situation -- in any framework):
>>
>> if (!foo.forEach) {
>>   // OK, let's go
>> }
>>
>> Result: (we don't go to `then` branch): damn, seems I underestimated the
>> author and he implemented it not after 5 years, but already now. Let's use
>> the native then... Hey, hold on! It's not `forEach` I expect, it's some
>> strange anonymous function instead. WTH? Where it comes from. Hey, wait the
>> second!:
>>
>> foo.blaBlaBla
>> foo.WTF
>> foo.heyIDontUnderstandWhatsGoingOn
>>
>> All of them are _some strange functions_? What happened here? Every
>> "non-existing" property _does_ exist! Don't know how to program then... the
>> logic is corrupted.
>>
>> So obviously, distinction of existing and non-existing property is needed.
>> And invariant "forEach" in foo == false && foo.forEach === undefined should
>> be _the invariant_ (i.e shouldn't be broken).
>>
>> (I understand, that working with proxies -- we can break any rules, as
>> e.g. implementation specific host object. I.e. we can "lie" in `in` operator
>> or in `hasOwnProperty` check, but at the same time return some stuff from
>> the `get`. However, the situation takes place and we should think how to
>> solve it. If we admit that proxies have complete right to break every logic
>> like host objects -- it may be leaved for the conscience of a proxy
>> developer).
>>
>>
>>   - Another minor thing -- `delete` does not really delete.
>>>
>>> delete foo.bar;
>>> foo.bar; // function
>>>
>>
>>  Well, it depends on how you implement the proxy. It could keep track of
>> deleted property names (I agree this would be cumbersome).
>>
>>
>>  Yeah, count, how many already additional code (including
>> caching/invalidating the cache) this implementation is required. Again, from
>> the abstraction viewpoint -- if all this will be correctly encapsulated from
>> a user -- then there is no difference how it is implemented. If the end
>> result is the same by semantics, from the semantics viewpoint all
>> implementations are equal.
>>
>>
>>  But would a separate `noSuchMethod` trap really help here? Consider:
>>
>>  delete foo.bar;
>> foo.bar(); // I expect this to crash now, but it will still call
>> `noSuchMethod`
>>
>>
>>  I specially mentioned, there the case with `delete` is not essential (the
>> words: "What are you trying to delete? Non-existing property? It doesn't
>> exist from the beginning"). However, in case of using noSuchMethod, this
>> invariant doesn't broken, since either with applying `delete` or without it
>> -- correctly `undefined` is returned at reading. In case of `get+fn` always
>> a function is return. So this minor step I think can be reduced to the first
>> issue described above -- a reading a "non-existing" property, which actually
>> _does_ always exist and is a function.
>>
>> Regarding your expectation, no, there should be no any crash, because
>> "bar" _did not exist before, and it does not exist now_.
>>
>> Actually, I see the issue of why there is a discussion of "just invoking
>> phantoms" vs. "real funargs". It's because of the _same syntax_ for _calling
>> a real function_ and _informing the hook_ that there is no such method on an
>> object. I said several times before, and repeat it now again: there is a
>> conceptual difference between these two approaches.
>>
>> 1. With handling the situation using noSuchMethod hook we deal exactly
>> with the _situation_, with the _event_ that something goes wrong. And we
>> have a special hook for that. We may don't wanna deal with a (some?)
>> function. In this case, we just _notify_ our handler about this _fact_
>> (about this _even_, a _situation_). And exactly in this case there is
>> contradiction because for notifying our handler, we use a _call expression_,
>> which also is used to call _real function_. In fact, this case is equivalent
>> to the check:
>>
>> if (typeof foo.bar != "function") {
>>   // the code of noSuchMethod passing "bar" and args
>> }
>>
>> And just for not repeating this each time, it can be encapsulated for a
>> some sugar, e.g. with using call expression for it:
>>
>> foo.bar() // which desugars to the mentioned above code
>>
>> This is the main problem. If there where used other syntax for this sugar,
>> e.g.:
>>
>> foo.bar?(1, 2, 3):defaultMethod("bar", [1, 2, 3])
>>
>> then there were no this philosophical dilemma with funargs/apply.
>>
>> 2. The approach with supporting funargs/apply assumes that we deal not
>> with _nonflying_ the handler about the _missing method even_, but with some
>> newly created function. But repeat, possibly a user didn't mean at all the
>> work with a function. I.e. this scheme assumes the same desugarred code, but
>> with previous creation of a function:
>>
>> if (typeof foo.bar != "function") {
>>   foo.bar = function () {
>>     return noSuchMethod.apply(...);
>>   };
>> }
>>
>> (Notice, this typeof check is assumed on the lower implementation level;
>> i.e. at higher level of abstraction which uses a user typeof already always
>> return `false` for the check since the method is already created by the
>> proxy, i.e. by the lower abstraction level).
>>
>> Theoretically, both approaches are acceptable. It'd be great though, to
>> have invariants with funargs/apply. But. Only if reading of non-existing
>> properties are fixed, i.e. does not return always a function for every
>> non-existing property. However, this is a _vicious circle_. On one hand --
>> we have always a function at reading non-existing property (that seems
>> broken behavior). On the other hand -- it's required to do the first case to
>> have funargs!
>>
>> That's it.
>>
>>
>>
>>
>>> - OK, and what about the prototype chain? Where should I put this proxy
>>> object in order to prevent of catching of all my missing properties (because
>>> I want to catch them from other objects in the prototype chain, to which
>>> these properties belong)?
>>>
>>> Object.prototype.foo = 10;
>>>
>>> "foo" in o // true, OK
>>> o.foo; // but it's a _function_, not 10
>>>
>>
>>  If o is a proxy that first queries another target object (like the
>> noopHandler does), it will find 'foo' and it will return 10.
>>
>>
>>
>>
>>  Yeah, this case seems OK. Just a correct `in` check is required before
>> returning a functions.
>>
>>
>>   What about to have `noSuchMethod` _additionally_ to the `get`? It will
>>> catch only missing properties, but: not _just_ missing properties, but
>>> missing properties which use a call expressions at call-sites. Thus, we can
>>> combine two approaches allowing a user to choose how to handle the case of
>>> missing _method_.
>>>
>>>
>>> handler.get = function (r, name) {
>>>    if (name == "baz") {
>>>     return function () { ... }; // and cache "baz" name if you wish
>>>   }
>>>   // other cases
>>>   return object[name];
>>> };
>>>
>>> handler.noSuchMethod = function (name, args) {
>>>   return this.delegate[name].apply(this, args);
>>> };
>>>
>>
>>  Could you specify when noSuchMethod is called? I think the algorithm
>> inside the proxy's [[Get]] method would look something like:
>>
>>  If the "get" trap on the handler returns undefined AND the handler
>> defines a "noSuchMethod" trap AND the [[Get]] was triggered by a call
>> expression, then instead of returning undefined, return the result of
>> calling the "noSuchMethod" trap.
>>
>>  Correct?
>>
>>
>>  Yes, absolutely correct. And having such a scheme, I don't see what do we
>> lose? Obviously -- nothing. But just gain. I.e. the scheme with "get+fn" is
>> _still here_ (I repeat it again and already repeated several times -- nobody
>> ask do not use it! Please, use) -- please, use it with all mentioned pros
>> and cons. However, in _addition_, a noSuchMethod hook for a _proxy handler_
>> can be provided. Who don't need it / don't like it -- they won't use it and
>> will use the scheme with "get+fn". Other -- will use it. I don't see any
>> issues here. Is it hard just to add this hook in addition? And then it will
>> be fair to check which of these two approaches will be used more often by
>> users and what do they *really* need in mostly cases.
>>
>> It's the ideal variant seems -- wanna work with partial applications
>> passing funargs? -- No problem! -- Use get+fn scheme! Wanna just simple
>> notification of a missing method event? -- Also no problem! -- We have
>> additionally noSuchMethod hook for a proxy.
>>
>> Dmitry.
>>
>>  Cheers,
>> Tom
>>
>>
>>
>
>
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>


-- 
    Cheers,
    --MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20101020/b637a5ef/attachment-0001.html>


More information about the es-discuss mailing list