Look Ma, no "this" (was: ECMAScript Harmony)

Kris Zyp kris at sitepen.com
Sun Aug 24 15:22:56 PDT 2008


I don't see why it is better to have classes be sugar for closured object 
literals rather than prototype based structures. It seems the benefits that 
are being sought after are auto-binding on method extraction and private 
method creation. Both of these can be attained with prototype-based class 
desugaring. If you really you want auto-binding on method extraction (which 
IMO is of very minimal value), why not do it this way and keep class 
semantics much closer to the prototype techniques used in JavaScript today:

(function(){
    // desugared Point class
    Point = function(){};
    function privateMethod(){
    }
    Point.prototype = {
        get getX(){
            return function(){
                return this.x;
            }.bind(this);
        },
        get getY(){
            ...
        }
        get publicMethod(){
            // example public method calling an example private method
            return function(){
                // sugared version would be this.privateMethod(1,2)
                privateMethod.apply(this,1,2);
            }.bind(this);
        }
    }
});

However, I would prefer |this| instanceof checking instead auto-binding on
extraction. Rather than binding on extraction, class methods would always 
have an implicit check to make sure they were called with an object that was 
an instance of (instanceof) the class:
Point = function(){};
Point.prototype = {
    getX : function(){
        if (!(this instanceof Point)){
            throw new TypeError("Method called with incompatible object
instance");
        }
        return this.x;
    },
    ...
};

By using type checking instead of binding on extraction, this would allow
functions that recieve a function/method as parameter to apply the function
to appropriate compatible object instance with apply or call:
function showCoordinateOnSelect(coordinateGetter){
    table.onclick = function(event){
        var point = getPointForClick(event);
        alert(coordinateGetter.call(point));
    }
}
...
showCoordinateOnSelect(Point.prototype.getX);

By using prototype based semantics for future class sugar, we can
retain the same semantics used by vast numbers of JavaScript libraries and
programmers themselves. VMs can just as easily apply clever optimizations
for these structures, and existing class constructs will have a smooth
migration to sugared syntax.

Thanks,
Kris

> On Tue, Aug 19, 2008 at 9:15 PM, Kris Zyp <kris at sitepen.com> wrote:
>> Why do you freeze the functions? Is this just to cater to mutable
>> function
> > critics, or is there actually a reason tied class semantics?
>
> It is to cater to mutable function critics, since they're right ;).

That may be, but it seems like an orthogonal feature, seems like it should
be discussed separately to avoid confusing the goal of basic class
desugaring.

Thanks,
Kris

----- Original Message ----- 
From: "Mark S. Miller" <erights at google.com>
To: "Peter Michaux" <petermichaux at gmail.com>
Cc: "Brendan Eich" <brendan at mozilla.org>; <es4-discuss at mozilla.org>; "TC39"
<e-TC39 at ecma-international.org>; <es3.x-discuss at mozilla.org>
Sent: Tuesday, August 19, 2008 6:41 PM
Subject: Look Ma, no "this" (was: ECMAScript Harmony)


> On Wed, Aug 13, 2008 at 7:15 PM, Peter Michaux <petermichaux at gmail.com>
> wrote:
>> On Wed, Aug 13, 2008 at 2:26 PM, Brendan Eich <brendan at mozilla.org>
>> wrote:
>>
>> [snip]
>>
>>> We talked about desugaring classes in some detail in Oslo. During
>>> these exchanges, we discussed several separable issues, including
>>> classes, inheritance, like patterns, and type annotations. I'll avoid
>>> writing more here,
>>
>> Is there more to read elsewhere? I'd like to know concretely what
>> "desugaring classes" means.
>
> The main difference from the old "Classes as Sugar" proposal is to
> desugar to the objects-as-closure style pioneered by Crock rather than
> ES3-classical style of prototypical inheritance + this-binding.
>
>
> Point as a final root class:
>
> function Point(x, y) {
>    const self = Object.create(Point.prototype, {
>        toString: {value: Object.freeze(function() ('<' + self.getX()
> + ',' + self.getY() + '>'))},
>            enumerable: true},
>        getX: {value: Object.freeze(function() x),
>            enumerable: true},
>        getY: {value: Object.freeze(function() y),
>            enumerable: true}
>    }, true);
>    return self;
> }
>
> (Assuming that absent attributes default to false, which I don't think
> is currently the case in the ES3.1 draft.)
>
> If we stick with zero inheritance, which seemed attractive at Oslo, we
> can skip the part about inheritance below. Otherwise, read on.
>
>
> <inheritance>
>
>
> Point as a non-final non-abstract root/mixin class where toString is a
> final method:
>
> function PointMixin(self, x, y) {
>    Object.defineProperties(self, {
>        toString: {value: Object.freeze(function() ('<' + self.getX()
> + ',' + self.getY() + '>'))},
>            enumerable: true},
>        getX: {value: Object.freeze(function() x),
>            enumerable: true, flexible: true},
>        getY: {value: Object.freeze(function() y),
>            enumerable: true, flexible: true}
>    });
> }
> function Point(x, y) {
>    const self = Object.create(Point.prototype); // only for instanceof
>    PointMixin(self, x, y);
>    return Object.freeze(self);
> }
> Object.freeze(PointMixin);
> Object.freeze(Point.prototype);
> Object.freeze(Point);
>
>
> WobblyPoint as a non-abstract non-final subclass:
>
> function WobblyPointMixin(self, wobble) {
>    const super = Object.snapshot(self); // a snapshot is a frozen copy
>    Object.defineProperties(self, {
>        getX: {value: function() (super.getX() + Math.random()*wobble),
>            enumerable: true, flexible: true}
>    });
> }
> function WobblyPoint(x, y, wobble) {
>    const self = Object.create(WobblyPoint.prototype); // only for
> instanceof
>    PointMixin(self, x, y);
>    WobblyPointMixin(self, wobble);
>    return Object.freeze(self);
> }
> Object.freeze(WobblyPointMixin);
> WobblyPoint.prototype = Object.create(Point.prototype, {
>    constructor: {value: WobblyPoint}
> }, true);
> Object.freeze(WobblyPoint);
>
>
> This gets self-overriding a super-binding correct under single
> inheritance and even under linearized multiple inheritance.
>
>
> </inheritance>
>
>
> Further, methods auto-bind on extraction, as they did in ES4:
>
> const pt = new WobblyPoint(3, 4, 0.1);
> const gx = pt.getX;
>
> gx is a no argument function bound to pt. For ES3-style classical
> code, you'd instead have to say "pt.getX.bind(pt)".
>
> Notice that the above code works well *because* it never says "this".
> JavaScript's "this" is an incredibly tricky construct. However, the
> above technique is impractical today because of the extra allocation
> cost -- one closure per method per instance. But as Dan Ingalls says
> "you can cheat if you don't get caught." The above desugaring shows
> how to define the *semantics* of the class construct. The actual
> behavior of the class construct must not be observably different from
> some such desugaring. But in a class-aware implementation, it should
> of course perform better than you'd expect from this desugaring.
>
> In the Caja project, we are exploring whether this optimization can
> even be provided as a source-to-source translation:
> <http://google-caja.googlecode.com/svn/trunk/doc/html/cajitaOptimization/index.html>.
> It's not yet clear that the idea is practically implementable by this
> technique
> <http://groups.google.com/group/google-caja-discuss/browse_thread/thread/df6c8ea9a1ca1aa3>.
> But none of these problems should impede a more directly implemented
> optimization.
>
> -- 
> Cheers,
> --MarkM
> _______________________________________________
> Es-discuss mailing list
> Es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>



More information about the Es-discuss mailing list