On Oct 20, 2010, at 9:16 AM, Mark S. Miller wrote: > On Wed, Oct 20, 2010 at 7:10 AM, Dmitry A. Soshnikov > <dmitry.soshni...@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)
[[HasProperty]] takes O as its receiver (this) parameter, so doesn't this break double-lifting? (http://wiki.ecmascript.org/doku.php?id=harmony:proxies#a_simple_membrane) > 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. Just because the handler's get trap returns undefined does not mean a noSuchMethod trap should be tried. A get trap returning undefined is the way to satisfy a get on a proxied property whose value happens to be undefined. > > Thoughts? Breaking double-lifting is a deal breaker. > > 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". This list is not set up for voting. It's not productive to try. Let's stick with technical issues such as breaking double-lifting. > > P.S.[2] In case of "no", we should back to all broken invariants and find the > solutions. Dmitry: I'm not sure what you mean here. > 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 bet V8 peeps would object as well, but let's hear from them directly if possible. > [*] I forget from who. If someone knows, please post. Thanks. IIRC the third parameter to the get trap, telling whether get trapped from a callee context, was from Dmitry. /be > > > > 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> >>> 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 > > > > > -- > Cheers, > --MarkM > _______________________________________________ > es-discuss mailing list > es-discuss@mozilla.org > https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss