An experiment using an object literal based class definition pattern

# Allen Wirfs-Brock (13 years ago)

This is partially a followup to the thread: An "extend" operator is a natural companion to <|

Several interesting ideas developed out of informal conversations during last week's TC-39 meeting.

The first ideas concerns the <| operator. As currently defined at harmony:proto_operator a usage such as: function foo () {}; foo.prototoype.method = function() {}; const bar = foo <| function() {};

assigns to bar a function whose [[Prototype]] internal property is the value of foo. However, as current specified the [[Prototype]] of bar.prototype will be Object.prototype. In other words, bar inherits from foo but bar.prototype doesn't inherit from foo.prototype. That seems unlikely to be the desired behavior in most situations. We can fix that by specifying that if the RHS of <| is a function expression and the LHS is an object with a "prototype" property then the object that is the value of the "prototype" property of the new function object inherits from LSH.prototype rather than Object.prototype. I'll assume that definition for the rest of this posting. It means that for the above statements, the following identities hold: Object.getPrototypeOf(bar)===foo; //true Object.getPrototypeOf(bar.prototype)===foo.prototype; //true

Also note that the LHS does not need to be a function for this to work. You might have defined: const foo = Function.prototype <| { aConstructorProperty: whatever, prototype: { anInstanceProperty: whatever } };

and the above identities would still hold.

Another idea was an alternative way to express the "extend" operator for literal property definitions. Doug Crockford suggested the following syntax:

obj.{
   prop:1,
   prop:2
   }

This takes the properties defined in the object literal and adds then to obj (or replaces like named already existing properties). An exception is thrown if they can't all be added. Essentially .{ } is a postfix operator that extends an object with additional properties.

Using these two new ideas and other active object literal enhancement proposals it is pretty easy to compose a class-like declaration. For example the SkinnedMesh from the harmony:classes proposal can be code as:

const SkinnedMesh = THREE.Matrix4.Mesh <| function(geometry,materials){ super.construtor(geometry,materials); this.{ identity.Matrix: new THREE.Matrix4(), bones: [], boneMatrices: [] }; }.prototype.{ update(camera) { ... super(update); } }.constructor.{ default(){ return new this(THREE.defaultGeometry,THREE.defaultMaterials); } };

Note that I added a constructor (ie, "static") method as one wasn't included in the original example.

This definition is very similar to the one in the classes proposal but in completely defined using object literals. Here is the generalized code pattern for such "class" definitions:

const className = superClass <| function(/constructor parameters/) { /constructor body/ super.constructor(/arguments to super constructor/); this.{ /* per instance property declarations / }; / other constructor code */ }.prototype.{ /instance properties defined on prototype/ ).constructor.{ /*class (ie, constructor "static") properties */ };

Another idea that was discussed and which I think we are near to reaching consensus on is how to define a private named property in an object literal (or class declaration). As has been suggested in the past, it would be done by using a bracketed expression in the property name position. For example:

const pname1 = Name.create(); //create a new private name and bind it to a const const pname2 = Name.create();
let obj = { regularName: 0, [pname1]: 1, // a data property with a private name pname2 {reurn this[pname1]} //a method data property with a private name };

I wanted to evaluate these feature proposals and coding patterns with something more substantial then a 5 line synthetic example. What would it be like to actually write real class based code using them. So, as an experiment, I decided to see what the Smalltalk-80 collection hierarchy would look like coded in ES.next using these ideas and patterns. The Smalltalk-80 collections hierarchy is a good test case because it is a well-known class hierarchy that exercises pretty much the full gambit of features that are useful in building complex single inheritance hierarchies. It is relatively deep (6-levels in my experiment), uses both class and instance side inheritance, make extensive use of super method calls, "protected" methods, and inherited private instance state. Its basic design has been in use for over thirty years and is one of the original object-oriented hierarchies that pretty much established the pattern of how to used OO implementation inheritance. You may quibble about some of the design approaches and implementation techniques used in this vintage hierarchy, but I think it is reasonable to expect that any fully featured dynamic OO language should be expressive enough to support an implementation of this class hierarchy.

My implementation is based upon the description in chapter 13 of the Smalltalk-80 "Blue Book" (stephane.ducasse.free.fr/FreeBooks/BlueBook ) along with an occasional peak at the open source Squeak Smalltalk-80 implementation (ftp.squeak.org/1.3/SqueakV1.sources.gz ). It isn't a complete implementation but is complete enough to get a pretty good feel for what it is like to write and read code written in this style. Oh, and did I mention, that is is completely untested and probably wouldn't even syntax check correctly if I had a parser for this ES.next alternative.

The full source file of this experiment is at allenwb/ESnext-experiments/blob/master/ST80collections-exp1.js . I encourage you to look closely at the whole thing, think about these coding pattens for defining "classes", and to make your feedback here.

As teaser, here is the code for one of the classes:

/-------------------------- ArrayCollection --------------------------/ /* I am an abstract collection of elements with a fixed range of integers (from 1 to n>=1) as external keys. */ export const ArrayedCollection = SequenceableCollection <| function(elements=0) { super.constructor(elements); }.prototype.{ //accessing protocol get size() {return this.basicSize}, at(index) {return this.basicAt(Math.floor(index))}, atPut(index,value) {return this.basicAtPut(Math.floor(index),value)}, //adding protocol add(newObject) {this.shouldNotImplement()}, //protected methods for storage access }.constructor.{ newWithAll(size,value) { return (new this(size)).atAllPut(value); }, with(...args) { const newCollection = new this(args.length); let i = 1; args.forEach(function(element) {newCollection.atPut(i++,element)}); return newCollection; }, className: "ArrayedCollection", };

# Axel Rauschmayer (13 years ago)

From: Allen Wirfs-Brock <allen at wirfs-brock.com> Subject: An experiment using an object literal based class definition pattern Date: August 4, 2011 22:57:48 GMT+02:00 To: es-discuss <es-discuss at mozilla.org>

However, as current specified the [[Prototype]] of bar.prototype will be Object.prototype. In other words, bar inherits from foo but bar.prototype doesn't inherit from foo.prototype. That seems unlikely to be the desired behavior in most situations. We can fix that by specifying that if the RHS of <| is a function expression and the LHS is an object with a "prototype" property then the object that is the value of the "prototype" property of the new function object inherits from LSH.prototype rather than Object.prototype.

That is really cool, because it’ll give you inheritance for class methods and for instance methods (with the two being separate).

Another idea was an alternative way to express the "extend" operator for literal property definitions. Doug Crockford suggested the following syntax:

obj.{
   prop:1,
   prop:2
   }

It looks nice, but the operator should also work for a RHS that is not an object literal.

This takes the properties defined in the object literal and adds then to obj (or replaces like named already existing properties). An exception is thrown if they can't all be added. Essentially .{ } is a postfix operator that extends an object with additional properties.

Using these two new ideas and other active object literal enhancement proposals it is pretty easy to compose a class-like declaration. For example the SkinnedMesh from the harmony:classes proposal can be code as:

const SkinnedMesh = THREE.Matrix4.Mesh <| function(geometry,materials){ super.construtor(geometry,materials); this.{ identity.Matrix: new THREE.Matrix4(), bones: [], boneMatrices: [] }; }.prototype.{ update(camera) { ... super(update); } }.constructor.{ default(){ return new this(THREE.defaultGeometry,THREE.defaultMaterials); } };

How about rewriting this as follows?

const SkinnedMesh = THREE.Matrix4.Mesh <| function(geometry,materials){ super.construtor(geometry,materials); this.{ identity.Matrix: new THREE.Matrix4(), bones: [], boneMatrices: [] }; }.{ default(){ return new this(THREE.defaultGeometry,THREE.defaultMaterials); } }.prototype.{ update(camera) { ... super(update); }

}; // possibly: }.constructor;

It seems more intuitive to me to add the constructor methods directly to the constructor, instead of returning to that function via prototype.constructor. The final ".constructor" is not as elegant as I would like, though.

Another possibility: allow "extend" for properties in object literals (kind of a recursive overriding).

const SkinnedMesh = THREE.Matrix4.Mesh <| function(geometry,materials){ super.construtor(geometry,materials); this.{ identity.Matrix: new THREE.Matrix4(), bones: [], boneMatrices: [] }; }.{ default(){ return new this(THREE.defaultGeometry,THREE.defaultMaterials); }, prototype.{ update(camera) { ... super(update); } }

};

One more idea: If the constructor is becoming the class and has properties of its own, perhaps there is a way of writing a constructor function as an object literal, by giving an object literal a callable “body”.

# Allen Wirfs-Brock (13 years ago)

On Aug 4, 2011, at 8:39 PM, Axel Rauschmayer wrote:

From: Allen Wirfs-Brock <allen at wirfs-brock.com> However, as current specified the [[Prototype]] of bar.prototype will be Object.prototype. In other words, bar inherits from foo but bar.prototype doesn't inherit from foo.prototype. That seems unlikely to be the desired behavior in most situations. We can fix that by specifying that if the RHS of <| is a function expression and the LHS is an object with a "prototype" property then the object that is the value of the "prototype" property of the new function object inherits from LSH.prototype rather than Object.prototype.

That is really cool, because it’ll give you inheritance for class methods and for instance methods (with the two being separate).

exactly!

Another idea was an alternative way to express the "extend" operator for literal property definitions. Doug Crockford suggested the following syntax:

obj.{
   prop:1,
   prop:2
   }

It looks nice, but the operator should also work for a RHS that is not an object literal.

But using . as the operator it would be ambiguous with normal property access. A different and unique operator wouldn't have the problem but also wouldn't look so nice and the primary motivation for the operator is for use in expression such this post is about. An alternative that could work would be to allow a parenthesized expression to follow the dot.

...

How about rewriting this as follows?

I did something similar in my first go at this, but there is a problem...

const SkinnedMesh = THREE.Matrix4.Mesh <| function(geometry,materials){ super.construtor(geometry,materials); this.{ identity.Matrix: new THREE.Matrix4(), bones: [], boneMatrices: [] }; }.{ default(){ return new this(THREE.defaultGeometry,THREE.defaultMaterials); } }.prototype.{ update(camera) { ... super(update); }

}; // possibly: }.constructor;

The value of the above expression that gets bound to SkinnedMesh is the prototype, not the constructor. For a "class" definition you want that value to be the constructor object, not the prototype. As your comment mentioned, to get this you would need to add a .constructor to the end. I suspect that there would be a strong tendency for programmer to forget to do this. I think always following the pattern I identified is what people would want always want to do. Plus the progress of constructor+per instance state, then prototype properties, and finally constructor properties seems like the right ordering from most important to least important.

It seems more intuitive to me to add the constructor methods directly to the constructor, instead of returning to that function via prototype.constructor. The final ".constructor" is not as elegant as I would like, though.

To me, the .prototype,{ and .constructor.{ feel very much like prototype: and class: compartment labels of the sort that was discussed a couple weeks ago.

Another possibility: allow "extend" for properties in object literals (kind of a recursive overriding).

const SkinnedMesh = THREE.Matrix4.Mesh <| function(geometry,materials){ super.construtor(geometry,materials); this.{ identity.Matrix: new THREE.Matrix4(), bones: [], boneMatrices: [] }; }.{ default(){ return new this(THREE.defaultGeometry,THREE.defaultMaterials); }, prototype.{ update(camera) { ... super(update); } }

};

Yes that could work, but it buries the prototype properties deep inside the constructor properties. I again, I think the flat constructor-instance properties-consturctor properties sequence will be the easiest to teach, remember, and use.

One more idea: If the constructor is becoming the class and has properties of its own, perhaps there is a way of writing a constructor function as an object literal, by giving an object literal a callable “body”.

const SkinnedMesh = THREE.Matrix4.Mesh <| { function(geometry,materials) { super.constructor(geometry,materials); this.{ identity.Matrix: new THREE.Matrix4(), bones: [], boneMatrices: [] }; } default() { return new this(THREE.defaultGeometry,THREE.defaultMaterials); } prototype: { // TODO: does not have the right prototype

but you can explicitly set it using <|

    update(camera) {
        ...
        super(update);
    }
}

};

This is essentially what I showed in the second code snippet of my original post and is a technique that I used to create the non-constructable base AbstractClass in my collections experiment. It works ok for situations where you need to break the parallelism of the class/instance inheritance hierarchies which you tend to want to do at the root of a hierarchy. However, for most situation, even when the class is abstract, I think it is best to follow the basic class pattern I provided.

# Axel Rauschmayer (13 years ago)

But using . as the operator it would be ambiguous with normal property access. A different and unique operator wouldn't have the problem but also wouldn't look so nice and the primary motivation for the operator is for use in expression such this post is about. An alternative that could work would be to allow a parenthesized expression to follow the dot.

Agreed. I like the solution with parens. A method would be OK, too, I guess.

It seems more intuitive to me to add the constructor methods directly to the constructor, instead of returning to that function via prototype.constructor. The final ".constructor" is not as elegant as I would like, though.

To me, the .prototype,{ and .constructor.{ feel very much like prototype: and class: compartment labels of the sort that was discussed a couple weeks ago.

Yes, I can see that. I really like the way it looks.

One more idea: If the constructor is becoming the class and has properties of its own, perhaps there is a way of writing a constructor function as an object literal, by giving an object literal a callable “body”.

const SkinnedMesh = THREE.Matrix4.Mesh <| { function(geometry,materials) { super.constructor(geometry,materials); this.{ identity.Matrix: new THREE.Matrix4(), bones: [], boneMatrices: [] }; } default() { return new this(THREE.defaultGeometry,THREE.defaultMaterials); } prototype: { // TODO: does not have the right prototype but you can explicitly set it using <|

    update(camera) {
        ...
        super(update);
    }
}

};

I thought about setting the prototype via <|, but didn’t like the redundancy.

This is essentially what I showed in the second code snippet of my original post and is a technique that I used to create the non-constructable base AbstractClass in my collections experiment. It works ok for situations where you need to break the parallelism of the class/instance inheritance hierarchies which you tend to want to do at the root of a hierarchy. However, for most situation, even when the class is abstract, I think it is best to follow the basic class pattern I provided.

Subjectively, I like to have an easily identifiable construct if concepts are reified in a language. Having things spread out like in your proposal (where the dependence on order also makes it feel brittle) seems less concise (even if it looks awfully nice). This line of arguing also makes me think that class literals might not be such a bad idea (easy tool support is another argument in their favor).

What I would propose is a new kind of object literal, because with constructors, the object nature of a function suddenly matters tremendously. Thus, you could flip things around as follows, if a function could be written like an object literal. That gives you a more compact single construct to represent a class.

== A function with extended properties ==

function(x, y) { …. } . { prop1: value1, prop2: value2 }

== An object literal that is a function == { function(x, y) { } prop1: value1, prop2: value2 }

# Brendan Eich (13 years ago)

On Aug 4, 2011, at 11:00 PM, Allen Wirfs-Brock wrote:

On Aug 4, 2011, at 8:39 PM, Axel Rauschmayer wrote:

Another idea was an alternative way to express the "extend" operator for literal property definitions. Doug Crockford suggested the following syntax:

obj.{
   prop:1,
   prop:2
   }

It looks nice, but the operator should also work for a RHS that is not an object literal.

But using . as the operator it would be ambiguous with normal property access. A different and unique operator wouldn't have the problem but also wouldn't look so nice and the primary motivation for the operator is for use in expression such this post is about.

Still, unliked <|, an extend function (built-in module export) would be useful in more cases, based on Prototype and other libraries' Object.extend.

An alternative that could work would be to allow a parenthesized expression to follow the dot.

ECMA-357 (E4X) uses that for something we want to get rid of (filtering predicates, 'with' like things). So a.(b) as Object.extend(a, b) is possible, but again there is not strong need for an operator.

Using a.{p1:v1,...} syntax is attractive in its own right, but not as a final syntax in lieu of classes.

How about rewriting this as follows? I did something similar in my first go at this, but there is a problem...

Right, the desugaring from class syntax is fragile and error-prone. It's clever, but it doesn't eliminate the higher-level win of consolidated class syntax -- provided we can agree on class-method sections, class-side inheritance, and the other open issues.

# Allen Wirfs-Brock (13 years ago)

On Aug 5, 2011, at 7:46 AM, Brendan Eich wrote:

On Aug 4, 2011, at 11:00 PM, Allen Wirfs-Brock wrote:

On Aug 4, 2011, at 8:39 PM, Axel Rauschmayer wrote:

Another idea was an alternative way to express the "extend" operator for literal property definitions. Doug Crockford suggested the following syntax:

obj.{
   prop:1,
   prop:2
   }

It looks nice, but the operator should also work for a RHS that is not an object literal.

But using . as the operator it would be ambiguous with normal property access. A different and unique operator wouldn't have the problem but also wouldn't look so nice and the primary motivation for the operator is for use in expression such this post is about.

Still, unliked <|, an extend function (built-in module export) would be useful in more cases, based on Prototype and other libraries' Object.extend.

Agreed, we would still want a functional form such as Object.extend.

An alternative that could work would be to allow a parenthesized expression to follow the dot.

ECMA-357 (E4X) uses that for something we want to get rid of (filtering predicates, 'with' like things). So a.(b) as Object.extend(a, b) is possible, but again there is not strong need for an operator.

Using a.{p1:v1,...} syntax is attractive in its own right, but not as a final syntax in lieu of classes.

How about rewriting this as follows? I did something similar in my first go at this, but there is a problem...

Right, the desugaring from class syntax is fragile and error-prone.

I not so sure I agree. It isn't obvious that this code pattern is particularly any more fragile, error prone, or less toolable than some of the class syntax alternatives that have been discussed. Particular, if a programmer sets up an editor template that inserts: const classname = subclass <| function ( ) { super.constructor(); this.{

}; }.prototype.{

}.constructor.{

};

It isn't clear why this is necessarily any more fragile (or less temptable) than for example:

class classname extends superclass { constructor() { this. = ; }

prototype:

class:

}

or other forms of class declarations we have discussed.

It's clever, but it doesn't eliminate the higher-level win of consolidated class syntax -- provided we can agree on class-method sections, class-side inheritance, and the other open issues.

That agreement is the rub. As you captured in the day 2 meeting notes we are a long ways from agreement on those issues. And, some of the progress we have been making has the feel of committee compromises in order to make progress rather than strong cohesive design. Also, as dherman keeps reminding us, time is short if we want to make our 2013 target.

The simple extensions I'm proposing supports a straight forward coding pattern that address all of the above issues without irrecoverably baking into the language a complex syntactic form that locks in those decisions.

JS programmers were able to create innovative and useful support for both classical and prototypal inheritance abstraction using ES1-3 even though the language support for doing so was weak and incomplete. ES5 provided improved support for creating such abstraction patterns but is still missing some key primitive capabilities. We are very close to agreement on filling in all the known remaining primitive holes for ES.next. In particular, the key extensions I used in the collections experiment are:

  • modules and modules exports
  • the <| operator -- defines the [[Prototype]] of a literal
  • the .{ operator -- extends the LHS object with properties from an object literal
  • concise method properties in obj lits - foo() {} defines a non-enumerable function data property
  • super propertry references
  • private names created via Name.create
  • using [expr] in the propertyname position in an object literal, evaluate the expr to get the name

The collections experiment seems to be a pretty good demonstration that with these extensions we will have reached at least the power of Smalltalk for creating class-based abstractions. Given the availability of those extensions, who knows what sorts of innovative abstraction ideas will emerge from the same JS programmers who accomplished so much using just ES3. Rather than baking into ES.next a syntactic class abstraction that is largely inspired from static OO languages experience lets just make these primitives available and see what happens in the real world. If a single dominate abstraction pattern emerges then we can consider baking it into ES.next.next syntax.

# Bob Nystrom (13 years ago)

Particular, if a programmer sets up an editor template that inserts:

If we're OK with relying on tooling to type the boilerplate for us, I'm not sure there's much reason to add any new sugar at all. At that point, we could just set up the tools to insert:

function classname() { subclass.constructor.call(this);

}

But, personally, I'm not OK with leaning on tools. Sure the machine can type the boilerplate for me, but I still have to read it with my eyes. (And I read a lot more code that I write.) If I could have a tool that would read that boilerplate pattern back in and spit out something declarative for me to read, I'd be happy. But, then, that's pretty much exactly what we're talking about with a class proposal.

For example, here's what SkinnedMesh would look like with classes and sections:

class SkinnedMesh extends THREE.Matrix4.Mesh { constructor(geometry, materials) { super(geometry, materials);

  this.identity.Matrix = new THREE.Matrix4();
  this.bones = [];
  this.boneMatrices = [];
}

update(camera) {
  ...
  super(update);
}

class: default() { return new this(THREE.defaultGeometry, THREE.defaultMaterials); } };

I like the idea of giving users primitives and letting them build their own patterns (and I think that use case is important to support in the language). But for things where a pattern is already established, I really like letting users express that directly in a notation that's easy on the eyes. To me, the above is pretty good for that.

And, some of the progress we have been making has the feel of committee

compromises in order to make progress rather than strong cohesive design.

That's unfortunate. :(

A few of the class proposals have felt like fairly solid cohesive designs to me, but I may have a different aesthetic here. My hunch is that cohesive to you (and others) means something like "a small number of orthogonal primitives that I can combine to get a combinatorial number of different usable configurations".

It's like magnetic poetry, where you can arrange the words in an infinite number of combinations. Most of the class proposals aren't in that vein. They're closer to mad-libs: you can fill in the blanks but the structure is pretty fixed.

The question then is should we add a feature like this that isn't that open-ended? My belief is that it in this case we should. It's been said that design patterns show a lack in the language. With ES, "classes" are a very common pattern, and if we enable the language to acknowledge that, we can make our users' lives easier.

Given the availability of those extensions, who knows what sorts of

innovative abstraction ideas will emerge from the same JS programmers who accomplished so much using just ES3.

I don't think this is necessarily an either/or choice. We can have both more primitive object extension operators like <| that you propose to serve the avant garde who want to build novel ideas from scratch while also providing a declarative form or two for the well-trod middle ground where it's clear that many users are expressing the same thing over and over again.

To me, those two work in harmony with each other. If I've got a terse declarative syntax for common stuff, I can spend less time reading and writing that boring boilerplate and have more time to spend using <| and other features to express the things that are unique to my app. As it is now, I spend most of my time just wading through MyClassThing.prototype.someProperty = ...

Possibly more importantly, if we bake classes and an inheritance scheme into the language, then it becomes the standard. That makes re-use easier. Long analogy...

In C++, I always found library re-use a nightmare. Library A would use smart pointers, but I was using my own ref-counting. Library B wanted me to managed its objects' memory but library C expected to manage itself. Library D wanted an allocator, Library E wanted to use its own.

Java just said "you get GC, take it or leave it". People said, "but you can implement your own GC in C++! Java isn't flexible!" And that's true. Memory management got pretty much taken out of your hands. But it also solved a ton of library interop problems. Now I could just pass objects to and from different libraries easily and re-use got much better.

With classes in JS, I think we have a similar opportunity. If we provide a default way to define and inherit classes, I'll be able to have my code inherit from classes in libraries A, B, C without having to worry about whether they do Dojo-style or Closure-style, or whatever.

Rather than baking into ES.next a syntactic class abstraction that is

largely inspired from static OO languages experience lets just make these primitives available and see what happens in the real world. If a single dominate abstraction pattern emerges then we can consider baking it into ES.next.next syntax.

Haven't we done that with ES1-3 already? Users have had 15 years of prototypes and what they chose to do was make classes. We've got Closure's goog.extends(), Dojo's dojo.declare(), Prototype's Class.create() (ironic, given the name of the framework!), JS.Class, and CoffeeScript's class syntax. How many more cows do we need before that path is ready for paving?

# Brendan Eich (13 years ago)

On Aug 5, 2011, at 9:32 AM, Allen Wirfs-Brock wrote:

I did something similar in my first go at this, but there is a problem...

Right, the desugaring from class syntax is fragile and error-prone.

I not so sure I agree. It isn't obvious that this code pattern is particularly any more fragile, error prone, or less toolable than some of the class syntax alternatives that have been discussed. Particular, if a programmer sets up an editor template that inserts: const classname = subclass <| function ( ) { super.constructor(); this.{

}; }.prototype.{

}.constructor.{

};

Let's count the hazards and costs:

  1. No hoisted binding.

  2. Boilerplate overhead of "const D = B <| function" vs. "class D extends B": 1+2+8=11 vs. 7 (ignoring spaces). Arrow function syntax helps but you still end up with <|...-> cussing.

  3. As you already mentioned contra Axel, one cannot transpose .prototype. and .constructor. parts. Worse, if you don't have class methods, you have to end with a runt .constructor followed by a ;.

  4. Braces required instead of no braces for section labels.

It isn't clear why this is necessarily any more fragile (or less temptable) than for example:

class classname extends superclass { constructor() { this. = ; }

prototype:

class:

}

Really? Item (3) is a big hazard. Readability is harmed by the cussing and bracing. The class syntax may not be the best sugar, but it is sweeter and (due to the order requirement of .prototype. before .constructor., and the mandatory .constructor at the end if you don't have class methods) safer.

# Douglas Crockford (13 years ago)

On 11:59 AM, Bob Nystrom wrote:

Haven't we done that with ES1-3 already? Users have had 15 years of prototypes and what they chose to do was make classes. We've got Closure's goog.extends(), Dojo's dojo.declare(), Prototype's Class.create() (ironic, given the name of the framework!), JS.Class, and CoffeeScript's class syntax. How many more cows do we need before that path is ready for paving?

This doesn't look like a path at all. The cows are all over the place. If we saw a lot of convergence, then I might say you have a good point. If you can convince Closure, Dojo,Prototype, and the others to all do it in the Same Right Way, then there would be a strong case to say that ought to go into the language.

There is a difference between a cow path and a field of cows. We should not attempt to pave the whole field, and we should be careful about following too closely behind any one cow.

# Brendan Eich (13 years ago)

On Aug 5, 2011, at 11:43 AM, Bob Nystrom wrote:

Haven't we done that with ES1-3 already? Users have had 15 years of prototypes and what they chose to do was make classes. We've got Closure's goog.extends(), Dojo's dojo.declare(), Prototype's Class.create() (ironic, given the name of the framework!), JS.Class, and CoffeeScript's class syntax. How many more cows do we need before that path is ready for paving?

I agree with this. We just need to avoid false "agreement" on syntax that doesn't actually work (is not symmetric or strongly analogical), and that is usable. And of course we need to agree on semantics that are already in the language (internally or, with <| as Allen has proposed, in ES.next), specifically class-side inheritance.

Leaving things out can help, but it risks making too stunted a proposal. Adding too much is obviously bad, and Mark already took out the traits option.

Again, it seems to me the meta-problem with classes is that no one champion is actively working the syntax and semantics to reduce appropriately, user-test, and otherwise to resolve the open issues. So, are you able to spend time editing the proposal?

# Allen Wirfs-Brock (13 years ago)

On Aug 5, 2011, at 12:14 PM, Brendan Eich wrote:

On Aug 5, 2011, at 9:32 AM, Allen Wirfs-Brock wrote:

I did something similar in my first go at this, but there is a problem...

Right, the desugaring from class syntax is fragile and error-prone.

I not so sure I agree. It isn't obvious that this code pattern is particularly any more fragile, error prone, or less toolable than some of the class syntax alternatives that have been discussed. Particular, if a programmer sets up an editor template that inserts: const classname = subclass <| function ( ) { super.constructor(); this.{

}; }.prototype.{

}.constructor.{

};

Let's count the hazards and costs:

  1. No hoisted binding.

I think hoisting classes may be problematic anyway. Hoisting really isn't need to make forward class references from within method bodies work. Outside of methods bodies hoisting allows oddities such as:

class First { class: partner: new Second(); //or what ever a data property definition looks like in a class def defaultInstance: new First; //is this legal }

class Second{ constructor() { this.friend = First.defaultInstance; } }

or even inheritance circularities. It isn't obvious to me that there is any actual utility in hoisting class declarations.

  1. Boilerplate overhead of "const D = B <| function" vs. "class D extends B": 1+2+8=11 vs. 7 (ignoring spaces). Arrow function syntax helps but you still end up with <|...-> cussing.

I'd argue that the verboseness of "function" is largely an orthogonal issue. Because <| requires (currently) a literal on the RHS the "function" keyword could conceivably be dropped in that context. However, I don't think I'd want to advocate for that. In the end, I don't that 4 does 4 characters would be a significant issue for anybody.

(BTW, does it bother anybody that the meaning of "extend" to mean "subclass of" in such class declarations is quite different from what seems to be the most common meaning (add properties to an object) of a function named "extend" in JS libraries and frameworks.)

  1. As you already mentioned contra Axel, one cannot transpose .prototype. and .constructor. parts. Worse, if you don't have class methods, you have to end with a runt .constructor followed by a ;.

My argument is that once you learn the pattern this isn't an issue. Certainly, I didn't find my self having any issues with this when writing the collection code. But, if this is really a work for you, here is a possible solution. Define this function:

function Class(anObj) { if (anObj.hasOwnProperty('constructor)) return anObj.constructor; return anObj; }

You could even put it as an export into a built-in module.

You can then say: const C=Class(A <| function B() { ... }.prototype.{ });

or put the constructor methods before the prototype methods if you prefer).

  1. Braces required instead of no braces for section labels.

I agree that there is a small cost associated with composeable primitives. But also that the composeability is worth the cost.

It isn't clear why this is necessarily any more fragile (or less temptable) than for example:

class classname extends superclass { constructor() { this. = ; }

prototype:

class:

}

Really? Item (3) is a big hazard. Readability is harmed by the cussing and bracing. The class syntax may not be the best sugar, but it is sweeter and (due to the order requirement of .prototype. before .constructor., and the mandatory .constructor at the end if you don't have class methods) safer.

I think the Class function I suggest above could be an answer. But exploring this was my motivation in writing a significant amount of code using my pattern. I personally didn't find it awkward, error-prone, or to present any readability issues. But that is all subjective. If somebody should translated that corpus into class declarations we can do a side-by-side comparison of readability.

# Bob Nystrom (13 years ago)

On Fri, Aug 5, 2011 at 3:29 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

I think the Class function I suggest above could be an answer. But exploring this was my motivation in writing a significant amount of code using my pattern. I personally didn't find it awkward, error-prone, or to present any readability issues. But that is all subjective. If somebody should translated that corpus into class declarations we can do a side-by-side comparison of readability.

Brilliant idea. I started doing that heremunificent/ESnext-experiments.

Example:

// Define a non-constructible superclass that provides some Smalltalk-like // conventions. class AbstractClass { name = "AbstractClass";

get class() { return this.constructor; }

error(message) { throw new Error(message); }

subclassResponsibility() { this.class.subclassResponsibility(); }
shouldNotImplement() { this.class.shouldNotImplement(); }

errorSubscriptBounds(index) {
  this.error("subscript is out of bounds: " + index);
}

class: subclassResponsibility() { throw new Error(this.name + " did not implemente an abstract method."); }

shouldNotImplement() {
  throw new Error(this.name + " should not implemented by a method");
}

}

# Andreas Rossberg (13 years ago)

On 4 August 2011 22:57, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Using these two new ideas and other active object literal enhancement proposals it is pretty easy to compose a class-like declaration.  For example the SkinnedMesh from the harmony:classes proposal can be code as:   const SkinnedMesh = THREE.Matrix4.Mesh <| function(geometry,materials){     super.construtor(geometry,materials);     this.{       identity.Matrix: new THREE.Matrix4(),       bones: [],       boneMatrices: []     };   }.prototype.{     update(camera) {       ...       super(update);     }   }.constructor.{     default(){       return new this(THREE.defaultGeometry,THREE.defaultMaterials);     }   };

Sorry for chiming in late, but I don't understand this example. My understanding so far was that <| takes a literal on its right-hand side. But that doesn't seem to be the case here. So what is the intended semantics? Does <| mutate the rhs object (that would make it equivalent to exposing mutable proto, which seems bad)? Or does it copy the object (then how is the copy defined)?

Or do you consider <| to take precedence over .{}? In that case, the example wouldn't make sense, because <| wouldn't see the prototype property.

# Allen Wirfs-Brock (13 years ago)

On Aug 8, 2011, at 3:39 AM, Andreas Rossberg wrote:

On 4 August 2011 22:57, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Using these two new ideas and other active object literal enhancement proposals it is pretty easy to compose a class-like declaration. For example the SkinnedMesh from the harmony:classes proposal can be code as: const SkinnedMesh = THREE.Matrix4.Mesh <| function(geometry,materials){ super.construtor(geometry,materials); this.{ identity.Matrix: new THREE.Matrix4(), bones: [], boneMatrices: [] }; }.prototype.{ update(camera) { ... super(update); } }.constructor.{ default(){ return new this(THREE.defaultGeometry,THREE.defaultMaterials); } };

Sorry for chiming in late, but I don't understand this example. My understanding so far was that <| takes a literal on its right-hand side.

In this case the literal is the FunctionExpression

But that doesn't seem to be the case here. So what is the intended semantics? Does <| mutate the rhs object (that would make it equivalent to exposing mutable proto, which seems bad)? Or does it copy the object (then how is the copy defined)?

Your original understand is correct <| causes the "literal" (in reality, an dynamic object generator) on its RHS to create its object using the LHS as its [[Prototype]] value.

Or do you consider <| to take precedence over .{}?

<| and . have the same precedence and are left associate.

In that case, the example wouldn't make sense, because <| wouldn't see the prototype property.

You can think of the above expression as evaluating in the following steps.

  1. THREE.Matrix4.Mesh ; evacuate THREE.Matrix4.Mesh (I think the 2nd . is a typo, but it really doesn't matter for the example)
  2. <| function() {}: Create a function object whose [[Prototype]] is the result of step 1. That object has a "prototype" property whose value is a new object whose [[Prototype]] is the value of the "prototype" property of step 1. The prototype object has a "constructor" property whose value is the function object. The value of this step is the function object.
  3. .prototype: access the value the "prototype" property of the step 2 result
  4. .{...}: extend the object that is the result of step 3 by adding to it the methods defined by the object literal. These become properties of the prototype The value of this step is the same as the value of step 3.
  5. .constructor: access the value of the "constructor" property of the step 4 result. This will be the original function object from step 2.
  6. .{...}: extend the object that is the result of step 5 by adding to it the methods defined by the object literal. These become properties of the constructor function (ie, the "class") The value of this step is the same as the value of step 5.
  7. =: bind the result of step 6 as the value of the const SkinnedMesh. This is now essentially a named class.

While these are logically independent runtime steps, the "shape" of the prototype and constructor objects are inherent in the object literals and should be statically apparent to the compiler.

# Allen Wirfs-Brock (13 years ago)

On Aug 5, 2011, at 4:15 PM, Bob Nystrom wrote:

On Fri, Aug 5, 2011 at 3:29 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: I think the Class function I suggest above could be an answer. But exploring this was my motivation in writing a significant amount of code using my pattern. I personally didn't find it awkward, error-prone, or to present any readability issues. But that is all subjective. If somebody should translated that corpus into class declarations we can do a side-by-side comparison of readability.

Brilliant idea. I started doing that here. Example:

This is a great start Bob.

A couple things I noticed with your version:

In AbstractClass, you moved the name property from the class side to the instance side. I assume this was unintentional.

We should probably try to use the same formatting and whitespace conventions across all the versions we want to compare. Currently I find that those differences between the two versions are more noticeable then the actual substantive syntax differences.

# Brendan Eich (13 years ago)

On Aug 5, 2011, at 3:29 PM, Allen Wirfs-Brock wrote:

On Aug 5, 2011, at 12:14 PM, Brendan Eich wrote:

On Aug 5, 2011, at 9:32 AM, Allen Wirfs-Brock wrote:

I did something similar in my first go at this, but there is a problem...

Right, the desugaring from class syntax is fragile and error-prone.

I not so sure I agree. It isn't obvious that this code pattern is particularly any more fragile, error prone, or less toolable than some of the class syntax alternatives that have been discussed. Particular, if a programmer sets up an editor template that inserts: const classname = subclass <| function ( ) { super.constructor(); this.{

}; }.prototype.{

}.constructor.{

};

Let's count the hazards and costs:

  1. No hoisted binding.

I think hoisting classes may be problematic anyway.

That would be surprising, if true, since classes are sugar for prototypal inheritance based on function declarations, which hoist.

Hoisting really isn't need to make forward class references from within method bodies work. Outside of methods bodies hoisting allows oddities such as:

class First { class: partner: new Second(); //or what ever a data property definition looks like in a class def defaultInstance: new First; //is this legal }

Why is this a problem? Let's desugar this to function declaration syntax:

function First() { } First.partner = new Second; First.defaultInstance = new First;

class Second{ constructor() { this.friend = First.defaultInstance; } }

function Second() { this.friend = First.defaultInstance; }

This all works fine thanks to hoisting. Without hoisting, it does not work.

or even inheritance circularities.

Circularities are errors in any system.

It isn't obvious to me that there is any actual utility in hoisting class declarations.

It's the same utility for hoisting function declarations: letrec-style binding for mutual recursion, and in general, freedom to use top-down or any order when declaring functions forming a callgraph, instead of having to topologically sort based on caller->callee relation and write function declarations in reverse order.

  1. Boilerplate overhead of "const D = B <| function" vs. "class D extends B": 1+2+8=11 vs. 7 (ignoring spaces). Arrow function syntax helps but you still end up with <|...-> cussing.

I'd argue that the verboseness of "function" is largely an orthogonal issue. Because <| requires (currently) a literal on the RHS the "function" keyword could conceivably be dropped in that context. However, I don't think I'd want to advocate for that. In the end, I don't that 4 does 4 characters would be a significant issue for anybody.

I tried to separate the function shorthand issue in the cited bit above about Arrow function syntax.

Characters count, both degree (even 4) and kind (cussing, i.e., punctuation).

(BTW, does it bother anybody that the meaning of "extend" to mean "subclass of" in such class declarations is quite different from what seems to be the most common meaning (add properties to an object) of a function named "extend" in JS libraries and frameworks.)

Slightly. The words differ. Class syntax uses 'extends', Prototype et al. use 'extend'.

Extension by copying vs. extension by delegation are two varieties of extension. Some call the copying form "composition".

Indeed classes with traits in heritage clauses have been proposed to use 'extends', same as for the case of a superclass. Only for the case of a super-prototype was 'prototype' proposed as the alternative linking keyword.

Perhaps we should use a different contextual keyword, but it's a close call at best. Worst case, it's actually better to use 'extends' and we shouldn't mess with existing terminology in either place (class heritage clause, Object.extend imperative API).

  1. As you already mentioned contra Axel, one cannot transpose .prototype. and .constructor. parts. Worse, if you don't have class methods, you have to end with a runt .constructor followed by a ;.

My argument is that once you learn the pattern this isn't an issue.

My argument is simply that class syntactic sugar can be considered on top of <| and .{ -- there is no "exclusive OR" mandate here. Could be "AND also" ;-).

# Allen Wirfs-Brock (13 years ago)

On Aug 8, 2011, at 1:42 PM, Brendan Eich wrote:

On Aug 5, 2011, at 3:29 PM, Allen Wirfs-Brock wrote:

I think hoisting classes may be problematic anyway.

That would be surprising, if true, since classes are sugar for prototypal inheritance based on function declarations, which hoist.

Let me try a more carefully constructed example:

class First() { class: partner = (new Second).getPartner(); competitor = (new Second).getCompetitor(); } class Second() { constructor() { this.getCompetitor = function() {/whatever/*} } getPartner() {/whatever/}} }

which viewed using the obvious desugaring with hosting ould presumably be:

function First() {} function Second() {ihis.getCompetitor = function() {/whatever/*}} ... First.partner = (new Second).getPartner(); //but getPartner is not defined yet First. competitor = (new Second).getCompetitor(); //but getCompetitor is ok Second.prototype.getPartner = function() {/whatever/}}

Hosting of function works for resolving references from within function bodies. But individual property initialization expressions for a class are evaluated in order and can have order dependencies. Function declarations don't have any corresponding parts that expose such dependencies but classes do. Hosting does not eliminate those dependencies.

How do you simply explain why the initialization of competitor works but the initialization but partner does not with out explicitly getting into the concept of de-sugaring which arguably is a difficult concept for unsophisticated programmers. It's seems much easier to to have a simple rule: must be defined before executed. This doesn't really change anything for references nested inside function bodies but by this rule, confusing things like the above example would be invalid. If you really wanted to do that you would have to manually decompose it. I'd be fine with that.

...

or even inheritance circularities.

Circularities are errors in any system.

Yes, but by my rule above such a circularity would be an immediate name resolution error. With class hoisting you have to actually analyze the inheritance chains to detect the error

It isn't obvious to me that there is any actual utility in hoisting class declarations.

It's the same utility for hoisting function declarations: letrec-style binding for mutual recursion, and in general, freedom to use top-down or any order when declaring functions forming a callgraph, instead of having to topologically sort based on caller->callee relation and write function declarations in reverse order.

The name resolution rules we agreed to permit mutually recursive functions without hoisting (or you could view those rules as hoisting everything):

let f1 = function() {f2()}; let f2 = function() {f1()}; f1(); //happily recurs until it it runs out of stack

and hoisting doesn't prevent this sort of error with tail-end function declarations:

f1(); //error because foo not yet initialized; ... let foo="foo" function f1() {print(foo)};

The above is essentially the situation that occurs with property initialization expressions.

(BTW, does it bother anybody that the meaning of "extend" to mean "subclass of" in such class declarations is quite different from what seems to be the most common meaning (add properties to an object) of a function named "extend" in JS libraries and frameworks.)

Slightly. The words differ. Class syntax uses 'extends', Prototype et al. use 'extend'.

Extension by copying vs. extension by delegation are two varieties of extension. Some call the copying form "composition".

I still think the difference is likely to create confusion and I see no need to follow Java in this choice of keyword. "subs" or "subclass" might be a possibility. Or possibly: class superclass <| subclass () { }

  1. As you already mentioned contra Axel, one cannot transpose .prototype. and .constructor. parts. Worse, if you don't have class methods, you have to end with a runt .constructor followed by a ;.

My argument is that once you learn the pattern this isn't an issue.

My argument is simply that class syntactic sugar can be considered on top of <| and .{ -- there is no "exclusive OR" mandate here. Could be "AND also" ;-).

My main concern is baking time for getting more complex syntactic sugar (which, BTW, often ends up being more than just sugar) right. The hoisting issues above are an example of the the sort of complexity that need to be worried about. Simpler constructors like <| and .{ are easier to get right and probably have lower long term impact if we get some detail wrong.

# Brendan Eich (13 years ago)

On Aug 8, 2011, at 3:14 PM, Allen Wirfs-Brock wrote:

Let me try a more carefully constructed example:

class First() { class: partner = (new Second).getPartner();

Sure, this isn't going to work unless Second is declared first.

How do you simply explain why the initialization of competitor works but the initialization but partner does not with out explicitly getting into the concept of de-sugaring which arguably is a difficult concept for unsophisticated programmers. It's seems much easier to to have a simple rule: must be defined before executed. This doesn't really change anything for references nested inside function bodies but by this rule, confusing things like the above example would be invalid. If you really wanted to do that you would have to manually decompose it. I'd be fine with that.

One cannot explain classes without desugaring, but you're right that not hoisting makes the explanation simpler. CoffeeScript does not hoist its translations. Still, I thought I heard someone say class declarations hoist their bindings. Maybe I misheard -- Bob and Mark should weigh in.

Extension by copying vs. extension by delegation are two varieties of extension. Some call the copying form "composition".

I still think the difference is likely to create confusion and I see no need to follow Java in this choice of keyword. "subs"

No, that's an ugly and unusual contraction. In a language with instanceof and function, it is important to match impedance.

or "subclass" might be a possibility. Or possibly: class superclass <| subclass () { }

That's so backward from other binding forms, and from class in other languages, that it won't fly. If you turned the triangle around, that would be progress.

My argument is simply that class syntactic sugar can be considered on top of <| and .{ -- there is no "exclusive OR" mandate here. Could be "AND also" ;-).

My main concern is baking time for getting more complex syntactic sugar (which, BTW, often ends up being more than just sugar) right. The hoisting issues above are an example of the the sort of complexity that need to be worried about. Simpler constructors like <| and .{ are easier to get right and probably have lower long term impact if we get some detail wrong.

Yes, but if we do too little the usability is poor and users won't bother.

I'm hopeful Bob will champion some usable syntax with desugared semantics we can all agree on.

# Allen Wirfs-Brock (13 years ago)

On Aug 8, 2011, at 9:24 PM, Brendan Eich wrote:

On Aug 8, 2011, at 3:14 PM, Allen Wirfs-Brock wrote:

Let me try a more carefully constructed example:

class First() { class: partner = (new Second).getPartner();

Sure, this isn't going to work unless Second is declared first.

And Second could be dependent upon First in the same way so reordering doesn't necessarily solve the problem.

How do you simply explain why the initialization of competitor works but the initialization but partner does not with out explicitly getting into the concept of de-sugaring which arguably is a difficult concept for unsophisticated programmers. It's seems much easier to to have a simple rule: must be defined before executed. This doesn't really change anything for references nested inside function bodies but by this rule, confusing things like the above example would be invalid. If you really wanted to do that you would have to manually decompose it. I'd be fine with that.

One cannot explain classes without desugaring,

Actually, this is one of my concerns about class declarations. As a major declarative form in the language many users will attempt to understand them as an atomic unit. It will be a problem if they cannot be understand them without desugaring them into something else.

but you're right that not hoisting makes the explanation simpler. CoffeeScript does not hoist its translations. Still, I thought I heard someone say class declarations hoist their bindings. Maybe I misheard -- Bob and Mark should weigh in.

I think it was probably just assumed (including by me) without fully working through the implications of that assumption.

Extension by copying vs. extension by delegation are two varieties of extension. Some call the copying form "composition".

I still think the difference is likely to create confusion and I see no need to follow Java in this choice of keyword. "subs"

No, that's an ugly and unusual contraction. In a language with instanceof and function, it is important to match impedance.

Wasn't really seriously, just throwing a bone to character counters ;-)

or "subclass" might be a possibility. Or possibly:

really meant "subclasses" but "subclassof" would certainly be following in the footsteps of "instanceof"

class superclass <| subclass () { }

That's so backward from other binding forms,

we don't really have a comparable binding form in ES. The closest comparable are function declarations and they don't really provide any guidance for this problem.

 class boundSymbol : superclass () { }

might work although, it may be confusing different from property definitions.

and from class in other languages, that it won't fly.

If required to choose I would favor internal consistency over similarity to other languages.

If you turned the triangle around, that would be progress.

Yes, but there are really good arguments the <| ordering, for example: protoValue <| { ... ... }

rather than

 {
    ...
    ...
 } |> protoValue

and similarly for functions and others multiple line literals. class superclass <| subclass () {} would be more internally consistent with that and with the desugaring if you want to go that route of explanation. May be: class subclass = superclass <| () { } would be even more consistent with: let subclass = superclass <| function() {}; which means exactly the same thing in the simplest cases.

My argument is simply that class syntactic sugar can be considered on top of <| and .{ -- there is no "exclusive OR" mandate here. Could be "AND also" ;-).

My main concern is baking time for getting more complex syntactic sugar (which, BTW, often ends up being more than just sugar) right. The hoisting issues above are an example of the the sort of complexity that need to be worried about. Simpler constructors like <| and .{ are easier to get right and probably have lower long term impact if we get some detail wrong.

Yes, but if we do too little the usability is poor and users won't bother.

Yes, but we have no real evidence one way or the other that the usability of <| and .{ is poor. Mostly just assertions based upon minimal use.

# Brendan Eich (13 years ago)

On Aug 8, 2011, at 10:24 PM, Allen Wirfs-Brock wrote:

class superclass <| subclass () { }

That's so backward from other binding forms, we don't really have a comparable binding form in ES. The closest comparable are function declarations and they don't really provide any guidance for this problem.

But class declarations in Python, Ruby, and other languages do provide guidance. None has this super < subclass ordering.

class boundSymbol : superclass () { }

might work although, it may be confusing different from property definitions.

And guards. The colon character was used this way in some earlier drafts of classes proposals, but extends is already reserved and it's in the current draft. I don't see a good argument to replace it with something, but perhaps one will be made.

and from class in other languages, that it won't fly.

If required to choose I would favor internal consistency over similarity to other languages.

There is nothing "internally consistent" about adding a binding form where the bound identifier does not immediately folllow the introductory keyword.

If you turned the triangle around, that would be progress.

Yes, but there are really good arguments the <| ordering, for example:

Sure, I was not seriously recommending |>.

Yes, but we have no real evidence one way or the other that the usability of <| and .{ is poor. Mostly just assertions based upon minimal use.

The usability of <| and .prototype. and the final .constructor (with .{ ... } or a ; after) is manifestly poor for defining a whole class. This is a no-brainer. Axel already got the order wrong!

# Claus Reinke (13 years ago)

The name resolution rules we agreed to permit mutually recursive functions without hoisting (or you could view those rules as hoisting everything):

let f1 = function() {f2()}; let f2 = function() {f1()}; f1(); //happily recurs until it it runs out of stack

Sorry, I seem to have missed that discussion - are those new rules documented somewhere? I had some concerns about the old rules, and would like to check whether those are resolved.

and hoisting doesn't prevent this sort of error with tail-end function declarations:

f1(); //error because foo not yet initialized; ... let foo="foo" function f1() {print(foo)};

Indeed, that is the main flaw in the idea of using hoisting as a substitute for letrec: mixing declarative and imperative forms. I've been tempted to suggest that function declarations are hoisted only within their group of declarations. In other words, in code like this

function A() {..}
function B() {..}
doSomething();
var X = ..;
function C() {..}
function D() {..X..}

A/B could be mutually recursive, and C/D could, but A/B could not forward reference C/D. That would still allow hoisting to emulate

letrec
    <mutually recursive fundefs>
in <code>

and it would avoid the trap of hoisting D outside the validity of X. As a downside, one could no longer use hoisting to emulate

<code>
where
    <mutually recursive fundefs>

Claus

# Claus Reinke (13 years ago)

.. the key extensions I used in the collections experiment are: ..

  • the <| operator -- defines the [[Prototype]] of a literal
  • the .{ operator -- extends the LHS object with properties from an object literal

How do these two relate to (non-destructive) object extension operators [1]? The syntax mentioned there would line up with array spreads:

{ properties , ...: obj }
 // returns copy of obj, extended with properties

If that is the same idea, then it is something I'd like to see [2]. Both syntax variations share the limitation to literal extension properties, the spread variation is more conventional and tends to be suitable for both construction and destructuring (also, one could write the extended object at the front or at the back), so it might be preferable?

It seems that <| is merely a special case, aiming to avoid naming [[Prototype]], right?

Claus

[1] harmony:destructuring#issues [2] esdiscuss/2011-June/015475

# Allen Wirfs-Brock (13 years ago)

On Aug 9, 2011, at 1:18 AM, Claus Reinke wrote:

The name resolution rules we agreed to permit mutually recursive functions without hoisting (or you could view those rules as hoisting everything):

let f1 = function() {f2()}; let f2 = function() {f1()}; f1(); //happily recurs until it it runs out of stack

Sorry, I seem to have missed that discussion - are those new rules documented somewhere? I had some concerns about the old rules, and would like to check whether those are resolved.

"July TC39 meeting notes, day 2" esdiscuss/2011-August/016188

# Bob Nystrom (13 years ago)

On Mon, Aug 8, 2011 at 3:14 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

function First() {} function Second() {ihis.getCompetitor = function() {/whatever/*}} ... First.partner = (new Second).getPartner(); //but getPartner is not defined yet First. competitor = (new Second).getCompetitor(); //but getCompetitor is ok Second.prototype.getPartner = function() {/whatever/}}

Hosting of function works for resolving references from within function bodies. But individual property initialization expressions for a class are evaluated in order and can have order dependencies. Function declarations don't have any corresponding parts that expose such dependencies but classes do. Hosting does not eliminate those dependencies.

All true, and it's good to make sure stuff like this is in the forefront of our minds.

How do you simply explain why the initialization of competitor works but the initialization but partner does not with out explicitly getting into the concept of de-sugaring which arguably is a difficult concept for unsophisticated programmers.

I think we'll want both explanations. For existing JavaScripters, an explanation of class syntax in terms of desugaring will help the map what they already know to a new form. But programmers coming to the language cold shouldn't have to learn the desugared form first in order to understand class syntax, so for them we'll want explanations that just explain the behavior directly.

It's seems much easier to to have a simple rule: must be defined before executed. This doesn't really change anything for references nested inside function bodies but by this rule, confusing things like the above example would be invalid. If you really wanted to do that you would have to manually decompose it. I'd be fine with that.

I'm coming to this thread a bit late, but that sounds reasonable to me. Mutual recursion of methods within a class is critical. Circular references between methods of different classes is also vital. Circular references of * constructor-level* properties seem less important to me. If a user really wants this:

class A { class: b = new B(); }

class B { class: a = new A(); }

I think it's reasonable to ask them to re-organize stuff a bit.

class A { }

class B { }

A.b = new B(); B.a = new A();

Do I understand the issue here, or am I missing some pieces?

Yes, but by my rule above such a circularity would be an immediate name resolution error. With class hoisting you have to actually analyze the inheritance chains to detect the error

How does this play with modules? If I have two classes declared in different modules (which I assume will a common case, if not the most common) then hoisting or not hoisting won't help either way will it?

I still think the difference is likely to create confusion and I see no need

to follow Java in this choice of keyword. "subs" or "subclass" might be a possibility. Or possibly: class superclass <| subclass () { }

It would deeply astonish people to have the base class come before the derived class in a class declaration. I didn't even notice that's what you were doing the first few times I read that. C++, C#, Java, Ruby, Python, Ada, Dylan, CLOS, Eiffel, Objective-C, and Scala are all subclass-first.

Smalltalk and Self are superclass (or parent in the case of Self) first, but that's a syntactic side-effect because subclassing is just a message send and the receiver is on the left. That isn't the case with the syntax we're proposing here, which is a more Algol-style keyword-first form.

My main concern is baking time for getting more complex syntactic sugar (which, BTW, often ends up being more than just sugar) right.

Baking time is a valid concern, and I think one we all share. The only solution I know of is to get the oven going as soon as we can.

I'm not personally as concerned about classes becoming more than syntactic sugar. JS already has the semantics needed to get something like classes working in a really friendly fashion, so adding new actual semantics would very likely overlap the existing ones in weird and nasty ways. Imagine trying to cram generic functions into Smalltalk while trying to retain the existing single-dispatch behavior too. You'd end up with a strange chimera at best, and more likely just a stuffed jackalope.

I think that alone will keep us honest. We can't jam in new dispatch semantics without making things really painful for ourselves.

The hoisting issues above are an example of the the sort of complexity that need to be worried about. Simpler constructors like <| and .{ are easier to get right and probably have lower long term impact if we get some detail wrong.

Simpler constructs are easier to get right, but I think only because they push the hard work onto our users. If we only give them <| and .{, they'll have to come up with their own patterns to accomplish what they want in terms of those. Those patterns will be just as hard to get right as it would be for us to design a syntax directly in the language. The only difference is that instead of us doing that hard work once (and I'd like to hope we're the best-qualified to do it!), they'll re-invent it over and over again.

I definitely understand the desire to punt on this, but I think we'd do our users a disservice if we did. I believe we can design a good declarative syntax for this and that if we do so, we'll make ES a language that's easier to read and easier to use.

# Allen Wirfs-Brock (13 years ago)

On Aug 9, 2011, at 10:23 AM, Bob Nystrom wrote:

On Mon, Aug 8, 2011 at 3:14 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: ...

I think it's reasonable to ask them to re-organize stuff a bit.

class A { }

class B { }

A.b = new B(); B.a = new A();

Do I understand the issue here, or am I missing some pieces?

It sounds like you got it.

Yes, but by my rule above such a circularity would be an immediate name resolution error. With class hoisting you have to actually analyze the inheritance chains to detect the error

How does this play with modules? If I have two classes declared in different modules (which I assume will a common case, if not the most common) then hoisting or not hoisting won't help either way will it?

Dave or Sam probably need to respond. I haven't yet deeply internalized the semantics for mutually referential modules in their proposal. However, in general, I believe that such modules have to be processed as a unit and that would imply some implicit ordering of declarations.

I still think the difference is likely to create confusion and I see no need to follow Java in this choice of keyword. "subs" or "subclass" might be a possibility. Or possibly: class superclass <| subclass () { }

I don't really like that either, but I do think there might be some merit to const subclass = class superclass <| () {/* your class body*/}; but that's for a different message.

It would deeply astonish people to have the base class come before the derived class in a class declaration. I didn't even notice that's what you were doing the first few times I read that. C++, C#, Java, Ruby, Python, Ada, Dylan, CLOS, Eiffel, Objective-C, and Scala are all subclass-first.

Smalltalk and Self are superclass (or parent in the case of Self) first, but that's a syntactic side-effect because subclassing is just a message send and the receiver is on the left. That isn't the case with the syntax we're proposing here, which is a more Algol-style keyword-first form.

My main concern is baking time for getting more complex syntactic sugar (which, BTW, often ends up being more than just sugar) right.

Baking time is a valid concern, and I think one we all share. The only solution I know of is to get the oven going as soon as we can.

I'm not personally as concerned about classes becoming more than syntactic sugar. JS already has the semantics needed to get something like classes working in a really friendly fashion, so adding new actual semantics would very likely overlap the existing ones in weird and nasty ways. Imagine trying to cram generic functions into Smalltalk while trying to retain the existing single-dispatch behavior too.

Been there, WRT, multiple inheritance. It wasn't pretty.

You'd end up with a strange chimera at best, and more likely just a stuffed jackalope.

I think future proofing is also an argument for why we might want to go slow with a comprehensive class declaration syntax. How confident are we that what we come up with would be suitable for adding multiple inheritance or traits/mix-ins or other plausible future extensions. We haven't even been able to agree on a declarative property visibility syntax. If we stop at the level of compositional class definition patterns like I have been proposing we have less risk of painting ourselves into a corner.

I think that alone will keep us honest. We can't jam in new dispatch semantics without making things really painful for ourselves.

The hoisting issues above are an example of the the sort of complexity that need to be worried about. Simpler constructors like <| and .{ are easier to get right and probably have lower long term impact if we get some detail wrong.

Simpler constructs are easier to get right, but I think only because they push the hard work onto our users. If we only give them <| and .{, they'll have to come up with their own patterns to accomplish what they want in terms of those. Those patterns will be just as hard to get right as it would be for us to design a syntax directly in the language. The only difference is that instead of us doing that hard work once (and I'd like to hope we're the best-qualified to do it!), they'll re-invent it over and over again.

But we shouldn't leave them to come up with their own patterns. We (I'm now speaking about us more as individuals rather than as TC39) should document and evangelize the recommend compositional pattern for defining "classes" in "ES.next"

I definitely understand the desire to punt on this, but I think we'd do our users a disservice if we did. I believe we can design a good declarative syntax for this and that if we do so, we'll make ES a language that's easier to read and easier to use.

Sure, we just have the normal schedule and complexity trade-offs that occur in any significant software project. I'm not saying that you shouldn't push on this. I'm more saying that it might not be a disaster if in the end it didn't make the cut, as long as we have the more compositional primitive (<|, super, private names, etc.) that allow synthesis of class-like abstraction patterns. I think it would be a bigger problem to not have those for ES.next.

# Allen Wirfs-Brock (13 years ago)

On Aug 9, 2011, at 1:29 AM, Claus Reinke wrote:

.. the key extensions I used in the collections experiment are: ..

  • the <| operator -- defines the [[Prototype]] of a literal
  • the .{ operator -- extends the LHS object with properties from an object literal

How do these two relate to (non-destructive) object extension operators [1]? The syntax mentioned there would line up with array spreads:

{ properties , ...: obj } // returns copy of obj, extended with properties

Currently, what is being specified for ES.next has [...obj] but not {...obj} or {... : obj}

obj.{a:1,b:2}

is equivalent to Object.defineProperties(obj,Object.getOwnPropertyDescriptors({a:1,b:2})) while (if it existed)

{... : obj, a:1, b:2}

would presumably mean something like: Object.create(Object.prototype,Object.getOwnPropertyDescriptors(obj)).defineProperties(Object.getOwnPropertyDescriptors({a:1,b:2}))

If that is the same idea, then it is something I'd like to see [2]. Both syntax variations share the limitation to literal extension properties, the spread variation is more conventional and tends to be suitable for both construction and destructuring (also, one could write the extended object at the front or at the back), so it might be preferable?

I had missed [2] when it first came by, so thanks for pointing it out. I generally agree that a complete set of compositional operations based upon copying seem useful. But for now, it seems more like work for the future that we possibly could try to future proof for.

It seems that <| is merely a special case, aiming to avoid naming [[Prototype]], right?

It is probably valid to look at it that way, but it is also intended to be a concise way to directly specify the [[Prototype]] for various literal forms.

I have defined it to be a copying operation (even though because the RHS is always a literal an actual copy is not required) with the expectation that someday we would extend it to allow non-literal expressions on the RHS. To do this, we would first need to define a semantics for shallow copy that deals with the various complexity of the language such including non-standard internal methods, host objects, proxies, etc. Also, a good shallow copy frame really needs to have hooks (pre-copy, post-copy, etc.) to allow application level invariants to be managed.

# Allen Wirfs-Brock (13 years ago)

On Aug 8, 2011, at 10:40 PM, Brendan Eich wrote:

On Aug 8, 2011, at 10:24 PM, Allen Wirfs-Brock wrote:

class superclass <| subclass () { }

That's so backward from other binding forms, we don't really have a comparable binding form in ES. The closest comparable are function declarations and they don't really provide any guidance for this problem.

But class declarations in Python, Ruby, and other languages do provide guidance. None has this super < subclass ordering.

As Bob pointed out in a later message Smalltalk and self do, but I'm not arguing from that perspective. I agree that the above is probably not a great binding form. But it might not be a bad literal form:

const subclass = class superclass <| () { /* class body */}

with a strong internal parallel to superclass <| function () {}

Is it really necessary to add new binding forms or can we simply get along with let and const. We Have already seen that there are issues with trying to hoist a class binding form. Perhaps we would be better off simply not having any additional binding forms.

...

Yes, but we have no real evidence one way or the other that the usability of <| and .{ is poor. Mostly just assertions based upon minimal use.

The usability of <| and .prototype. and the final .constructor (with .{ ... } or a ; after) is manifestly poor for defining a whole class. This is a no-brainer. Axel already got the order wrong!

And I got it wrong the first time I tried to write it up. But both Axel and I were experimenting with trying to find the right pattern rather than having been taught a specific pattern and how to use it. After I figured it out, I haven't had any problem in remembering the exact pattern as subsequently wrote quite a bit of code. Also, I also proposed the Class function to address that order issue, although I don't know whether I would actually use it. I generally don't find the exact pattern any more seemingly arbitrary or difficult to use or remember than the various class syntaxes. Try it, you may find it isn't so bad.

# Brendan Eich (13 years ago)

On Aug 9, 2011, at 1:18 AM, Claus Reinke wrote:

The name resolution rules we agreed to permit mutually recursive functions without hoisting (or you could view those rules as hoisting everything):

let f1 = function() {f2()}; let f2 = function() {f1()}; f1(); //happily recurs until it it runs out of stack

Sorry, I seem to have missed that discussion - are those new rules documented somewhere? I had some concerns about the old rules, and would like to check whether those are resolved.

See my day 2 notes:

esdiscuss/2011-August/016188

or here, with better formatting:

www.mail-archive.com/[email protected]/msg09188.html

Indeed, that is the main flaw in the idea of using hoisting as a substitute for letrec: mixing declarative and imperative forms. I've been tempted to suggest that function declarations are hoisted only within their group of declarations.

We are not breaking compatibility on this point in an opt-in future edition. It would just break incorrectly-migrated code at runtime.

# Brendan Eich (13 years ago)

On Aug 9, 2011, at 12:57 PM, Allen Wirfs-Brock wrote:

Is it really necessary to add new binding forms or can we simply get along with let and const. We Have already seen that there are issues with trying to hoist a class binding form. Perhaps we would be better off simply not having any additional binding forms.

If class is not a binding form then we are not sugaring prototypal patterns that use function declarations.

Specifically, we're therefore taxing the extremely common case of binding a class name, by requiring a leading let C = or const C = in front of a class expression. This is punitive, it wouldn't do for functions (even though some advise it as always better style to write var f = function(){} -- they're mistaken).

Rather, the harmony:classes proposal supports class declarations and expressions, parallel to functions. Which is why some of us expected the former to hoist, but that's not essential.

Yes, but we have no real evidence one way or the other that the usability of <| and .{ is poor. Mostly just assertions based upon minimal use.

The usability of <| and .prototype. and the final .constructor (with .{ ... } or a ; after) is manifestly poor for defining a whole class. This is a no-brainer. Axel already got the order wrong!

And I got it wrong the first time I tried to write it up. But both Axel and I were experimenting with trying to find the right pattern rather than having been taught a specific pattern and how to use it. After I figured it out, I haven't had any problem in remembering the exact pattern ...

Patterns cost. Peter Norvig's point about pattern-mongering being a sign of unfixed language bugs is one I agree with in general. There is a sunk cost fallacy in preferring patterns, especially ones with strung-out dots and punctuation and order requirements. This is ongoing cognitive load, scaled across all future users.

I do not think we're going to kill class syntax so easily.

# Axel Rauschmayer (13 years ago)

I find the following points remarkable about Allen’s proposal:

  • The <| operator creates prototype chains for both prototypes and constructors. This introduces an important symmetry.
  • The merge operator obj.{…} makes object literals usable in many more contexts.

So the consensus is to start with the simplest possible class literal? I don’t think we currently need much more than object literal syntax + sections + subclassing. The only thing that programmers might get wrong is initializing property values in the prototype while thinking that they have created instance properties. But as that is a common pattern for giving default values to instance properties, it shouldn’t cause any problems; instance properties will be created automatically upon assigning a value (as opposed to reading it).

In my mind, Allen has shown that such syntactic sugar can be very similar to something you build using only object literals. I find that very important – too much of a gap makes things hard to understand, once you look underneath the surface.

# Brendan Eich (13 years ago)

On Aug 10, 2011, at 7:28 AM, Axel Rauschmayer wrote:

So the consensus is to start with the simplest possible class literal? I don’t think we currently need much more than object literal syntax + sections + subclassing.

No, the class syntax includes a body which is not an object literal. If it were, comma separators would be mandatory between methods. That is bogus.

Class syntax has its own body plan.

The only thing that programmers might get wrong is initializing property values in the prototype while thinking that they have created instance properties. But as that is a common pattern for giving default values to instance properties, it shouldn’t cause any problems; instance properties will be created automatically upon assigning a value (as opposed to reading it).

This is not so, as Alex Russell has pointed out, due to mutable objects as prototype-property values. Such values are usually bug bait where unwanted sharing with mutation is the likely result.

In my mind, Allen has shown that such syntactic sugar can be very similar to something you build using only object literals. I find that very important – too much of a gap makes things hard to understand, once you look underneath the surface.

No argument there.

The issue is whether class syntax with appropriate semantics is worth adding too.

# Sam Tobin-Hochstadt (13 years ago)

On Tue, Aug 9, 2011 at 2:54 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Aug 9, 2011, at 10:23 AM, Bob Nystrom wrote:

On Mon, Aug 8, 2011 at 3:14 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Yes, but by my rule above such a circularity would be an immediate name resolution error.  With class hoisting you have to actually analyze the inheritance chains to detect the error

How does this play with modules? If I have two classes declared in different modules (which I assume will a common case, if not the most common) then hoisting or not hoisting won't help either way will it?

Dave or Sam probably need to respond.  I haven't yet deeply internalized the semantics for mutually referential  modules in their proposal.  However, in general, I believe that such modules have to be processed as a unit and that would imply some implicit ordering of declarations.

There are two different ways that the semantics of mutually referential modules matter. The first is name visibility. Here there shouldn't be any problems: the static semantics of such modules mean both classes are visible (in terms of names and any other static information) to each other. The second is when the execution of the class body happens. Here, the order is determined by the order that the modules are loaded, assuming that the classes are written at module top-level. The semantics [1] would then initialize each module variable to undefined (unless it's a function), and then run the module top-down.

For this particular case, modules won't make anything easier or harder than putting them in a the same module together. If that works properly, then modules will work (possibly depending on ordering the loads appropriately). If some pattern with classes doesn't work in one module, then spreading it across two modules won't change that, either.

[1] harmony:modules#run