On Wed, Oct 20, 2010 at 7:10 AM, Dmitry A. Soshnikov < [email protected]> 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 <[email protected]> > >> 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 > [email protected] > https://mail.mozilla.org/listinfo/es-discuss > > -- Cheers, --MarkM
_______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss

