Proxies: get+fn vs. invoke

Tom Van Cutsem tomvc.be at gmail.com
Sun Oct 17 21:30:52 PDT 2010


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. 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).

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
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20101017/8bcdcede/attachment-0001.html>


More information about the es-discuss mailing list