Proxies: get+fn vs. invoke

Brendan Eich brendan at mozilla.com
Thu Oct 14 12:33:54 PDT 2010


On Oct 14, 2010, at 11:57 AM, Tom Van Cutsem wrote:

> 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?

Tom, thanks for this, assuming it is an accurate reading of Dmitry's post, your reply clarifies something for me. When we bridged Java to JS in the '90s, we had to deal with Java objects having overloaded members. A member o.m could be an int field or a String -> int method, say. And methods could be overloaded to take different argument types.

We implemented something like proxies in C code, and for any overloaded field, we returned a proxy that could access the int field or call the String-returning method, depending on context. If called, we tried the method. If got or set with a number value, we accessed the field. For overloaded methods we tried disambiguated by JS actual argument type, but this could fail. There was an elaborate property name, accessibly only via o["m(i):S"] or some such (I forget the syntax), by which one could unambiguously name a specific method in the overload.

But in no case would one want the overloaded member to test as if it were not present. This may be where Dmitry parts company from us on TC39. Whatever happens, having methods appear only when invoked is a bug in our view.


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

It is indeed more work:

function makeLazyMethodCloner(eager) {
    function DELETED() {}
    var cache = Object.create(null);
    var handler = {
        get: function (self, name) {
            if (cache[name] === DELETED)
                return Object.getPrototypeOf(eager)[name];
            if (!cache[name]) {
                if (name === 'hasOwnProperty') {
                    cache[name] = Proxy.createFunction({}, function (name) {
                        return cache[name] !== DELETED && eager.hasOwnProperty(name);
                    });
                } else {
                    cache[name] = Proxy.createFunction({}, function () {
                        return eager[name].apply(eager, arguments);
                    });
                }
            }
            return cache[name];
        },
        hasOwn: function (name) {
            return cache[name] && cache[name] !== DELETED;
        },
        has: function (name) {
            return this.hasOwn(name) || name in Object.getPrototypeOf(eager);
        },
        delete: function (name) {
            cache[name] = DELETED;
            return true;
        }
    };
    return Proxy.create(handler, Object.getPrototypeOf(eager));
}


Running this:

js -f proxymethod2.js -
m1
m2
js> p.hasOwnProperty('m1')
true
js> 'm1' in p
true
js> delete p.m1
true
js> p.hasOwnProperty('m1')
false
js> 'm1' in p              
false
js> o.__proto__.m1 = function(){return "new m1"}
(function () {return "new m1";})
js> p.hasOwnProperty('m1')                       
false
js> 'm1' in p                                    
true
js> p.m1                                         
(function () {return "new m1";})
js> p.m1()
"new m1"

Comments welcome. There's no comparison with __noSuchMethod__ since it does not even try to fix the delete bug, as you note:


> 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`

Exactly! Apples-to-apples comparison, the original makeLazyMethodCloner I showed compares favorably to the __noSuchMethod__ alternative (13 lines to 9; both fail to handle delete). But you can't fix the __noSuchMethod__ version to handle delete.

Calling that a feature in the __noSuchMethod__ case is defining away the problem. It goes beyond invoke-only methods. These are invoke-only with a vengeance -- you can't get rid of them. They smell like a certain browser's hated host objects.

Anyway, it's useful to agree to disagree sometimes. I do not agree that invoke-only methods are a valid use-case for proxies. I waved the __noSuchMethod__ flag hard when we discussed proxies and tried to make an invoke trap, but I'm convinced that the design you and Mark favored, with no invoke trap, is strictly better.

/be
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20101014/9f1f5ef2/attachment.html>


More information about the es-discuss mailing list