Minor extension to Set Literal Prototype operator as minimal classes

# Russell Leggett (14 years ago)

I'm probably not the first to have thought of this, but it seemed like the most minimal class support I could think of, and it sounds like that's where things are headed from the lack of agreement. Through the additions made by the Set Literal Prototype operator proposal, I think you nailed 90% of the pain point for most developers. Nicer method syntax, simple extension functionality, and the addition of super seals the deal. The only thing missing is how to translate that set of functionality to work with "classes" instead of just objects. It seems so close, in fact, that the idea of doing a class hierarchy in a different way seems almost unintuitive.

My suggestion is simply that the <| operator get overloaded to work slightly differently if the LHS is a "class" instead of an object. The wiki defines the class definition pattern as:

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

I guess I just don't see why we can't just overload to do this instead:

const ClassName = SuperClass <| {
	constructor(/*constructor parameters */) {
	   //constructor body
	   super.constructor(/*arguments to super constructor */);
	   this.{
	     //per instance property definitions
	   }
	}
	method1(){ return super.method1(); }
	method2(){}
	prop1:"Properties unlikely, but allowed"
}.{
	//class properties
	staticMethod(){}
};

That would be sugar enough for me, and I think most javascript devs. It also means that no additional syntax is introduced beyond what already has been added to harmony.

