traits are now impossible in ES6 until ES7 since rev32?

# Luke Scott (9 years ago)

I know traits are not something that will make it into ES6. This was the suggested alternative on the mailing list:

class Thing extends mixin(Base, Trait1, Trait2) {...}

Unfortunately since rev32 this is now seems impossible, as a custom implementation of traits would need to this to work:

class Foo {
    constructor() {}
}
function ctor() {
    Foo.call(this); // <— illegal?
}
ctor.prototype = Object.create(Foo.prototype);
ctor.prototype.constructor = ctor;
new ctor();

The code I’m working with makes extensive use of traits. Simple inheritance just doesn’t work for what I’m trying to do (and avoid duplicating code). I’m using 6to5, and now Trait.call(this) is failing since I was defining my traits as classes. I can fix this by not using classes for my traits, and 6to5 or traceur cannot enforce Foo.call(this) from being illegal since “this instanceof Foo” is true, but it is sure to break on the real thing.

Hopefully I’m wrong in that Foo.call(this) is illegal, but if it is, this is a devastating change, especially when traits are scheduled for ES7 or later.

# Kevin Smith (9 years ago)

Hopefully I’m wrong in that Foo.call(this) is illegal, but if it is, this is a devastating change, especially when traits are scheduled for ES7 or later.

Class constructors will now throw when called.

The changes to classes were fundamental but necessary to support subclassing of builtins (and future extensions to classes).

For mixins, can you move the initialization logic into a method (or a static method), instead of having it in the constructor?

class M() {
    // methods
    static initialize(obj) { }
}

function ctor() {
    M.initialize(this);
}

// etc.

Is this a viable pattern for traits/mixins?

# Luke Scott (9 years ago)

With the code example I provided Foo is the class, not the mixin. Foo needs to be copied so traits can be added to it.

Here’s an example:

class Foo {}
class Fooy extends mixin(Foo, SomeTrait) {}

“Foo” needs to be copied and the methods from SomeTrait needs to be added to the copy’s prototype, which Fooy then extends.

This is how you would traditionally copy a “class” (pre-es6 rev32):

class Foo {
   constructor() {}
}
function copy(classObject) {
	function ctor() {
	   Foo.call(this);
	}
	ctor.prototype = Object.create(classObject.prototype);
	ctor.prototype.constructor = ctor;
	return ctor;
}

Although, after some discussion in a 6to5 issue, this may (hopefully) be legal:

class Foo {
   constructor() {}
}
function copy(classObject) {
    return class extends classObject {};
}

As long as the prototype of the class can still be modified, that should work.

# Andrea Giammarchi (9 years ago)

I'm using traits without any sort of problems in es-class and the key is to have an initialize method. It's actually IMO bad expectation to explicitly initialize a known constructor via a mixin, it makes it non portable and non reusable, correct?

What Kevin suggested is indeed the way I've implemented them, I also didn't know that the chosen keyword for traits was mixin.

Is this final decision? Thanks

# Claude Pache (9 years ago)

Le 6 févr. 2015 à 05:47, Luke Scott <luke at cywh.com> a écrit :

class Foo {
   constructor() {}
}
function ctor() {
   Foo.call(this); // <— illegal?
}
ctor.prototype = Object.create(Foo.prototype);
ctor.prototype.constructor = ctor;
new ctor();

That pattern is the exact (almost character-for-character) pattern I use for defining a subclass the pre-ES6 way. Thus, (as you noted in a subsequent message,) just use standard ES6 subclassing, (but beware of enumerability issues).

But, more generally, that raises an interesting issue if you intend to write a general library that works both with ES6-classes and pre-ES6-pseudoclasses: If you use the pre-ES6 pattern, it risks to break on ES6-classes; but if you use the ES6-classes pattern, it will (fortunately) probably work with pre-ES6-pseudoclasses, but it won't compile on implementations that don't support ES6-classes. So, you have to write your library the both ways and do some feature-detection in order to decide what code to serve.

# Ben Newman (9 years ago)

The specific line in rev32 of the spec that prevents [[Call]]ing "classConstructor" functions is 9.2.2.2:

2 If F’s [[FunctionKind]] internal slot is "classConstructor", throw a TypeError exception.

