const classes for max/min

# Russell Leggett (13 years ago)

Waldemar has put a pretty firm line in the sand regarding the need for a higher integrity class construct. While I would love to start by agreeing on max/min as a safety syntax and iterating forward, I appreciate the desire for such a construct and would probably use it myself. It seems to me that one of the big reasons for Dart's version of classes is to enforce this type of integrity. Certainly, it would be useful. The questions is: how hard would it be to get it to work well this max/min as the base. Waldemar conceded that he was even fine with const not being the default, which would have been a much bigger problem. After looking over the const class section of the harmony class proposalharmony:classes#constand

realized that it could work almost exactly the same. The big thing I wanted to avoid, though was the public keyword. This is what I came up with for making a const Monster class from the max/min proposal:

// a private name used by the Monster class
const pHealth = Name.create();

// adding const here triggers a few key things
// 1. The variable Monster will be const
// 2. Monster.prototype is frozen
const class Monster {
    // 3. the constructor will be frozen (which includes adding

"static" style functions) constructor(name, health) { // 4. anything added to the instance here will be writable/non-configurable this.name = name; // using private names allows protection against mutation from the outside this[pHealth] = health; 5. before returning, instance is frozen - see original const class for details } // 6. all other methods on the prototype will also be frozen attack(target) { log('The monster attacks ' + target); }

    // get/set (also frozen) can be used to enforce encapsulation on

data members set health(value) { if (value < 0) { throw new Error('Health must be non-negative.') } this[pHealth] = value } get isAlive() { return this[pHealth] > 0; } }

// cannot reassign
Monster = foo; // error, Monster is const

// cannot modify constructor function
Monster.myStaticFunc = function(){}; // error, constructor is frozen

// cannot modify the prototype
Monster.prototype.bar = bar; // error, prototype is frozen

let m = new Monster("Beholder",45);
m.name = "Fred"
log(m.name); // logs "Fred"


// only has a setter
log(m.health); // error, no getter
m.health = 0;
log(m.isAlive); // logs false

m.randomProp = foo; // error, instance is frozen

