Classes as Cosntructors

# Luke Hoban (13 years ago)

In the discussion of max/min classes at the March TC39 meeting, there was support for minimal class syntax, but a couple concerns with max/min classes in particular.

One category of concern is the (intentional) bare-bones nature of max/min. In this case though, I think it has been demonstrated that additional concepts (class properties, privates, prototype properties, etc.) can be added to max/min later, if and when experience shows them to be needed.

The other category of concern is that max/min decides the core syntactic use of the valuable 'class' reserved keyword in the language. Max/min chooses to use 'class' for a new kind of block with a nested constructor, instead of as direct extensions of the notion of constructor function.

I've raised the class as constructor alternative approach before, and it's something that a few teams I've worked with inside Microsoft have advocated. Since a choice on approach for classes will decide the foundation upon which the class keyword is used in the language going forward, I did a writeup of the alternative syntactic approach at strawman:minimal_classes_as_constructors for comparison.

Here's one of the examples from the write-up:

class SkinnedMesh(geometry, materials) extends THREE.Mesh(geometry, materials) {
  
  // properties on the instance object
  public identityMatrix = new THREE.Matrix4();
  public bones = [];
  public boneMatrices = [];

  // ... note, this is the constructor body, statements are allowed here

  // methods on the prototype object
  public update(camera) {
    // ...
    super.update();
  }
}

Classes as constructors aims to align more closely with existing 'function' syntax, augmenting with the ability to declaratively specify members of the class. Importantly though, methods (and accessors) defined in the class body are bound as prototype properties, not instance properties. This can lead to more succinct classes in many cases, and keeps the notion that a class is primarily a constructor function.

Luke

# Bob Nystrom (13 years ago)

I think this is really interesting.

This will look like I'm poking holes here, but please don't take that to mean that I don't find the overall approach worth investigating. You can consider anything I *don't *poke at to be a sign of approval. Some concerns:

It looks like methods on the prototype can access constructor arguments when they can't. I worry that users would expect this to work:

class Rect(width, height) { public area() { return width * height; } }

Mixing statements and definitions together may be future-hostile. One thing I like about other class approaches is the a class body can only contain a specific whitelist of allowed definitions. That lets us wipe a corner of the syntax clean so that we have room to fill it in later with other kind of definitions (const, class properties, nested classes, insert-your-pet-feature-here, etc.). Letting arbitrary statements in gives us a lot less breathing room.

This is, I think, one of the fundamentally hard things about class syntax. There's basically three clumps of code users need to write in order to define a class: the constructor, the prototype properties and the class properties. Our (valid!) desire for simplicity encourages us to reduce that but it's really hard to do without just muddling stuff together.

