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.

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.soshni...@gmail.com <mailto:dmitry.soshni...@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@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to