The only major difference between what I am proposing now, and the original const class, is that I stick with just using this.name = name; instead of public getName(){ return name;} ( I think public name = name; would have been allowed, and almost exactly what I'm intending instead of adding a method). Additionally, of course, my proposal is based on max/min instead of harmony classes, so the other features are obviously not allowed.

The only big hole I'm noticing is that const classes basically could not have static members. It is future friendly to addition of the static keyword, though if we wanted to add that.

Here's a max/min const class version of the const class example from harmony classes:

const pX = Name.create();
const pY = Name.create();
const class Point{
    constructor(x,y){
        this[pX] = x;
        this[pY] = y;
    }
    get x(){ return this[pX]; }
    get y(){ return this[pY]; }
}

I think this could really work, because it is minimal impact, and does not affect future friendliness. Other additions like private properties and static methods, for example, would easily fit before ES6 is finalized or as a future. enhancement.

# Axel Rauschmayer (13 years ago)

Various thoughts:

  • One key question: Does a property declaration have to look declarative in order to be used declaratively? You are saying no.

  • One could have public foo = ... as syntactic sugar for this.foo = .... But then the issue is whether private will ever be used in an analogous manner (my understanding: no).

  • Subtyping is going to be a bit tricky, because a constructor has to behave differently if called from a subconstructor.

  • In theory, one could freeze instances, without freezing classes (enabling constructor properties). But that would be a bit odd.

  • I would love to have sealed instances. Those would be great for catching typos and performing shape-related optimizations.

# Russell Leggett (13 years ago)

On Thu, May 24, 2012 at 3:53 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

Various thoughts:

  • One key question: Does a property declaration have to look declarative in order to be used declaratively? You are saying no.

I think a more declarative form could be added later, this is not future hostile to that. What is currently here makes it so that any properties added during construction are non-configurable. Assuming the constructor gets run, and assuming properties are not added conditionally, guarantees can be made.

  • One could have public foo = ... as syntactic sugar for this.foo = .... But then the issue is whether private will ever be used in an analogous manner (my understanding: no).

Going with the max/min approach of tiny additions. The only addition here is the const keyword in front of class. Big bang for the buck.

  • Subtyping is going to be a bit tricky, because a constructor has to behave differently if called from a subconstructor.

This is covered in the original proposal, and I would have it work the same: "During construction of an instance of a const class, the instance is extensible. Each public declaration adds a non-configurable, (possibly non-writable) data property to the instance. During constructor chainingharmony:classes#constructor_chaining,

the [[Call]] method of the superclass constructor, even if const, does not make the instance non-extensible. Rather, the [[Construct]] method of a const class makes the instance non-extensible before returning. An instance of a non-const class which inherits from a const class is thereby born extensible unless the constructor makes it non-extensible by other means. Taken together, instantiating a const class must result either in a thrown exception, non-termination, or in an instance of that class with the public own properties declared within the constructor of that class."

  • In theory, one could freeze instances, without freezing classes (enabling constructor properties). But that would be a bit odd.

Its easier to add static later than freeze classes later.

  • I would love to have sealed instances. Those would be great for catching typos and performing shape-related optimizations.

Yes, I think I might have specified this poorly. I'm seeking the same behavior as the original const class proposal. Instances would be non-extensible upon completion of construction. Nothing could be added or removed. Methods would be non-writable, but data would be. As I indicated, private names can be used to protect against unwanted mutation.

# Axel Rauschmayer (13 years ago)
  • One key question: Does a property declaration have to look declarative in order to be used declaratively? You are saying no.

I think a more declarative form could be added later, this is not future hostile to that.

It looks OK to me. However, adding another form later seems like a bad idea.

What is currently here makes it so that any properties added during construction are non-configurable. Assuming the constructor gets run, and assuming properties are not added conditionally, guarantees can be made.

Wouldn’t the instance be frozen? Then all properties would be non-configurable and non-writeable and the instance would not be extensible.

  • One could have public foo = ... as syntactic sugar for this.foo = .... But then the issue is whether private will ever be used in an analogous manner (my understanding: no).

Going with the max/min approach of tiny additions. The only addition here is the const keyword in front of class. Big bang for the buck.

I agree.

  • Subtyping is going to be a bit tricky, because a constructor has to behave differently if called from a subconstructor.

This is covered in the original proposal, and I would have it work the same: "During construction of an instance of a const class, the instance is extensible. Each public declaration adds a non-configurable, (possibly non-writable) data property to the instance. During constructor chaining, the [[Call]] method of the superclass constructor, even if const, does not make the instance non-extensible. Rather, the [[Construct]] method of a const class makes the instance non-extensible before returning. An instance of a non-const class which inherits from a const class is thereby born extensible unless the constructor makes it non-extensible by other means. Taken together, instantiating a const class must result either in a thrown exception, non-termination, or in an instance of that class with the public own properties declared within the constructor of that class."

Nice. But it goes beyond a class declaration being syntactic sugar for a function (unless one introduces a way to specify [[Construct]]).

  • I would love to have sealed instances. Those would be great for catching typos and performing shape-related optimizations.

Yes, I think I might have specified this poorly. I'm seeking the same behavior as the original const class proposal. Instances would be non-extensible upon completion of construction. Nothing could be added or removed. Methods would be non-writable, but data would be. As I indicated, private names can be used to protect against unwanted mutation.

What function (or equivalent operation) would [[Construct]] apply to an instance after initialization? Object.seal() or Object.freeze()?

# Erik Arvidsson (13 years ago)

I think it is impossible to achieve Waldemar's goal with syntactic sugar only. I also don't think that is reason enough to block ES6 classes. The requirements he wants cannot be expressed with ES5 semantics.

The big issues Waldemar wanted were (as far as I remember):

  1. Reading a non existent property should throw:

const class C {} const c = new C; console.log(c.foo); // throw to catch typos

This is a major change in semantics of JS and it was shot down for ES5 strict so I don't think it has any chance of getting accepted ever.

  1. No way for an object to escape the constructor before the constructor was "done":

const class C { constructor() { this.x = doSomething(this); // oops, this.y is not yet initialized this.y = doSomethingElse(); }

Alternatives here include read barriers but that also requires declarative instance properties so we know ahead of time what to poison.

  1. Shape guarantee:

const class C { constructor() { if (Math.random() < .5) this.x = true; // Half of the times there will not be an x property. }

The problems with these is that no other dynamic language has these kind of requirements. JS developers get by without them today. If we designed a new language I think they would be nice features to have (ahem Dart) but our goal is to improve ECMAScript and not replace it. We should not try to make all languages fit into the same box.

At this point I think it is up to Waldemar and supporters of his requirements to come up with concrete proposals how these things can fit into ECMAScript. (Thanks Russel for starting this thread.)

# Russell Leggett (13 years ago)

On Thu, May 24, 2012 at 4:27 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

  • One key question: Does a property declaration have to look declarative

in order to be used declaratively? You are saying no.

I think a more declarative form could be added later, this is not future hostile to that.

It looks OK to me. However, adding another form later seems like a bad idea.

An immediate example would be something like adding private properties.

class Point(){ private x; private y; constructor(x,y){ @x = x; @y = y; } }

In this case, the keyword private might create a private name, as well as declare the property exists on the instance. The constructor would simply set the value. This is something which could work in either a const, or non-const class. I'm not proposing this now, just saying its a possibility.

What is currently here makes it so that any properties added during construction are non-configurable. Assuming the constructor gets run, and assuming properties are not added conditionally, guarantees can be made.

Wouldn’t the instance be frozen? Then all properties would be non-configurable and non-writeable and the instance would not be extensible.

The instance itself would be sealed. The prototype would be frozen, though. So basically, data would be writable, but methods would not. Because non-method properties cannot be put on const classes, this makes a pretty strong guarantee about the separation between data and behavior.

  • One could have public foo = ... as syntactic sugar for `this.foo =

.... But then the issue is whetherprivate` will ever be used in an analogous manner (my understanding: no).

Going with the max/min approach of tiny additions. The only addition here is the const keyword in front of class. Big bang for the buck.

I agree.

  • Subtyping is going to be a bit tricky, because a constructor has to

behave differently if called from a subconstructor.

This is covered in the original proposal, and I would have it work the same: "During construction of an instance of a const class, the instance is extensible. Each public declaration adds a non-configurable, (possibly non-writable) data property to the instance. During constructor chainingharmony:classes#constructor_chaining, the [[Call]] method of the superclass constructor, even if const, does not make the instance non-extensible. Rather, the [[Construct]] method of a const class makes the instance non-extensible before returning. An instance of a non-const class which inherits from a const class is thereby born extensible unless the constructor makes it non-extensible by other means. Taken together, instantiating a const class must result either in a thrown exception, non-termination, or in an instance of that class with the public own properties declared within the constructor of that class."

Nice. But it goes beyond a class declaration being syntactic sugar for a function (unless one introduces a way to specify [[Construct]]).

Yes, other than the syntax, I suppose this is the biggest addition. I think it makes sense, though, and thats pretty out of the way from users.

  • I would love to have sealed instances. Those would be great for catching

typos and performing shape-related optimizations.

Yes, I think I might have specified this poorly. I'm seeking the same behavior as the original const class proposal. Instances would be non-extensible upon completion of construction. Nothing could be added or removed. Methods would be non-writable, but data would be. As I indicated, private names can be used to protect against unwanted mutation.

What function (or equivalent operation) would [[Construct]] apply to an instance after initialization? Object.seal() or Object.freeze()?

Object.seal()

# Russell Leggett (13 years ago)

On Thu, May 24, 2012 at 4:50 PM, Erik Arvidsson <erik.arvidsson at gmail.com>wrote:

[snip]

The problems with these is that no other dynamic language has these kind of requirements. JS developers get by without them today. If we designed a new language I think they would be nice features to have (ahem Dart) but our goal is to improve ECMAScript and not replace it. We should not try to make all languages fit into the same box.

At this point I think it is up to Waldemar and supporters of his requirements to come up with concrete proposals how these things can fit into ECMAScript. (Thanks Russel for starting this thread.)

Yes, I would really like to see some more elaboration from him. I guess we'll have to wait for his return. One of the things that I was trying to show was that the max/min proposal is not future hostile, whether or not these things happen now, especially if it is not the default. I really just don't want classes to be blocked.

# Rick Waldron (13 years ago)

On Thursday, May 24, 2012 at 5:36 PM, Russell Leggett wrote:

On Thu, May 24, 2012 at 4:50 PM, Erik Arvidsson <erik.arvidsson at gmail.com (mailto:erik.arvidsson at gmail.com)> wrote:

[snip]

The problems with these is that no other dynamic language has these kind of requirements. JS developers get by without them today. If we designed a new language I think they would be nice features to have (ahem Dart) but our goal is to improve ECMAScript and not replace it. We should not try to make all languages fit into the same box.

At this point I think it is up to Waldemar and supporters of his requirements to come up with concrete proposals how these things can fit into ECMAScript. (Thanks Russel for starting this thread.)

Yes, I would really like to see some more elaboration from him. I guess we'll have to wait for his return. One of the things that I was trying to show was that the max/min proposal is not future hostile, whether or not these things happen now, especially if it is not the default. I really just don't want classes to be blocked.

Agreed 100%, especially considering the favorable consensus appears to be in the majority now.

I may be in left field here, but what stops:

class C { f = "foo";

constructor() {

} }

From desugaring to:

function C() {}

C.prototype.f = "foo";

I looked through as many of the class related proposals and couldn't find anything that disproved this possibility

# Axel Rauschmayer (13 years ago)

Couldn’t #1 (optionally) be handled via a proxy and #2+#3 via static analysis?

# Brendan Eich (13 years ago)

Axel Rauschmayer wrote:

Couldn’t #1 (optionally) be handled via a proxy and #2+#3 via static analysis?

Don't bring up proxies as a solution here! Direct proxies require target objects, which are both irrelevant and unacceptably high overhead.

# Allen Wirfs-Brock (13 years ago)

On May 24, 2012, at 1:50 PM, Erik Arvidsson wrote:

I think it is impossible to achieve Waldemar's goal with syntactic sugar only. I also don't think that is reason enough to block ES6 classes. The requirements he wants cannot be expressed with ES5 semantics.

I'm actually rather this disinclined to play this game. As soon as you put "const" in front of "class" you have a whole new construct within which you can define pretty much any syntax and semantics you want as long as you think users will find them acceptable. Hence there is very little need to "prove" anything here.

But, who can resist a design challenge...

The big issues Waldemar wanted were (as far as I remember):

  1. Reading a non existent property should throw:

const class C {} const c = new C; console.log(c.foo); // throw to catch typos

This is a major change in semantics of JS and it was shot down for ES5 strict so I don't think it has any chance of getting accepted ever.

The specification framework is all in place to support this. Objects created via a const class constructor could be an alternative version of [[Get]] (ecma-international.org/ecma-262/5.1/#sec-8.12.3 remember to say "thank you Jason") that throws in step 2.

  1. No way for an object to escape the constructor before the constructor was "done":

const class C { constructor() { this.x = doSomething(this); // oops, this.y is not yet initialized this.y = doSomethingElse(); }

lots of ways to approach this, but a simple way to prevent a value from escaping is simply to not provide any way to reference it. Hence, don't bind |this| in the body of a const constructor. Provide an alternativenon value producing way to refer to the uninitialized instance within the constructor body. For example, this (I'm being intentionally ugly) that can only occur as the base of a Reference that is produced by the LHSExpression on the left of a = operator. eg:

const class C{ constructor() { this.x = 0; __this.__y = doSomething(this); //oops, this isn't defined here } }

or with additional sugar:

const class C{ constructor() { initialize { //new special form this.x = 0; __this.__y = doSomething(this); //oops, this isn't defined here } then { CManager.memorize(this); //ok to reference this here };
} }

Alternatives here include read barriers but that also requires declarative instance properties so we know ahead of time what to poison.

  1. Shape guarantee:

const class C { constructor() { if (Math.random() < .5) this.x = true; // Half of the times there will not be an x property. }

The problems with these is that no other dynamic language has these kind of requirements. JS developers get by without them today. If we designed a new language I think they would be nice features to have (ahem Dart) but our goal is to improve ECMAScript and not replace it. We should not try to make all languages fit into the same box.

Smalltalk objects have a fixed shape...

Building upon the approach above

const class C{ instance {x,y} //announce the shape constructor() { this.x = 0; __this.__y = doSomething(this); //oops, this isn't defined here //post condition: x and y are initialized. Can be either statically proven or dynamically checked } }

At this point I think it is up to Waldemar and supporters of his requirements to come up with concrete proposals how these things can fit into ECMAScript. (Thanks Russel for starting this thread.)

And since there are so many possible ways of doing this and max/min classes constrains so little, it is hard to see why this should be a blocking issue.

# Herby Vojčík (13 years ago)

Allen Wirfs-Brock wrote:

On May 24, 2012, at 1:50 PM, Erik Arvidsson wrote:

I think it is impossible to achieve Waldemar's goal with syntactic sugar only. I also don't think that is reason enough to block ES6 classes. The requirements he wants cannot be expressed with ES5 semantics.

I'm actually rather this disinclined to play this game. As soon as you put "const" in front of "class" you have a whole new construct within which you can define pretty much any syntax and semantics you want as long as you think users will find them acceptable. Hence there is very little need to "prove" anything here.

But, who can resist a design challenge...

The big issues Waldemar wanted were (as far as I remember):

  1. Reading a non existent property should throw:

const class C {} const c = new C; console.log(c.foo); // throw to catch typos

This is a major change in semantics of JS and it was shot down for ES5 strict so I don't think it has any chance of getting accepted ever.

The specification framework is all in place to support this. Objects created via a const class constructor could be an alternative version of [[Get]] (ecma-international.org/ecma-262/5.1/#sec-8.12.3 remember to say "thank you Jason") that throws in step 2.

  1. No way for an object to escape the constructor before the constructor was "done":

BTW you can not guarantee this at all. Because of super constructors that are not const. imo.

const class C { constructor() { this.x = doSomething(this); // oops, this.y is not yet initialized this.y = doSomethingElse(); }

lots of ways to approach this, but a simple way to prevent a value from escaping is simply to not provide any way to reference it. Hence, don't bind |this| in the body of a const constructor. Provide an alternativenon value producing way to refer to the uninitialized instance within the constructor body. For example, this (I'm being intentionally ugly) that can only occur as the base of a Reference that is produced by the LHSExpression on the left of a = operator. eg:

Why use any ugly thing like this, when plain 'this' could be used as well (and static analysis will only allow it to be use as the base of a Reference that is produces by the LHSExpression)?

(correct me if I am wrong and have missed something, but it seems to me 'this' is fine as-is when I know the context is const-constructor)

const class C{ constructor() { this.x = 0; __this.__y = doSomething(this); //oops, this isn't defined here } }

or with additional sugar:

const class C{ constructor() { initialize { //new special form this.x = 0; __this.__y = doSomething(this); //oops, this isn't defined here } then { CManager.memorize(this); //ok to reference this here }; } }

class C { constructor() { const; // constant part this.x = 0; this.y = doSomething(this); //oops var; // variable (modifiable) part CManager.memorize(this); } }

Alternatives here include read barriers but that also requires declarative instance properties so we know ahead of time what to poison.

  1. Shape guarantee:

const class C { constructor() { if (Math.random() < .5) this.x = true; // Half of the times there will not be an x property. }

The problems with these is that no other dynamic language has these kind of requirements. JS developers get by without them today. If we designed a new language I think they would be nice features to have (ahem Dart) but our goal is to improve ECMAScript and not replace it. We should not try to make all languages fit into the same box.

Smalltalk objects have a fixed shape...

Building upon the approach above

const class C{ instance {x,y} //announce the shape constructor() { this.x = 0; __this.__y = doSomething(this); //oops, this isn't defined here //post condition: x and y are initialized. Can be either statically proven or dynamically checked } }

class C { constructor() { const {x,y}; // constant part, annoucing the shape this.x = 0; this.y = doSomething(this); //oops //post condition: x and y are initialized. Can be either statically var; // variable (modifiable) part CManager.memorize(this); } }