Proxies: get+fn vs. invoke

Brendan Eich brendan at mozilla.com
Wed Oct 13 17:14:26 PDT 2010


On Oct 13, 2010, at 6:56 AM, Dmitry A. Soshnikov wrote:

> Also I think now, that what was named as pros, i.e. ability to have funargs and call/apply invariants, in real, not so pros. Because users more likely want to catch exactly missing methods (if you don't like the word "methods", since there're no methods, there are properties, let's say -- missing properties which ended with `call expression` at the call-site).

That's not our experience with E4X (ECMA-357), which specifies XML methods as invoke-only. They seem to be normal function-valued properties of XML.prototype, but getting one by name on an XML instance in a non-callee expression context instead tries to find an XML child element or elements of the method's name, returned as a list.

Some of this is peculiar to E4X, but the invoke-only nature of the methods, per-spec, is not. And it breaks apply and functional programming, so we extended E4X with the function:: pseudo-namespace to allow one to extract methods from XML instances.

Others using __noSuchMethod__ are happier as you say, because (for example) they are Smalltalkers (Bill Edney is on this list) who pretend there are only method calls (message sends), never properties or first-class functions.

But that happiness is not universal, so your "not so pros" judgment is not true for everyone.

Should we support everyone even if it makes the Proxy system more complicated and allows for not-quite-function methods?

Our decision was "no". You're asking us to revisit to support the some (not all) developers who want to make not-quite-function methods. That's a fair request but I think you need to do more than assert that the resulting complexity is not a problem. Further below, I'll do some legwork for you.


> And funargs/apply invariants should be leaved for _real functions_ (existing or ad-hoc, explicitly returned from the `get`).

Why shouldn't all methods including missing ones be _real functions_? Why complicate the domain of discourse with real and not-quite-real functions?


> Moreover, as it has been mentioned, such returning has broken === invariant anyway (and also broken invariant with non-existing properties).

Proxy implementors can memoize so === works. It is not a ton of code to write, and it gives the expected function-valued-property-is-method semantics. Here is the not-all-that-inconvenient proxy code:

function makeLazyMethodCloner(eager) {
    var cache = Object.create(null);
    var handler = {
        get: function (self, name) {
            if (!cache[name])
                cache[name] = Proxy.createFunction({}, function () {
                    return eager[name].apply(eager, arguments);
                });
            return cache[name];
        }
    };
    return Proxy.create(handler, Object.getPrototypeOf(eager));
}

A little test code:

var o = {m1: function () { return "m1"}, m2: function () { return "m2"; }};
var p = makeLazyMethodCloner(o);
print(p.m1());
print(p.m2());

Some subtle things here:

* The missing fundamental traps in the handlers are filled in by the system. This is a recent change to the spec, implemented in SpiderMonkey in Firefox 4 betas.

* Even p.hasOwnProperty('m1') works, because the get trap fires on 'hasOwnProperty' and clones eager['hasOwnProperty'] using a function proxy, even though that method comes from Object.prototype (eager's Object.prototype). The hasOwnProperty proxy then applies Object.prototype.hasOwnProperty to eager with id 'm1'. No get on 'm1' traps yet -- no function proxy creation just to ask hasOwnProperty.

* Both p.m1 and p.m1() work as expected. Only one kind of function.

Now consider if you had a third parameter to the 'get' trap to signal callee context vs. non-callee context. You'd still want to proxy the functions, that doesn't get simpler just due to a change of trap parameters. You'd still want to cache for identity. But you would have made invoke-only methods.

Ok, let's give up on functional programming and cached methods. Here' s my version written to use only __noSuchMethod__, no proxies:

function makeLazyMethodCloner(eager) {
    return Object.create(Object.getPrototypeOf(eager), {
        __noSuchMethod__: {
            value: function (name, args) {
                return eager[name].apply(eager, arguments);
            }
        }
    });
}

9 lines instead of 13, but broken functional programming semantics -- you cannot extract p.m1 or p.m2 and apply them later, pass them around, etc.

What good would result from this? Again, our view in TC39 is "not much".

Note that I used a mechanical, consistent coding style ("JSK&R",  { on same line as function, newline after {), so the comparison is apples to apples. Is the broken semantics really worth four lines of savings?

So, no fair asserting "practically it's unsoundly complicated and inconvenient". And please stop invoking "ideology" as a one-sided epithet to throw against Tom or TC39. Please do start showing examples, specifically an apples-to-apples comparison with __noSuchMethod__ that is significantly simpler. I don't see it.

/be
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20101013/2c703ca9/attachment-0001.html>


More information about the es-discuss mailing list