From my reading of the spec, I think the idiomatic Foo.call(this) pattern that Luke Scott described would work if we simply changed that line to something slightly weaker:

2 If F’s [[FunctionKind]] internal slot is "classConstructor" and InstanceofOperator(thisArgument, F) is false, throw a TypeError exception.

This mirrors an assertion discipline that has saved me from many bugs due to forgetting the new operator:

function Base() {
  assert.ok(this instanceof Base);
  ...
}

function Derived() {
  assert.ok(this instanceof Derived);
  Base.call(this);
  ...
}

Derived.prototype = Object.create(Base.prototype, {
  constructor: { value: Derived, ... }
});

Is the addition of the instanceof check naive? Would it invalidate any of the assumptions involved in the invocation of F?

I'm happy to file a bug if this change merits further consideration.

It may be worth noting that only constructors created by class syntax will have their [[FunctionKind]] internal slot set to "classConstructor", so (even with the current spec) you can still invoke ordinary constructor functions using [[Call]]. However, it seems regrettable that you have to know whether a constructor was created by class syntax in order to know whether the Foo.call(this) pattern is safe.

# Claude Pache (9 years ago)

The issue is deeper. In short, you cannot feed an ES6-class constructor with an already allocated object, whatever that object is.

With a user-defined good ol' pre-ES6 constructor:

foo = new Foo // allocate a Foo object and initialise it

is usually equivalent to:

foo = Object.create(Foo.prototype) // allocate a Foo object ...
Foo.call(foo) // ... and initilise it

With the new ES6-class semantics, for the sake of subclassability of builtins, it is, on purpose, not possible to separate allocation from initialisation that way. Even before ES6, builtin classes, did not support such a pattern, e.g.,

arr = new Array(2, 3) // allocate a new array and initialise it

is in no way equivalent to:

arr = Object.create(Array.prototype) // allocate an new object, but it won't be an array...
Array.call(arr, 2, 3) // ... and don't initialise it, but create uselessly a new Array

And it appeared that introducing the possibility for builtins to have separate allocation and initialisation phases was problematic.

# Luke Scott (9 years ago)

The following should be sufficient with the current ES6, spec, right? It currently works in 6to5.

function mixin(classObject, …traits) {
    var newClassObject = class extends classObject{}
    // … fill in prototype here
}

Below is a possible workaround for pre-ES6 code (without being transpired), provided that an Object.isClass is added (checks for internal classConstructor flag), which would always return false in pollyfills.

function mixin(classObject, traits) {
    var newClassObject;
    if (Object.isClass(classObject)) {
        newClassObject = eval("class extends classObject{}");
    } else {
        newClassObject = function() {classObject.apply(this, arguments);};
        newClassObject.prototype = Object.create(classObject.prototype);
        newClassObject.prototype.constructor = newClassObject;
    }
    // … fill in prototype here
}

It’s an ugly hack. Unfortunately the eval is necessary because Chrome throws “Unexpected reserved word” for the class keyword.

# Allen Wirfs-Brock (9 years ago)

On Feb 6, 2015, at 9:04 AM, Ben Newman wrote:

Is the addition of the instanceof check naive? Would it invalidate any of the assumptions involved in the invocation of F?

I'm happy to file a bug if this change merits further consideration.

There is nothing about ES6 classes or subclassing built-ins that inherently requires line 2 above. Without it, calling a class would work just fine and new.target would even give you a way to distinguish [[Call]] invocation from a [[Construct]] invocation.

Line 2 exists because some TC39 members wanted to future proof for things they want to experiment with for future editions. These things may include:

  1. Some way to provide a separate function body that is used when the constructor is [[Call]]'ed
  2. Allowing unqualified using of super() in "called" constructor to mean the same thing as super.constructor()
  3. Maybe making "calling" a constructor equivalent to "newing" the constructor

Allowing calls of class constructors would allow people to start writing code that might create legacy issues for such future features.

I think it would take more than a bug to change this now.

It may be worth noting that only constructors created by class syntax will have their [[FunctionKind]] internal slot set to "classConstructor", so (even with the current spec) you can still invoke ordinary constructor functions using [[Call]]. However, it seems regrettable that you have to know whether a constructor was created by class syntax in order to know whether the Foo.call(this) pattern is safe.

yup