# Axel Rauschmayer (14 years ago)
  const ClassName = SuperClass <| {
          constructor(/*constructor parameters */) {
             //constructor body
             super.constructor(/*arguments to super constructor */);
             this.{
               //per instance property definitions
             }
          }
          method1(){ return super.method1(); }
          method2(){}
          prop1:"Properties unlikely, but allowed"
  }.{
          //class properties
          staticMethod(){}
  };
  1. If a "constructor" method is supplied as part of the object literal, it will be used as the new constructor, and take the LHS constructor as its prototype. If not, an new empty constructor will be created instead and take the LHS constructor as its prototype.
  2. The rest of the object literal will be used as the new constructors prototype, and it will use the LHS's prototype as it's prototype.
  3. The result of the <| operand in my example would be the new class/constructor function, so it can be assigned to "const ClassName" or as in my example, the .{ operator can be used to add class methods and still return the correct result.

That would indeed work: If the lhs is a function and the rhs a non-function object, then the rhs is basically used as the prototype (including a nested constructor inside) of the “sub-class”.

However: What are the advantages over class literals? You would still need to introduce a new mechanism (the overloading of <|). My favorite solution for avoiding the introduction of a new mechanism is still Allen’s <| for two function plus Function.prototype.* methods that add class methods and prototype methods. But I like (minimal) class literals even more than that:

  class ClassName extends SuperClass {
          constructor(/*constructor parameters */) {
             //constructor body
             super.constructor(/*arguments to super constructor */);
             this.{
               //per instance property definitions
             }
          }
          method1(){ return super.method1(); }
          method2(){}
          prop1:"Properties unlikely, but allowed"
  class:
          //class properties
          staticMethod(){}
  };

Isn’t that the same in spirit as your proposal, but nicer to read? In both cases you introduce something new: Either a new overloading or minimal syntactic sugar.

# Russell Leggett (14 years ago)

I'm not opposed to the class literal as much as I was trying to think of the most minimal classes that could find some consensus. It seems from the discussion that if no syntax can be agreed on, then perhaps it would have to wait for ES.next.next.

The problem with the class literal syntax that you've turned my proposal into (and similar to many of the other proposals) is that it is yet another form with it's own syntax which must be agreed on and then later understood by developers etc. That may be worthwhile if there are significant benefits, but so far it sounds like nobody can agree on any additional features beyond what I propose other than some syntactic sugar. I would hate to see the whole thing get thrown out, so I guess I would say that maybe what I'm proposing is a backup plan.

One of the other things I find appealing about my proposal is that it is very clear that the {}s are for an object literal. That means it's contents are already well defined. At first glance, if I see "class Foo {" I might expect what follows to be like a module or function body. It also unifies the notion of extension to a single operator, instead of prototypes using <| and classes using extend.

On the other hand, perhaps we could flip the bit the other way, and unify on "extend" instead of <|. In scala, the is both a "class" and "object" syntax that is kind of similar to the situation we currently have creating objects with object literal notation and class literal notation. In scala, they would be:

object Foo extends Bar {...} and class Foo extends Bar {...}

Where object Foo defines a single object instead of a class. Perhaps instead of saying:

var sub = sup <| {...}

We should be using

object sub extends sup {...}

And then at least we would have some consistency between these similar concepts. I know that <| has more flexibility and other cases beyond this, but I think these are the most common.

# Axel Rauschmayer (14 years ago)

On the other hand, perhaps we could flip the bit the other way, and unify on "extend" instead of <|. In scala, the is both a "class" and "object" syntax that is kind of similar to the situation we currently have creating objects with object literal notation and class literal notation.

I know what you mean (it’s why I like “prototypes as classes”), but there is an asymmetry in JavaScript and I’ve made my peace with the fact that it won’t go away:

  1. Before an instance has been created, the closest analog of a class is the constructor.
  2. After an instance has been created, the closest analog of a class is the instance’s prototype.

Example: while you write obj instanceof MyClass, what you check is MyClass.prototype.isPrototypeOf(obj).

Both Allen’s <| operator for functions and the class literals do a very good job of hiding that asymmetry. Most programmers are used to having an explicit class construct (e.gl those coming from Python, PHP, Java, etc.). Class literals provide such a construct for JavaScript, but are still close enough to what we currently have to do manually that they do not go too much against the JavaScript grain.

What do you think about the following variation of Allen’s pattern?

const ClassName = SuperClass <| function(/*constructor parameters */) {
    //constructor body
    super.constructor(/*arguments to super constructor */);
    this.{
        // per-instance properties
    };
}.{
    prototype: {
        // methods
    },
    // class (ie, constructor) properties
}

Requirement: The .{} operator extends recursively if there is a name clash. Otherwise, the prototype cannot be extended this way.

# Allen Wirfs-Brock (14 years ago)

On Oct 1, 2011, at 8:23 PM, Axel Rauschmayer wrote:

...

What do you think about the following variation of Allen’s pattern?

const ClassName = SuperClass <| function(/*constructor parameters */) { //constructor body super.constructor(/*arguments to super constructor */); this.{ // per-instance properties }; }.{ prototype: { // methods }, // class (ie, constructor) properties }

Requirement: The .{} operator extends recursively if there is a name clash. Otherwise, the prototype cannot be extended this way.

+1

Your last requirement is the rub. As currently defined, prototype: { ...} would replace the value of the constructor's 'prototype' property which would mess up the implicit constructor/prototype wiring. Defining : to mean .{ if the property already exists seems fragile and precludes the possibility of using .{ to replace property values. But, what if we allow: propName.{ ...} to occur in a PropertyAssignment position of an object extension literal. Then the pattern could be:

const ClassName = SuperClass <| function(/*constructor parameters */) { //constructor body super.constructor(/*arguments to super constructor */); this.{ // per-instance properties }; }.{
prototype.{ // <<<<< note . instead of : // methods }, // class (ie, constructor) properties };

It's only a one character change but it eliminates the hazard that some people are concerned about of leaving off the .constructor or .constructor.{...} at the end of the pattern. Also, I can imagine that "recursive stache" (hey, it's inevitable that "mustache" will get contracted to "stache") would be useful in other situations. Personally, I find this new pattern not quite as "pretty" than my original proposal (it's less symmetric/ more irregular hence probably harder to learn and remember) but I'd still be happy to use it.

If we could actually agree to this, then we could move on to making sure that the new operator did the right thing for non-function object and then we really would have a complete set of primitives that nicely and interoperability supported both classical and prototypal inheritance abstractions.

# Russell Leggett (14 years ago)

On Sun, Oct 2, 2011 at 3:52 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

It's only a one character change but it eliminates the hazard that some people are concerned about of leaving off the .constructor or .constructor.{...} at the end of the pattern.  Also, I can imagine that "recursive stache"  (hey, it's inevitable that "mustache" will get contracted to "stache") would be useful in other situations. Personally, I find this new pattern not quite as "pretty" than my original proposal (it's less symmetric/ more irregular hence probably harder to learn and remember) but I'd still be happy to use it. If we could actually agree to this, then we could move on to making sure that the new operator did the right thing for non-function object and then we really would have a complete set of primitives that  nicely and interoperability supported both classical and prototypal inheritance abstractions. Allen

I can see the "recursive stache" useful in some situations (although it is another syntax addition to object literals). It would allow for the ability to apply a deeply nested "patch" to an object, which is sort of interesting to think about. However, what I think this pattern, the original pattern, and Axel's pattern all lack is that it places too much emphasis on class members, and not enough on the prototype. Perhaps I'm being too nit-picky now, but I find that class/static members are a whole lot more rare than prototype/instance members. My proposal was shooting for a sweet spot where the 90% (a made up number of course) case of a constructor and some prototype methods could be handled in one object literal, effectively the same code as the body of a potential class literal.

Again, if a class literal can be agreed on, my proposal is likely moot because then we likely wouldn't need that little bit of extra sugar. It's not a far stretch from mine to yours, I just think that if no agreement can be made, I like my pattern better for the general use case.

# Allen Wirfs-Brock (14 years ago)

On Oct 2, 2011, at 1:32 PM, Russell Leggett wrote:

... I can see the "recursive stache" useful in some situations (although it is another syntax addition to object literals). It would allow for the ability to apply a deeply nested "patch" to an object, which is sort of interesting to think about. However, what I think this pattern, the original pattern, and Axel's pattern all lack is that it places too much emphasis on class members, and not enough on the prototype. Perhaps I'm being too nit-picky now, but I find that class/static members are a whole lot more rare than prototype/instance members. My proposal was shooting for a sweet spot where the 90% (a made up number of course) case of a constructor and some prototype methods could be handled in one object literal, effectively the same code as the body of a potential class literal.

The problem with:

const ClassName = SuperClass <| { constructor(/*constructor parameters */) { //constructor body super.constructor(/*arguments to super constructor */); this.{ //per instance property definitions } } method1(){ return super.method1(); } method2(){} prop1:"Properties unlikely, but allowed" }.{ //class properties staticMethod(){} };

Is that the SuperClass <| { ... } part evaluates to the prototype object, not the constructor function and hence what you would be naming is the prototype. This, in general, is how stache has to work for arbitrary object literals where all you really are trying to do is set the [[Prototype]]. There really isn't anything special in your pattern that distinguishes it from that simple object case.

As has been discussed on this list before, if you are actually using prototypal inheritance to construct your object abstractions then it really is the prototype you want to name rather than the constructor function. EG:

const Person = Mammal <| { name: 'John Doe', constructor(sex,name) { super.constructor(sex); this.{name} } }

console.log(typeof Person); //'object', not 'function' console.log(Person.name); //'John Doe'

Person is the prototypal person. You would then really like to create new Person instances like:

let joe = new Person('male','Joe Smith'); // means roughly joe = Object.create(Person).constructor('male','Joe Smith')

console.log(joe.name); //'Joe Smith' console.log(Object.getPrototypeOf(joe).name); //'John Doe'

However, new currently throws when applied to non-function objects. This is something I would like to fix for ES.next. Also, there is also an issue that in a definition like mine above the constructor function that is being created really should automatically get a 'prototype' property that back references the object with the 'constructor' property.

# Russell Leggett (14 years ago)

On Oct 2, 2011, at 6:19 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Oct 2, 2011, at 1:32 PM, Russell Leggett wrote:

... I can see the "recursive stache" useful in some situations (although it is another syntax addition to object literals). It would allow for the ability to apply a deeply nested "patch" to an object, which is sort of interesting to think about. However, what I think this pattern, the original pattern, and Axel's pattern all lack is that it places too much emphasis on class members, and not enough on the prototype. Perhaps I'm being too nit-picky now, but I find that class/static members are a whole lot more rare than prototype/instance members. My proposal was shooting for a sweet spot where the 90% (a made up number of course) case of a constructor and some prototype methods could be handled in one object literal, effectively the same code as the body of a potential class literal.

The problem with:

const ClassName = SuperClass <| { constructor(/*constructor parameters */) { //constructor body super.constructor(/*arguments to super constructor */); this.{ //per instance property definitions } } method1(){ return super.method1(); } method2(){} prop1:"Properties unlikely, but allowed" }.{ //class properties staticMethod(){} };

Is that the SuperClass <| { ... } part evaluates to the prototype object, not the constructor function and hence what you would be naming is the prototype. This, in general, is how stache has to work for arbitrary object literals where all you really are trying to do is set the [[Prototype]]. There really isn't anything special in your pattern that distinguishes it from that simple object case.

What distinguishes it is that I was adding a new case to <| operator where the LHS is a constructor function and the RHS is an object literal. My intention was that SuperClass would be the constructor function not a prototype. Perhaps this is confusing because the return type is not the same as the same as the RHS.

However, new currently throws when applied to non-function objects. This is something I would like to fix for ES.next.

I assume this would be sugar for Object.create with only the first argument? I think that would be interesting considering JavaScript has optional parens on a no arg constructor, so the two would look the same - new foo vs new Foo. In a way, they are practically the same thing. It could be a good way of closing the gap between pure prototypal inheritance and the simulated class constructor style. I think both are valuable and it would be really great if they sat well together in ES.next

# Allen Wirfs-Brock (14 years ago)

On Oct 2, 2011, at 5:02 PM, Russell Leggett wrote:

On Oct 2, 2011, at 6:19 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Is that the SuperClass <| { ... } part evaluates to the prototype object, not the constructor function and hence what you would be naming is the prototype. This, in general, is how stache has to work for arbitrary object literals where all you really are trying to do is set the [[Prototype]]. There really isn't anything special in your pattern that distinguishes it from that simple object case.

What distinguishes it is that I was adding a new case to <| operator where the LHS is a constructor function and the RHS is an object literal. My intention was that SuperClass would be the constructor function not a prototype. Perhaps this is confusing because the return type is not the same as the same as the RHS.

Ok, your intent didn't come across.

What happens if the RHS does't have a 'constructor' property? For example: let obj = function() {} <| {};

Would obj be a function object? If so, what is it's body. Is it the existence of a 'constructor' property in the LHS object literal that triggers the creation of a function object instead of a non-function?

# Russell Leggett (14 years ago)

On Oct 2, 2011, at 8:19 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Oct 2, 2011, at 5:02 PM, Russell Leggett wrote:

On Oct 2, 2011, at 6:19 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Is that the SuperClass <| { ... } part evaluates to the prototype object, not the constructor function and hence what you would be naming is the prototype. This, in general, is how stache has to work for arbitrary object literals where all you really are trying to do is set the [[Prototype]]. There really isn't anything special in your pattern that distinguishes it from that simple object case.

What distinguishes it is that I was adding a new case to <| operator where the LHS is a constructor function and the RHS is an object literal. My intention was that SuperClass would be the constructor function not a prototype. Perhaps this is confusing because the return type is not the same as the same as the RHS.

Ok, your intent didn't come across.

This is what I wrote to Axel to explain:

  1. If a "constructor" method is supplied as part of the object literal, it will be used as the new constructor, and take the LHS constructor as its prototype. If not, an new empty constructor will be created instead and take the LHS constructor as its prototype.
  2. The rest of the object literal will be used as the new constructors prototype, and it will use the LHS's prototype as it's prototype.
  3. The result of the <| operand in my example would be the new class/constructor function, so it can be assigned to "const ClassName" or as in my example, the .{ operator can be used to add class methods and still return the correct result.

What happens if the RHS does't have a 'constructor' property? For example: let obj = function() {} <| {};

Would obj be a function object? If so, what is it's body.

obj would indeed be a constructor function and would basically be equivalent to let obj = function(){} <| function(){};

Is it the existence of a 'constructor' property in the LHS object literal that triggers the creation of a function object instead of a non-function?

I'm guessing you meant RHS, but no, I don't think that should be necessary. I think all that would be needed would be a function on the LHS, and an object literal on the RHS.

# Kam Kasravi (14 years ago)

If the <| operator's intent is to create new classes easily via composition, it seems like its grammar should be  'class' friendly eg the LHS <| RHS could be a class and/or class expression as in the following:

class Person {    constructor(name) {      private name;     @name = name;   }   describe() {     return "Person called "+ at name;   } }  class Worker = Person <| class {   constructor(name, title) {     private title;     super(name);     @title = title;   }   describe() {     return super.describe()+" ("+ at title+")";    } };

However the grammar as described would exclude the above. Would the Set Literal Prototype grammar eventually be reconciled with the class grammar or do you feel it's a replacement? I ask because I suspect an application programmer would not understand why the RHS could  take an ObjectLiteral (for example) but not an anonymous class - after all both contain 'class' elements.  If the answer is 'no', it seems like '<|' fragments the ways to define a class shape where

LHS <| {   constructor: function(name,title) {     Person.call(this,name);     this.title = title;   } }

works but

LHS <| class {   constructor(name,title) {     private title;     super(name);     @title = title; }

does not.

# Axel Rauschmayer (14 years ago)

What distinguishes it is that I was adding a new case to <| operator where the LHS is a constructor function and the RHS is an object literal. My intention was that SuperClass would be the constructor function not a prototype. Perhaps this is confusing because the return type is not the same as the same as the RHS.

Ok, your intent didn't come across.

What happens if the RHS does't have a 'constructor' property? For example: let obj = function() {} <| {};

Would obj be a function object? If so, what is it's body. Is it the existence of a 'constructor' property in the LHS object literal that triggers the creation of a function object instead of a non-function?

I can see this work for a general case of <function> <| { … }

As soon as the RHS is a function, the result is a function. Russell mentioned that if the RHS didn’t have a property "constructor", its value would be function() {} by default.

I see two problems:

  • If you introduce a new mechanism, you might as well introduce class literals.
  • You might really want to create an object whose prototype is a function.
# Axel Rauschmayer (14 years ago)

const Person = Mammal <| { name: 'John Doe', constructor(sex,name) { super.constructor(sex); this.{name} } }

console.log(typeof Person); //'object', not 'function' console.log(Person.name); //'John Doe'

Person is the prototypal person. You would then really like to create new Person instances like:

let joe = new Person('male','Joe Smith'); // means roughly joe = Object.create(Person).constructor('male','Joe Smith')

console.log(joe.name); //'Joe Smith' console.log(Object.getPrototypeOf(joe).name); //'John Doe'

However, new currently throws when applied to non-function objects. This is something I would like to fix for ES.next.

I’d still be a huge fan of that, e.g.:

operator new(proto, ...args) { let o = Object.create(proto); o.constructor(…args); return o; }

Obviously, you would use Person.isPrototypeOf(p) instead of p instanceof Person.

# Axel Rauschmayer (14 years ago)

Your last requirement is the rub. As currently defined, prototype: { ...} would replace the value of the constructor's 'prototype' property which would mess up the implicit constructor/prototype wiring. Defining : to mean .{ if the property already exists seems fragile and precludes the possibility of using .{ to replace property values. But, what if we allow: propName.{ ...} to occur in a PropertyAssignment position of an object extension literal. Then the pattern could be:

const ClassName = SuperClass <| function(/*constructor parameters */) { //constructor body super.constructor(/*arguments to super constructor */); this.{ // per-instance properties }; }.{
prototype.{ // <<<<< note . instead of : // methods }, // class (ie, constructor) properties };

It's only a one character change but it eliminates the hazard that some people are concerned about of leaving off the .constructor or .constructor.{...} at the end of the pattern. Also, I can imagine that "recursive stache" (hey, it's inevitable that "mustache" will get contracted to "stache") would be useful in other situations. Personally, I find this new pattern not quite as "pretty" than my original proposal (it's less symmetric/ more irregular hence probably harder to learn and remember) but I'd still be happy to use it.

If we could actually agree to this, then we could move on to making sure that the new operator did the right thing for non-function object and then we really would have a complete set of primitives that nicely and interoperability supported both classical and prototypal inheritance abstractions.

I had considered introducing a ..{} operator for this kind of recursive merging, but your solution is better, it gives you something like generic tree merging. Pros and cons of this new pattern:

  • Con: Not as pretty as your pattern.
  • Con: It makes it easier to define class methods than instance methods (the former being rarer).
  • Pro: Better reflects what is actually going on under the hood (where things just aren’t as symmetric as your pattern suggests). It looks to me like the prototype turned inside out, as if one could define a callable object literal.

IMHO, the pro outweighs the two cons.

# Axel Rauschmayer (14 years ago)

Keep in mind that class literals are syntactic sugar and that Russells suggestion was about using his modified <| operator as a replacement for class literals.

What you are suggesting is a generic <| operator, where both LHS and RHS can be arbitrary expressions. However, the point of <| is that the RHS must be a literal (function declaration or object literal), because it actually should be considered to be a part of that literal. It creates the RHS with the appropriate prototype. Allowing any expression as the RHS would mean that the prototype of the RHS would have to be changed and that is much more tricky.

# Kam Kasravi (14 years ago)

On Oct 3, 2011, at 3:11 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

Keep in mind that class literals are syntactic sugar and that Russells suggestion was about using his modified <| operator as a replacement for class literals.

What you are suggesting is a generic <| operator, where both LHS and RHS can be arbitrary expressions.

Actually no - see below...

However, the point of <| is that the RHS must be a literal (function declaration or object literal), because it actually should be considered to be a part of that literal. It creates the RHS with the appropriate prototype. Allowing any expression as the RHS would mean that the prototype of the RHS would have to be changed and that is much more tricky.

The grammar rules for ProtoLiteral cover the various literal types including regex, so they're fairly broad now. I wasn't suggesting any expression, just integration of the class grammar since the intent of the '<|' operator seems to be to make it easier to extend classes. Having the '<|' operator exclude class constructs seems counterintuitive unless it is an alternative syntax to the classes proposal.

# Axel Rauschmayer (14 years ago)

Ah, OK. So you are suggesting

 Person <| class Worker { … }

instead of

 class Worker extends Person { … }

Both solutions can be justified: The first solution makes <| more universal. The second solution limits <| to "under the hood" work and lets class literals be a higher level of abstraction.

An additional reason might be that the current grammar keeps <| and class literals independent. I think the former is currently more likely to make it into ES.next than the latter.

# Axel Rauschmayer (14 years ago)

Addendum: With the definition of the new operator as below (for non-function operands, if the operand is a function, things stay the same), do you think a reference from Person.constructor.prototype to Person is still necessary?

# Allen Wirfs-Brock (14 years ago)

On Oct 3, 2011, at 4:19 AM, Axel Rauschmayer wrote:

Addendum: With the definition of the new operator as below (for non-function operands, if the operand is a function, things stay the same), do you think a reference from Person.constructor.prototype to Person is still necessary?

operator new(proto, ...args) { let o = Object.create(proto); o.constructor(…args); return o; }

I think it is if you want: Let Employee = Person.constructor <| function () {} ... class pattern

to wire-up as: Object.getPrototypeOf(Employee) === Person.constructor and Object.getPrototypeOf(Employee.prototype) === Person

In other words, it provides a way to contect prototypal inheritance based abstractions with classical inheritance based abstractions