We can probably punt on class properties (at least for now), but that still leaves either a "two adjacent curly block" approach like a lot of the .{ proposals, a "class body containing a constructor body" approach like other OOP languages, or something like this that tries to merge them together. I like the terseness of the last option but I worry that trying to use a single curly body to do double-duty in that way will come back to haunt us.

Still, I think what you have here is a very lucid approach to that option.

# Erik Arvidsson (13 years ago)

I think this proposal has one fatal flaw and that was what brought it down the last time we had a proposal which used the same concepts. Given:

class C(x) { public method() { return x; } }

It seems like the arguments to the constructor are in scope for the entire class body when they really aren't. The above would raise an error that "x is undefined".

I also have some minor issues. The most notable one is evident in the example used:

class SkinnedMesh(geometry, materials) extends THREE.Mesh(geometry, materials) { ... }

What comes after extends is an AssignmentExpression that is called when the class is created, not parameters that are passed to the super class. This even confused you so I'm sure it will lead to endless confusions to less experience programmers.

# Herby Vojčík (13 years ago)

Luke Hoban wrote:

In the discussion of max/min classes at the March TC39 meeting, there was support for minimal class syntax, but a couple concerns with max/min classes in particular.

One category of concern is the (intentional) bare-bones nature of max/min. In this case though, I think it has been demonstrated that additional concepts (class properties, privates, prototype properties, etc.) can be added to max/min later, if and when experience shows them to be needed.

The other category of concern is that max/min decides the core syntactic use of the valuable 'class' reserved keyword in the language. Max/min chooses to use 'class' for a new kind of block with a nested constructor, instead of as direct extensions of the notion of constructor function.

I've raised the class as constructor alternative approach before, and it's something that a few teams I've worked with inside Microsoft have advocated. Since a choice on approach for classes will decide the foundation upon which the class keyword is used in the language going forward, I did a writeup of the alternative syntactic approach at strawman:minimal_classes_as_constructors for comparison.

I just don't understand why you use class keyword here. This will work without any changes, as-is, with plain function keyword. After all, you just augment a constructor definition.

# David Herman (13 years ago)

On May 22, 2012, at 9:29 AM, Erik Arvidsson wrote:

I think this proposal has one fatal flaw and that was what brought it down the last time we had a proposal which used the same concepts. Given:

class C(x) { public method() { return x; } }

It seems like the arguments to the constructor are in scope for the entire class body when they really aren't. The above would raise an error that "x is undefined".

Or worse:

var x = "WAT";
class C(x) {
    public method() {
        return x
    }
}
(new C("inner")).method() // WAT

I'm afraid this is just not a workable approach.

# T.J. Crowder (13 years ago)

With respect, I have to say that I vastly prefer the current class proposal (harmony:classes). I remember being struck when I first read it (only recently, about a month ago) at how incredibly sensible that proposal was.

Some issues I see with this alternative:

  1. It's not clear how I can call the superclass constructor with different arguments than the ones I was called with, as they seem to be passed in the extends clause.

  2. Conflating the constructor's execution context with the context defining prototype properties seems unnecessarily confusing. The current proposal is immediately clear to anyone used to JavaScript, and very familiar ("class" body, constructor and other methods separately) to those coming from more class-based OOP backgrounds.

  3. As others have said, the syntax complicates understanding of execution contexts and scope by making it seem like the constructor arguments would be in-scope throughout, when presumably they wouldn't. I'm not keen on introducing new magic to execution context definition if we can avoid it, and I think we can avoid it here.

FWIW,

T.J. Crowder Independent Software Engineer www / crowder software / com tj / crowder software / com

# Luke Hoban (13 years ago)

On May 22, 2012, at 9:29 AM, Erik Arvidsson wrote:

I think this proposal has one fatal flaw and that was what brought it down the last time we had a proposal which used the same concepts. Given:

class C(x) { public method() { return x; } }

It seems like the arguments to the constructor are in scope for the entire class body when they really aren't. The above would raise an error that "x is undefined".

This is indeed a valid concern, though I don't see it as a fatal flaw. It's effectively the same issue that statics would pose if added to any existing class proposal.

One way to address this concern is to put 'x' in scope in the method bodies, but as a poisoned reference that raises an (early?) error. That helps ensure that unexpected outer variables are not unintentionally captured.

The code above would be rewritten:

class C(public x) { public method() { return this.x; } }

On the other hand - this allows a sufficiently simpler representation of the class than the max/min proposal:

class C { constructor(x) { this.x = x; } public method() { return this.x; } }

Luke

# Luke Hoban (13 years ago)

Herby Vojčík <herby at mailbox.sk> wrote:

I just don't understand why you use class keyword here. This will work without any changes, as-is, with plain function keyword. After all, you just augment a constructor definition.

The body of this kind of function is different, in that it can define instance properties and prototype methods. It is valuable to distinguish this syntactically, but also valuable to align with expectations that developers currently have for using functions as constructors.

Luke

# Luke Hoban (13 years ago)

It looks like methods on the prototype can access constructor arguments when they can't. I worry that users would expect this to work: class Rect(width, height) {   public area() { return width * height; } }

Right - this is the concern that others raised on a parallel branch of the discussion. Although I see the concern, I think this is not necessarily a fatal concern, and there are options that could help here raised on the other thread.

Mixing statements and definitions together may be future-hostile. One thing I like about other class approaches is the a class body can only contain a specific whitelist of allowed definitions. That lets us wipe a corner of the syntax clean so that we have room to fill it in later with other kind of definitions (const, class properties, nested classes, insert-your-pet-feature-here, etc.). Letting arbitrary statements in gives us a lot less breathing room.

While having a new kind of body for classes does have some benefits, it also comes at the cost of making classes heavier in all cases. I think this is the core aspect of the tradeoff between the two syntactic approaches.

We can probably punt on class properties (at least for now), but that still leaves either a "two adjacent curly block" approach like a lot of the .{ proposals, a "class body containing a constructor body" approach like other OOP languages, or something like this that tries to merge them together. I like the terseness of the last option but I worry that trying to use a single curly body to do double-duty in that way will come back to haunt us.

In the approach of treating a class as a constructor, class properties can be added in the future just as "static foo() {}". I don't think this needs a separate block, assuming you buy into the first question raised about expectations of scopes.

Luke

# Erik Arvidsson (13 years ago)

On Tue, May 22, 2012 at 11:16 AM, Luke Hoban <lukeh at microsoft.com> wrote:

The code above would be rewritten:

class C(public x) {    public method() {      return this.x;    }  }

But also,

class C(x) { var y = 42; public method() { return x + y; } }

This can be solved by treating all variables in the class body and the constructor params as "private" instance properties and compile it to something like:

import {Name} from "@name"; const _x = new Name; const _y = new Name; class C(x) { this[_x] = x; var y = 42; this[_y] = y; public method() { return this[_x] + this[_y]; } }

This might be too magical and confusing though.

# Herby Vojčík (13 years ago)

Luke Hoban wrote:

On May 22, 2012, at 9:29 AM, Erik Arvidsson wrote:

I think this proposal has one fatal flaw and that was what brought it down the last time we had a proposal which used the same concepts. Given:

class C(x) { public method() { return x; } }

It seems like the arguments to the constructor are in scope for the entire class body when they really aren't. The above would raise an error that "x is undefined".

This is indeed a valid concern, though I don't see it as a fatal flaw. It's effectively the same issue that statics would pose if added to any existing class proposal.

One way to address this concern is to put 'x' in scope in the method bodies, but as a poisoned reference that raises an (early?) error. That helps ensure that unexpected outer variables are not unintentionally captured.

Oh no, it's better to define static constraints that methods can not see into constructor's locals (args or let/vars).

And fail-fast when such constructor function is defined.

The code above would be rewritten:

class C(public x) { public method() { return this.x; } }

On the other hand - this allows a sufficiently simpler representation of the class than the max/min proposal:

class C { constructor(x) { this.x = x; } public method() { return this.x; } }

This is not exactly fair comparision, because there are other shortcut constructor proposals, so it is more:

 class C(public x) {
   public method() {
     return this.x;
   }
 }

against

 class C {
   constructor(this.x) {}
   public method() {
     return this.x;
   }
 }

Luke

As I told in different mail, I see no reason to use "class" - this is just an augmented function syntax. The function keyword is, after all, the one that creates constructor functions (and mainly those, in ES6 with arrows).

I would be for this, if it gets through, but only if it will use plain old function keyword.

# Herby Vojčík (13 years ago)

Luke Hoban wrote:

Herby Vojčík<herby at mailbox.sk> wrote:

I just don't understand why you use class keyword here. This will work without any changes, as-is, with plain function keyword. After all, you just augment a constructor definition.

The body of this kind of function is different, in that it can define instance properties and prototype methods. It is valuable to distinguish this syntactically, but also valuable to align with expectations that developers currently have for using functions as constructors.

This mail arrived late, that's why I did not reply to this. Nevertheless, I see no difference here. See the other mail, the function keyword is now primarily a tool to create constructor functions, and what's more important, your proposal does not changes_something_existing nor removes something - it just adds "public" as a way to define instance variables and prototype methods. You only add functionality to existing function syntax and semantics. I wpuld see this as bigger waste of class keyword (it does not add any real value - it just creates two things where one suffices, making language more complex) than other proposals where it is used for something really different than function body.

(of course, extends must be added to functions and semantics of <| should be fixed, but that is something I have told many times and no one seems to care)