Summary: prototypes as classes

# Axel Rauschmayer (14 years ago)

FWIW: I’ve written down my current understanding.

www.2ality.com/2011/06/prototypes-as-classes.html

# Bob Nystrom (14 years ago)

I like the simplicity of this, but I'm not crazy about how it merges two distinct objects into one. TodayJS (and most class-based languages) let you distinguish two things:

  1. A set of properties relevant to the class itself.
  2. A set of properties shared by each instance of the class.

In a class-based language, #1 up there is "static" methods and fields. So when you do something like this in Java:

Integer.parseInt("1234");

The "parseInt" method isn't a method that you can call on an integer, it's a method you call on the Integer class itself.

In Javascript, those are properties on the constructor:

function Point(x, y) { this.x = x; this.y = y; }

Point.zero = function() { return new Point(0, 0); }

Point.zero(); // "static" method.

The second set of properties are properties on ".prototype" in JS:

Point.prototype.flipX = function() { this.x = -this.x; }

var p = new Point(1, 2); p.flipX(); p.x // -1

To me, those two sets are utterly distinct. This code looks pretty strange:

Point.flipX(); // What x?

var p = new Point(1, 2); p.zero(); // This works, but what does it have to do with 'p'?

Your proposal would allow that, I think. For example, starting from yours:

// Superclass var Person = { constructor: function (name) { this.name = name; }, describe: function() { return "Person called "+this.name; } };

I could do:

var bob = new Person("Bob"); bob.constructor("Fred"); // I guess I'm Fred now.

Does the above seem strange to you too?

# Brendan Eich (14 years ago)

On Jun 28, 2011, at 12:34 PM, Bob Nystrom wrote:

I like the simplicity of this, but I'm not crazy about how it merges two distinct objects into one. TodayJS (and most class-based languages) let you distinguish two things:

  1. A set of properties relevant to the class itself.
  2. A set of properties shared by each instance of the class.

Yes, and I don't see a good solution to this.

From the point of view of utmost simplicity, ignoring history of other languages and of JS itself (not only the built-ins, the DOM and other host object APIs, and user-defined constructor functions), deferring classes wins.

But we can't ignore either tradition or user mental models still fostered by function-as-constructor-with-.prototype in JS, which is not going away. The Q&A at

www.quora.com/How-was-classical-inheritance-supposed-to-be-done-in-ECMAScript-3

shows how classical inheritance with class as constructor or factory for instances, not as prototype, has deep roots in developers' minds.

Plus, the prototype is in some ways secondary. It's the less directly used object when one calls a constructor often, after populating the prototype. And if class methods come into the picture, the prototype is even more "backstage", an implementation detail.

In a class-based language, #1 up there is "static" methods and fields. So when you do something like this in Java:

Integer.parseInt("1234");

The "parseInt" method isn't a method that you can call on an integer, it's a method you call on the Integer class itself.

Or perhaps a constructor, if not the constructor.

Irakli's Ruby-ish .new protocol could help unify how one constructors built-ins and new prototype-first abstractions, but it is not yet proposed, and it is a new protocol (no pun intended). Do we need a second constructor protocol?

Your proposal would allow that, I think. For example, starting from yours:

// Superclass var Person = { constructor: function (name) { this.name = name; }, describe: function() { return "Person called "+this.name; } };

I could do:

var bob = new Person("Bob"); bob.constructor("Fred"); // I guess I'm Fred now.

Does the above seem strange to you too?

It seems a bit strange to me. One can get used to many things. Probably people could get used to this, but the fact is JS developers are already "used to" constructors with prototypes, where the abstraction's name denotes the constructor, not the prototype.

Whatever I personally think, I suspect developers in general, and TC39 in particular, will be divided on this. We can find out the hard way, but I'm pretty sure we won't defer classes in favor of prototypes-first with .constructor as the more backstage object.

Grass roots spokes-models also tell me some grumbling against the <| operator hurts this class-free idea. And I note in the font I just used, the | is too tall compared to the <, an aesthetic bad omen.

# Allen Wirfs-Brock (14 years ago)

On Jun 28, 2011, at 8:34 PM, Bob Nystrom wrote:

I like the simplicity of this, but I'm not crazy about how it merges two distinct objects into one. TodayJS (and most class-based languages) let you distinguish two things:

(actually in the today's most widely used class-based languages (Java, C#, C++) a "class" is no an object at all and don't have properties/fields/polymorphic methods/etc. they really are just a scoping mechanism. What you are saying is more typical of many dynamically typed class-based languages).

Note that the goal of the prototype as classes proposals is not to turn JS into more of a class-based language. Instead it was to solve the same problem that classes solve but in a manner that returns JavaScript closer to its original prototype-based roots. This was covered in the message that started the "Prototype as new new class declaration" thread esdiscuss/2011-June/015135 It is probably worth while going back to review those first few messages to get a better chance of what this proposal is really about.

While both the class-based approach and the prototype-base approach allow you to describe an open ended set of similar objects via a named abstraction, the key difference is in what you name. In the class based approach you name the object that is responsible for creating new instances (the constructor) of the set. In the prototype-based approach you name the prototypical instance of the set.

The "two objects" you speak of is purely an artifact of how the class-bassed approach works. It is not a fundamental characteristic of all OO languages.

When it is useful to have s separate factory object for creating the instances of a prototype-based abstractions, prototype-based languages simply create another object to act as the factory. that object can have private state, addiional public properties for accessing canonical instances, etc.

In esdiscuss/2011-June/015195 is examine how the self language addressed various situations where "static methods" might be used in a class-based langauges.

  1. A set of properties relevant to the class itself.
  2. A set of properties shared by each instance of the class.

In a class-based language, #1 up there is "static" methods and fields. So when you do something like this in Java:

Integer.parseInt("1234");

The "parseInt" method isn't a method that you can call on an integer, it's a method you call on the Integer class itself.

In self, you would likely do exactly the above. Integer would be the name of the prototypical integer object and that object would have a method named parseInt. You then can just say: Integer.parseInt("1234"); //actually in self you would say: Integer parseInt: '1234'. or you can say 0.parseInt("1234"); or any other integer object that was conveniently available.

If you didn't want the ability to parse to be a method of integers you would just create a factory object: IntegerBuilder.parse("1234");

Why is this worse than a "static method"?

In Javascript, those are properties on the constructor:

function Point(x, y) { this.x = x; this.y = y; }

note that the above isn't a property of the constructor, it is the constructor. It actually is a property of the prototype.

Point.zero = function() { return new Point(0, 0); }

Point.zero(); // "static" method.

It's all a mater of how you think of responsibilities. In a consistent prototype based usage, the "zero" point would probably actually be the prototypical point in which case you would access it simply as Pointl

The second set of properties are properties on ".prototype" in JS: Its

Point.prototype.flipX = function() { this.x = -this.x; }

var p = new Point(1, 2); p.flipX(); p.x // -1

To me, those two sets are utterly distinct. This code looks pretty strange:

Point.flipX(); // What x?

The x of the prototypical point named Point. As a prototypical point it has all the properties of any other point instance including an x and y property. (I'm ignoring the fact the a point abstraction with mutable x and y fields is probably not a very good design).

In practice, you probably wouldn't call such a method on the prototype, so you wouldn't see the above at all. But you could, that's what it means to be a prototype-based language.

var p = new Point(1, 2); p.zero(); // This works, but what does it have to do with 'p'?

probably as much as any other Point method that creates a new instance. Since you probably really do want to have immutable points, that means most point methods:

p.add(new Point(3,4)); //returns a new point with x=4, y=6 p.zero(); //return a point with x=0,y=0

But, similar to above, in practice you probably would seldom actually see the latter. You would just see: Point.zero();

Your proposal would allow that, I think. For example, starting from yours:

// Superclass var Person = { constructor: function (name) { this.name = name; }, describe: function() { return "Person called "+this.name; } };

I could do:

var bob = new Person("Bob"); bob.constructor("Fred"); // I guess I'm Fred now.

Does the above seem strange to you too?

not particularly anymore so than Bob("Fred"); //note no new or Bob.call(bob,"Fred")

# Mike Shaver (14 years ago)

On Tue, Jun 28, 2011 at 3:34 PM, Bob Nystrom <rnystrom at google.com> wrote:

I like the simplicity of this, but I'm not crazy about how it merges two distinct objects into one. TodayJS (and most class-based languages) let you distinguish two things:

  1. A set of properties relevant to the class itself.
  2. A set of properties shared by each instance of the class. In a class-based language, #1 up there is "static" methods and fields. So when you do something like this in Java:   Integer.parseInt("1234"); The "parseInt" method isn't a method that you can call on an integer, it's a method you call on the Integer class itself.

Actually, in Java, you can (or could, when last Java and I spoke) call class methods on instances: Integer boxed = new Integer(5); boxed.parseInt(42);

That doesn't work well in JS, because obviously there's no prevention of name collisions.

Mike

# Bob Nystrom (14 years ago)

On Tue, Jun 28, 2011 at 3:56 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>

wrote: (actually in the today's most widely used class-based languages (Java, C#, C++) a "class" is no an object at all and don't have properties/fields/polymorphic methods/etc. they really are just a scoping mechanism. What you are saying is more typical of many dynamically typed class-based languages).

Understood. That's why I used the vague "set of properties" to describe static methods in class-based languages. A class may not be a first-class object (and there may not be a metaclass for it) but it still has a set of methods associated with it for some loose definition of "associated".

The "two objects" you speak of is purely an artifact of how the

class-bassed approach works. It is not a fundamental characteristic of all OO languages.

I have two responses to that:

You're right that it's an artifact of how the class-based approach works, but I look at that as a positive sign: that artifact represents the choice of many popular languages (and lots of idiomatic JS code for that matter). If the cow-path is that well-worn, it may be worth paving it, or at least tossing a bit of gravel down.

But, more abstractly, I do think there are two fundamentally different "objects" here. Some operations, conceptually, relate to a kind of object, but don't relate to any specific instance of it. Others are specific to a single instance.

We can lump those operations together (and hope they don't collide), but I'm having trouble seeing why we'd want to.

When it is useful to have s separate factory object for creating the

instances of a prototype-based abstractions, prototype-based languages simply create another object to act as the factory. that object can have private state, addiional public properties for accessing canonical instances, etc.

I think the lesson from lots of languages (including JS) is that those factory objects are pretty handy. Why move away from them?

or you can say 0.parseInt("1234"); or any other integer object that was conveniently available.

When I see code like:

somePoint.flipX();

That implies to me that flipX() uses somePoint's state in some significant way. If it didn't, why is somePoint receiving the message and not some other object? So when I see:

0.parseInt("345");

I feel like the code is misleading the reader. Why am I sending "parseInt" to 0 if it doesn't have anything to do with it?

If you didn't want the ability to parse to be a method of integers you

would just create a factory object:

IntegerBuilder.parse("1234");

Why is this worse than a "static method"?

Who said it was? Factory objects are swell. I just don't see why instances should inherit from them. In some general sense, a point in space at (3, 2) is not a point factory, it's a point. Why should it inherit from an object that represents a point factory?

function Point(x, y) { this.x = x; this.y = y; }

note that the above isn't a property of the constructor, it is the

constructor.

Heh, understood. I was referring to the stuff after that bit. :)

In a consistent prototype based usage, the "zero" point would probably

actually be the prototypical point in which case you would access it simply as Pointl

I do realize that, and for very simple types like (immutable) points, that model works pretty well. I'm not sure what the prototypical image would look like (Lena?), or what the prototypical mailing address is (123 Main Street?).

(I'm ignoring the fact the a point abstraction with mutable x and y fields

is probably not a very good design).

I wouldn't be too eager to ignore things like that. People using our languages may not be the best designers but they still want to get their job done. If prototypes-as-classes help, that's awesome, but if it's just a gun that auto-aims for their feet we haven't done them any favors.

But you could, that's what it means to be a prototype-based language.

Well, that's what it means to be a prototype-based language and follow a certain inheritance pattern. I don't think I'm arguing against prototypes. I'm just not sold on making the object bound to the class name be the prototype of instances of that class.

From my view, JS-of-today is no less prototype-based than what Axel is

proposing, it just wires things up a bit differently. Or is there something I'm missing?

# Axel Rauschmayer (14 years ago)

As an aside:

  • Static methods in Java always felt like a hack, because Java’s creators couldn’t bring themselves to support first-class functions.
  • Thus: I would much prefer to put Object.* methods into a module (once we have those). The same holds for “class methods” such as Math.*. There Math is used for namespacing and not really a class.

From my view, JS-of-today is no less prototype-based than what Axel is proposing, it just wires things up a bit differently. Or is there something I'm missing?

I agree. However: Prototypes are already the core mechanism with regard to JS inheritance, they are what stays after an instance has been created, they are actually used for instanceof tests, etc. PaCs put more emphasis on them, that’s all. I find they really shine when it comes to subclassing. You have to work harder with constructor functions (super-references will help a little, though). See code below. To me, the prototypes are at the core of what is happening in subclassing.

== Prototypes as classes ==

// Superclass
var Person = {
    constructor: function (name) {
        this.name = name;
    },
    describe: function() {
        return "Person called "+this.name;
    }
};

// Subclass
var Worker = Person <| {
    constructor: function (name, title) {
        Person.constructor.call(this, name);
        this.title = title;
    },
    describe: function () {
        return Person.describe.call(this)+" ("+this.title+")"; // (*)
    }
};

== Constructor functions ==

// Superclass
function Person(name) {
    this.name = name;
}
Person.prototype.describe = function() {
    return "Person called "+this.name;
};

// Subclass
function Worker(name, title) {
    Person.call(this, name);
    this.title = title;
}
Worker.prototype = Person.prototype <| {
    constructor: Worker,
    describe: function() {
        return Person.prototype.describe.call(this)+" ("+this.title+")";
    }
};
# Bob Nystrom (14 years ago)

On Tue, Jun 28, 2011 at 5:19 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

As an aside:

  • Static methods in Java always felt like a hack, because Java’s creators couldn’t bring themselves to support first-class functions.
  • Thus: I would much prefer to put Object.* methods into a module (once we have those). The same holds for “class methods” such as Math.*. There Math is used for namespacing and not really a class.

+1 for that.

However: Prototypes are already the core mechanism with regard to JS

inheritance, they are what stays after an instance has been created, they are actually used for instanceof tests, etc.

But before you've created an instance, it is pretty handy to have the constructor bound to a name so you can get to it. I don't think making the named object be the constructor means that they're more important, just that you generally have to go through them first to get to an instance. They get the name because they're the entrypoint.

// Superclass var Person = { constructor: function (name) { this.name = name; }, describe: function() { return "Person called "+this.name; } };

// Subclass var Worker = Person <| { constructor: function (name, title) { Person.constructor.call(this, name); this.title = title; }, describe: function () { return Person.describe.call(this)+" ("+this.title+")"; // (*) } };

For comparison's sake, here's that in the class proposal:

class Person {
  constructor(name) {
    this.name = name;
  }
  describe() {
    return "Person called " + this.name;
  }
}

class Worker extends Person {
  constructor(name, title) {
    super(name);
    this.title = title;
  }
  describe() {
    return super.describe() + " (" + this.title + ")";
  }
}

I definitely sympathize with the purity/simplicity argument against adding class syntax to JS, but for me, that code does read better with a dedicated declarative class syntax.

# Brendan Eich (14 years ago)

On Jun 28, 2011, at 4:41 PM, Bob Nystrom wrote:

From my view, JS-of-today is no less prototype-based than what Axel is proposing, it just wires things up a bit differently. Or is there something I'm missing?

Brendan here, you know, the idiot who perpetrated JS. You are not missing anything. I wired up functions as constructors with .prototype properties to mimic Java classes, and supported the new operator, from JS1.0 in 1995 on. I made JS prototypal but not Self-like (no copy message, no multiple parents, no prototypes-not-constructors).

So now that we've cleared this up ;-), can we agree that prototypes and closures rock, and that constructor functions are not going away?

If so, it seems to me the prototype-first idea is trying to roll a very large, still moving stone up a tall hill. Yes, you can use prototypes that way. Indeed nothing stops users of ES.next, assuming <| or whatever it'll be called, and super-in-object-initialisers, make it to the finish line.

But that may not suffice to get the cows off the classy path. It may just complicate new and instanceof, with few users benefiting. I claim we can do little to control the outcome in terms of adoption. The downrev browser problem, plus the classical OOP patterning in many devs' brains, count more than our exhortations.

Would it be a better world if JS had always named the prototype, wired up new C to use C.constructor, etc.? Maybe. But that road was not taken, and that makes all the difference.

# Axel Rauschmayer (14 years ago)

From: Brendan Eich <brendan at mozilla.com> Date: June 29, 2011 2:29:21 GMT+02:00

But that may not suffice to get the cows off the classy path. It may just complicate new and instanceof, with few users benefiting. I claim we can do little to control the outcome in terms of adoption. The downrev browser problem, plus the classical OOP patterning in many devs' brains, count more than our exhortations.

Would it be a better world if JS had always named the prototype, wired up new C to use C.constructor, etc.? Maybe. But that road was not taken, and that makes all the difference.

Agreed, that does indeed weigh heavily.

# Axel Rauschmayer (14 years ago)

But before you've created an instance, it is pretty handy to have the constructor bound to a name so you can get to it. I don't think making the named object be the constructor means that they're more important, just that you generally have to go through them first to get to an instance. They get the name because they're the entrypoint.

It depends on how you view instance creation:

Prototypes-as-classes: new C() means: (1) Create an instance o whose prototype is C. (2) Initialize the new instance via o.constructor().

Constructor functions: new C() means: (1) Create an instance o whose prototype is C.prototype. (2) Initialize it via the body of the constructor function (with |this| set up properly).

Thus:

  • Prototypes-as-classes: step (1) determines the name of the class (which – to me – makes more sense for instanceof and subclassing)
  • Constructor functions: step (2) determines the name of the class.
# Brendan Eich (14 years ago)

On Jun 28, 2011, at 5:45 PM, Axel Rauschmayer wrote:

But before you've created an instance, it is pretty handy to have the constructor bound to a name so you can get to it. I don't think making the named object be the constructor means that they're more important, just that you generally have to go through them first to get to an instance. They get the name because they're the entrypoint.

It depends on how you view instance creation:

Prototypes-as-classes: new C() means:

If there are arguments in between the ( and ), they are passed to o.constructor.

(1) Create an instance o whose prototype is C. (2) Initialize the new instance via o.constructor().

What if o.constructor returns another object than o? See below

Constructor functions: new C() means: (1) Create an instance o whose prototype is C.prototype. (2) Initialize it via the body of the constructor function (with |this| set up properly).

Step (2) could be exactly the same as the previous step (2), modulo the forgotten feature which is needed in both cases:

For a function C in JS today, the return value if an object trumps the new instance (and engines actually avoid creating a new instance if it isn't needed for this reason). So there's more to it than this two-step procedure.

Thus:

  • Prototypes-as-classes: step (1) determines the name of the class (which – to me – makes more sense for instanceof and subclassing)
  • Constructor functions: step (2) determines the name of the class.

I don't see how the step # determines the name of the class. That's backward.

Rather, the two different choices of value bound to the name of the abstraction (prototype vs. constructor) determine the steps.

The prototypes-as-classes approach makes new C(a,b) invoke C.constructor(a, b) with extra rules for object return value, which is a complexity. Especially for abstractions that want to be callable without 'new' to construct a new instance (Array, e.g.).

Intuitions vary. There's nothing magically simpler about either approach. The |this| binding for the constructor invocation can be done in exactly the same way in both cases, but you left out the return value substitution feature.

# Brendan Eich (14 years ago)

On Jun 28, 2011, at 5:55 PM, Brendan Eich wrote:

The prototypes-as-classes approach makes new C(a,b) invoke C.constructor(a, b) with extra rules for object return value, which is a complexity.

Sorry, that was unclear: I meant the indirection through .constructor was a complexity, not the substituted object return value which applies to both approaches.

Especially for abstractions that want to be callable without 'new' to construct a new instance (Array, e.g.).

This is a crucial case. User-defined functions can be new'ed or invoked without new. They have .prototypes. None of this is going away.

# Axel Rauschmayer (14 years ago)

Sorry for dragging this out. The following should be my last email in this thread.

The prototypes-as-classes approach makes new C(a,b) invoke C.constructor(a, b) with extra rules for object return value, which is a complexity. Especially for abstractions that want to be callable without 'new' to construct a new instance (Array, e.g.).

That is complexity that is added to make things compatible with constructor functions and current built-ins. If you have to factor that in, then it is indeed hard to argue in favor of prototypes-as-classes.

I don’t particularly like that most things are constructed via new, but that in a few cases, you can omit the “new” if you want to. I’m also trying to avoid the Array constructor (e.g. there is no way to create arrays such as [3] with it).

But I do understand that that’s how it is, that people use it and that there is nothing one can do about it.

If I summarize my perceived advantages as follows, then it becomes clear that they might not be worth the trouble of having two kinds of classes:

(1) Simpler instanceof (Contra: no need to look under the hood) (2) Simpler subclassing (Contra: not needed very often, super-references make most of the advantages go away). Minor inconvenience: having to set up C.prototype.constructor in the subclass. (3) Inheritance of class methods (Contra: I’ve only seen one case where this mattered – an inheritance API with an extend() method. Contra: property name pollution where constructors are currently used as namespaces).

Looking at these points, it becomes clear that with class literals as syntactic sugar, we get all of the advantages of prototypes-as-classes, without their disadvantage of having to support two kinds of classes. However, class literals are still a move away from the constructor as the dominant construct (conceptually, – nothing changes under the hood).

# Brendan Eich (14 years ago)

On Jun 28, 2011, at 6:33 PM, Axel Rauschmayer wrote:

Sorry for dragging this out. The following should be my last email in this thread.

Axel, thanks for interacting, nothing to apologize for here.

The prototypes-as-classes approach makes new C(a,b) invoke C.constructor(a, b) with extra rules for object return value, which is a complexity. Especially for abstractions that want to be callable without 'new' to construct a new instance (Array, e.g.).

That is complexity that is added to make things compatible with constructor functions and current built-ins. If you have to factor that in, then it is indeed hard to argue in favor of prototypes-as-classes.

There has to be a constructor function somewhere, somehow. The issue is what name you invoke it by when calling vs. constructing. With constructor-as-class it's the same name, the name of the abstraction denotes the constructor. With prototype-as-class, the two diverge -- you call C.constructor but you 'new C' (although you can spell out 'new C.constructor' too if you like).

I don’t particularly like that most things are constructed via new, but that in a few cases, you can omit the “new” if you want to. I’m also trying to avoid the Array constructor (e.g. there is no way to create arrays such as [3] with it).

I hate that particular misfeature of Array.

But I do understand that that’s how it is, that people use it and that there is nothing one can do about it.

The Array(length) botch aside, yes: many constructors are callable is functions and they construct in that case too.

If I summarize my perceived advantages as follows, then it becomes clear that they might not be worth the trouble of having two kinds of classes:

(1) Simpler instanceof (Contra: no need to look under the hood) (2) Simpler subclassing (Contra: not needed very often, super-references make most of the advantages go away). Minor inconvenience: having to set up C.prototype.constructor in the subclass. (3) Inheritance of class methods (Contra: I’ve only seen one case where this mattered – an inheritance API with an extend() method. Contra: property name pollution where constructors are currently used as namespaces).

Looking at these points, it becomes clear that with class literals as syntactic sugar, we get all of the advantages of prototypes-as-classes, without their disadvantage of having to support two kinds of classes. However, class literals are still a move away from the constructor as the dominant construct (conceptually, – nothing changes under the hood).

It's true, classes want to slide down various lower slopes, over time. The "lint brush" problem, I called it. We

# Bob Nystrom (14 years ago)

On Tue, Jun 28, 2011 at 6:38 PM, Brendan Eich <brendan at mozilla.com> wrote:

It's true, classes want to slide down various lower slopes, over time. The "lint brush" problem, I called it. We on TC39 are obligated to hold the line.

Have you seen this occur in practice? My impression from C++ and C# is that most of the language addition over time was at the expression or statement level and not so much in class bodies.

# Brendan Eich (14 years ago)

On Jun 28, 2011, at 6:45 PM, Bob Nystrom wrote:

On Tue, Jun 28, 2011 at 6:38 PM, Brendan Eich <brendan at mozilla.com> wrote: It's true, classes want to slide down various lower slopes, over time. The "lint brush" problem, I called it. We on TC39 are obligated to hold the line.

Have you seen this occur in practice? My impression from C++ and C# is that most of the language addition over time was at the expression or statement level and not so much in class bodies.

I meant JS classes. We have traits waiting in the wings. Trademarking/guards have designs on classes. Something else lurks, I forget at the moment.

# Axel Rauschmayer (14 years ago)

I’m not arguing in favor of prototypes-as-classes, just against the assertion, below.

There has to be a constructor function somewhere, somehow. The issue is what name you invoke it by when calling vs. constructing. With constructor-as-class it's the same name, the name of the abstraction denotes the constructor. With prototype-as-class, the two diverge -- you call C.constructor but you 'new C' (although you can spell out 'new C.constructor' too if you like).

let o = new X(...);

  • Constructor functions: X is the name of the construct that initializes the instance. Implicit/hidden: Specifying the prototype.
  • Prototypes-as-classes: X is the name of the prototype of the new instance. Implicit/hidden: Invoking the method constructor().

Both have merit, I don’t see either one as being more elegant.

Purely subjectively, I find that the prototype is the thing that stays and lasts, while initialization only happens once. Obviously, constructor fans disagree with this assertion.

But if you like class literals syntactically then maybe we’re not even disagreeing here. Their clear advantage (over PaCs) is that they desugar to something that is perfectly compatible with current semantics.

# Brendan Eich (14 years ago)

On Jun 28, 2011, at 8:36 PM, Axel Rauschmayer wrote:

I’m not arguing in favor of prototypes-as-classes, just against the assertion, below.

I think you missed my point. You can't call a prototype -- it's generally not callable. But in the PaC proposal you can 'new' it.

# Axel Rauschmayer (14 years ago)

That is complexity that is added to make things compatible with constructor functions and current built-ins. If you have to factor that in, then it is indeed hard to argue in favor of prototypes-as-classes.

There has to be a constructor function somewhere, somehow. The issue is what name you invoke it by when calling vs. constructing. With constructor-as-class it's the same name, the name of the abstraction denotes the constructor. With prototype-as-class, the two diverge -- you call C.constructor but you 'new C' (although you can spell out 'new C.constructor' too if you like).

Rephrasing my earlier response in reaction to your feedback:

I see it as a separation of concerns: (1) Instantiation: Create a new instance, give it the proper prototype. (2) Initialization: Set up the instance variables.

Smalltalk: (1) Method "new" (2) Method "initialize"

Prototypes-as-classes (starting from scratch, with a clean protocol that has no notion of constructor functions): new C(x,y) desugars (conceptually!) to (new C).constructor(x,y) With: (1) Instantiation: new C (2) Initialization: .constructor(x, y)

I find that PaCs untangle (1) and (2), because method constructor() only has to play role (2), whereas constructor functions have to sometimes play both roles, sometimes only role (2) (when called from a sub-constructor).

# Brendan Eich (14 years ago)

On Jun 29, 2011, at 1:48 PM, Axel Rauschmayer wrote:

Prototypes-as-classes (starting from scratch, with a clean protocol that has no notion of constructor functions): new C(x,y) desugars (conceptually!) to (new C).constructor(x,y) With: (1) Instantiation: new C (2) Initialization: .constructor(x, y)

That's all neat in a kind of desugaring ftw, nerdcore way, but tl;dr -- users don't care and they want to write new C and have it just work (mostly; granted some crack the code, for a few good and bad purposes).

I find that PaCs untangle (1) and (2), because method constructor() only has to play role (2), whereas constructor functions have to sometimes play both roles, sometimes only role (2) (when called from a sub-constructor).

I see your point, but most of the time this is detail to be abstracted away from, not modeled in one's head every time one writes new C or even C().

Anyway, the die was cast long ago.

# Axel Rauschmayer (14 years ago)

That's all neat in a kind of desugaring ftw, nerdcore way, but tl;dr -- users don't care and they want to write new C and have it just work (mostly; granted some crack the code, for a few good and bad purposes).

Note that I am arguing from the perspective of a language that only has PaCs, versus a language that only has constructor functions. If you don’t, then we don’t disagree.

You argue that constructor functions are more intuitive at the user level (to all people) and that PaCs wouldn’t “just work” and that’s what I disagree with.

With either new Constructor(x,y) or with new Prototype(x,y) there are things you leave out (abstract away from). It is a matter of preference what those things should be:

(1) let o = new Constructor(x,y): hides/obscures the fact that the new instance has the prototype Constructor.prototype. (2) let o = new Prototype(x,y): hides the fact that method constructor() is called.

Anyway, the die was cast long ago.

I agree that this might be one of those cases where the cost of breaking compatibility is not worth the benefits (especially if we even can’t agree that there are benefits ;-)

# Brendan Eich (14 years ago)

On Jun 29, 2011, at 2:24 PM, Axel Rauschmayer wrote:

That's all neat in a kind of desugaring ftw, nerdcore way, but tl;dr -- users don't care and they want to write new C and have it just work (mostly; granted some crack the code, for a few good and bad purposes).

Note that I am arguing from the perspective of a language that only has PaCs, versus a language that only has constructor functions. If you don’t, then we don’t disagree.

That was not clear, because we keep coming back to JS as it is. That's where I want to land, so arguing about what-if's and might-have-been's is not my cup of tea.

You argue that constructor functions are more intuitive at the user level (to all people) and that PaCs wouldn’t “just work”

I never wrote anything like either of those things, certainly not "to all people". Indeed I was the one pointing out that your universals did not apply to all people.

Last concrete disagreement we had was over new C() vs. C() in the current language being notably different from new C() vs. C.constructor() in the alternate-reality language with prototypes as classes.

# Axel Rauschmayer (14 years ago)

Last concrete disagreement we had was over new C() vs. C() in the current language being notably different from new C() vs. C.constructor() in the alternate-reality language with prototypes as classes.

As in “don’t mix too well”. I agree with that. That will be a challenge for class literals. But the abstraction argument might apply to both cases: If people see a class C, they just want to instantiate it via new C(x,y). As long as instanceof works accordingly afterwards, I’m not sure much has changed superficially.

# Irakli Gozalishvili (14 years ago)

There have been multiple thread on this and I did not know were to comment, so I'll add few comments here.

I think that whole constructors as classes business in JS is confusing for everyone coming to JS, and they only understand it after understanding confusing constructor- prototype relationship.

It is true that people find

var point = new Point(x, y) point instanceof Point

intuitive but that's because it looks like Java. Also they are quite puzzled to find out that in some cases:

var point = Point(x, y)

works the same.

On the other hand people may find Point.new(x, y) as intuitive, as they will think of ruby instead of java. Also, I do not think there is a point to use constructor with all it's confusing prototype relationships for initialization in "prototypes as classes", just use initialize method to do that.

Is it really too late to undo constructor as classes ? There are other proposals like rest and spread with a hope that arguments will die off some day. Why this would be different ?

Finally I believe that killing constructors will make js straight forward to understand and lighter in syntax.

And not everyone tries to emulate classical inheritance today: javascript.crockford.com/prototypal.html

-- Irakli Gozalishvili Web: www.jeditoolkit.com Address: 29 Rue Saint-Georges, 75009 Paris, France (goo.gl/maps/3CHu)

# Axel Rauschmayer (14 years ago)

On the other hand people may find Point.new(x, y) as intuitive, as they will think of ruby instead of java.

As an aside, you can see API code for the above functionality here:

# Bill Frantz (14 years ago)

On 6/30/11 at 17:46, rfobic at gmail.com (Irakli Gozalishvili) wrote:

I think that whole constructors as classes business in JS is confusing for everyone coming to JS, and they only understand it after understanding confusing constructor- prototype relationship.

I don't know about others, but I need to have a mental model of what is going on in order to feel comfortable using a language/API/library. I didn't really feel comfortable with the ADD instruction until I understood how addition was implemented at the circuit level. I didn't feel comfortable with C++ classes until I understood the basic layout of an instance. I think is a matter of believing I can avoid stepping on any landmines that may be lying about.

I have a prejudice that the model should match the reality, but I accept that an optimizer may do something completely different if it still implements the model.

Simple is nice, but accurate is more important.

Cheers - Bill


Bill Frantz | The first thing you need when | Periwinkle (408)356-8506 | using a perimeter defense is a | 16345 Englewood Ave www.pwpconsult.com | perimeter. | Los Gatos, CA 95032

# Thaddee Tyl (14 years ago)

From: Axel Rauschmayer <axel at rauschma.de>

On the other hand people may find Point.new(x, y) as intuitive, as they will think of ruby instead of java.

As an aside, you can see API code for the above functionality here:

-- Dr. Axel Rauschmayer

I wish to assert my support for the dl.2ality.com/dl/2011/06/Proto.js syntax. It looks very clean to me.

# Angus Croll (14 years ago)

I think its important to make a distinction between chained and unchained prototypes...

By unchained prototypes I mean those that directly extend Object.prototype (note that that every built-in prototype specified by ES5 is unchained). Unchained prototypes are gorgeous - one instance defining the properties of many. And it's really not that hard...once you understand the difference between Constructor.prototype and instance.proto (or its symbolic successor) you're all set. The concept itself is very simple - a dynamic archetype to be shared by all my instances: my prototype changes, my instances know about it. I would not want to hide such a smart, simple concept behind the more complex and (in this scenario) less meaningful concept of class.

By chaining prototypes together we can mimic classical inheritance: objA ->

objA.prototype -> objB.prototype -> Object.prototype. As everyone knows

prototype-chaining is non-trivial (though made easier by es5's Object.create) and this is the rationale for a class literal in es6: let the language absorb the complexity and let the engines make it faster.

This would be fine and good if classical inheritance were a good fit for JavaScript, but I don't think it is:

a) The syntax may be fixable but the logic isn't. Inheritance hierarchies are gnarly.

  • Nine times out of ten it's a re-use technique masquerading as a classification of types. Yes a shark is a fish (thanks @jb_briaud) and if you ever need a fish and a shark in your JavaScript then classical inheritance is for you. However if its re-use you want (and it almost always is) then implementation inheritance is tedious at best, a death-march at worst, because sooner or later the hierarchy becomes arbitrary and self defeating. Single-root trees are good for representing ancestry, I can attest that they are not good for defining re-use.

  • Even when the intention is to model relationships between types the process is awkward. Most types have multiple characteristics but classical JavaScript allows for only one parent. Moreover classical hierarchies must be implemented top-down - a superclass must be created before it can be extended - yet classes closer to the root are by nature more generic and abstract and are more easily defined after we have more knowledge of their concrete classes subclasses. Hierarchy definition is by necessity iterative which does not play well with an existing code base.

b) JavaScript already has cleaner, lightweight, more flexible options:

  • Mixins allow objects to borrow from an unlimited number of other objects regardless of lineage, while still allowing for organization of functions by type. Mixins are way more flexible than inheritance chains - we can buy behavior types on demand with zero impact on the larger model. Prototypes facilitate Mixins, since a mixin is normally applied to an unchained prototype, another reason why new developers should continue to understand prototypes - 'class' is not a helpful concept where Mixins are concerned.

  • Delegation (call/apply) lets you grab any function whenever you need it. Classical Inheritance is necessary when your language constrains function usage by type hierarchy (Java, C++). JavaScript doesn't do this and doesn't need it.

  • The module pattern allows a chunk of data and functionality to be parceled into an object which can be used anywhere without compromising the integrity of the module's code. This ability to package up mini-eco-systems frees the developer from constraints of context that would occur if the same logic were defined as part of a class.

c) How does the class concept square (no pun) with objects like Math, which is not a constructor and \therefore will not, I assume be considered a class? Is Math.pow a const now? If so how will that work when Math is not a class? If not that seems like an inconsistency.

tl;dr.... Unchained prototypes: Invaluable concept; would be obfuscated by class syntax. Chained prototypes: Class literals might fix the syntax but the not the concept. JavaScript has better, easier tools already.


I don't buy the argument (advanced by some in this discussion group) that as long as nothing is removed from the language, new features can't hurt. JavaScript has fewer keywords and a more lightweight syntax than almost any other mainstream language. For many of us that is hugely appealing and paradoxically it makes JavaScript enormously expressive. C++ syntax has everything but the kitchen sink (maybe that too) but as a language I suspect it bores most of use to sobs. Less is more.

Earlier in this thread, Brendan said "I claim we can do little to control the outcome in terms of adoption...the classical OOP patterning in many devs' brains, count more than our exhortations." That's sad because I think many ex-classical programmers (myself included) grew tired of implementation inheritance as the dominant paradigm and gravitated to JavaScript (and also functional languages) as a refuge from the stodgy tyranny of bloated hierarchies.

The classical OOP ship may have sailed, but its taking on water. I'm not convinced that "users don't care and they want to write new C and have it just work" - I think that does a disservice to our community. JavaScript is on the upward path, Java on the down. We're fostering a generation of converts from classical OOP to something more elegant, agile and useful. It would be a shame to throw in the towel now.

Angus

# Brendan Eich (14 years ago)

Remember, I'm the one who is a little sad about classes making the ES.next cutoff. Also I agree with dherman that they have too many option issues and would benefit from reductions ("minimal classes", see ).

On Jul 2, 2011, at 11:46 AM, Angus Croll wrote:

By unchained prototypes I mean those that directly extend Object.prototype (note that that every built-in prototype specified by ES5 is unchained). Unchained prototypes are gorgeous - one instance defining the properties of many. And it's really not that hard...once you understand the difference between Constructor.prototype and instance.proto (or its symbolic successor) you're all set. The concept itself is very simple - a dynamic archetype to be shared by all my instances: my prototype changes, my instances know about it. I would not want to hide such a smart, simple concept behind the more complex and (in this scenario) less meaningful concept of class.

The difference is just induction from 1 to 2 (assuming you don't extend Object.prototype, which ES5 allows you to do now without breaking for-in loops -- from 2 to 3 if you do extend Object.prototype).

Is a difference in degree really a difference in kind or meaning? I don't think so.

Some empirical results:

5.3 Prototype Chains One higher-level metric is the length of an object’s prototype chain, which is the number of prototype objects that may potentially be traversed in order to find an object’s inherited property. This is roughly comparable to metrics of the depth of class hierarchies in class-based languages, such as the Depth of Inheritance (DIT) metric discussed in [23]. Studies of C++ programs mention a maximum DIT of 8 and a median of 1, whereas Smalltalk has a median of 3 and maximum of 10. Figure 6 shows that in all but four sites, the median prototype chain length is 1. Note that we start our graph at chain length 1, the minimum. All objects except Object.prototype have at least one prototype, which if unspecified, defaults to the Object.prototype. The maximum observed prototype chain length is 10. The majority of sites do not seem to use prototypes for code reuse, but this is possibly explained by the existence of other ways to achieve code reuse in JavaScript (i.e., the ability to assign closures directly into a field of an object). The programs that do utilize prototypes have similar inheritance properties to Java [23].

Figure 6:

from

An Analysis of the Dynamic Behavior of JavaScript Programs Gregor Richards Sylvain Lebresne Brian Burg Jan Vitek S3 Lab, Department of Computer Science, Purdue University, West Lafayette, IN {gkrichar,slebresn,bburg,jv}@cs.purdue.edu

Unit-length prototype chain, including Object.prototype -- meaning object initialisers and other new Object uses, not (new C) where C is a user-defined function -- cover a lot of ground.

But the truth is messier. Some JS libraries (and possibly compilers) use deeper prototype chains.

Calling these a mistake across the board is a bit much.

  • Nine times out of ten it's a re-use technique masquerading as a classification of types. Yes a shark is a fish (thanks @jb_briaud) and if you ever need a fish and a shark in your JavaScript then classical inheritance is for you. However if its re-use you want (and it almost always is) then implementation inheritance is tedious at best, a death-march at worst, because sooner or later the hierarchy becomes arbitrary and self defeating. Single-root trees are good for representing ancestry, I can attest that they are not good for defining re-use.

I'm as big a critic of classical OOP as anyone. No argument there, as a generalization.

But JS has C.prototype for user-defined function C, and that makes a two-level prototype chain. Some cohort then go beyond that to make three- or deeper chains. CoffeeScript makes this all pleasant. Why shouldn't ES.next?

  • Even when the intention is to model relationships between types the process is awkward. Most types have multiple characteristics but classical JavaScript allows for only one parent. Moreover classical hierarchies must be implemented top-down - a superclass must be created before it can be extended - yet classes closer to the root are by nature more generic and abstract and are more easily defined after we have more knowledge of their concrete classes subclasses. Hierarchy definition is by necessity iterative which does not play well with an existing code base.

No argument from me. Bottom-up, inductive, or constructive learning and system-building are usually best.

We shouldn't make a religious war here. JS has "classes" in its spec for all the built-ins, disclosed by Object.prototype.toString().slice(8,-1). These are represented in-language by constructor functions. Users define constructor functions too.

Since you are not out to prevent users from defining such "classes", why is syntax for the clichéd common version of prototypal inheritance beyond the pale?

b) JavaScript already has cleaner, lightweight, more flexible options:

  • Mixins allow objects to borrow from an unlimited number of other objects regardless of lineage, while still allowing for organization of functions by type. Mixins are way more flexible than inheritance chains - we can buy behavior types on demand with zero impact on the larger model. Prototypes facilitate Mixins, since a mixin is normally applied to an unchained prototype, another reason why new developers should continue to understand prototypes - 'class' is not a helpful concept where Mixins are concerned.

Mixins require copying properties, and there's nothing lightweight (in usable syntax or runtime cost) in that.

  • Delegation (call/apply) lets you grab any function whenever you need it. Classical Inheritance is necessary when your language constrains function usage by type hierarchy (Java, C++). JavaScript doesn't do this and doesn't need it.

This is logically unsound. Java (let's skip C++, it has functional programming and even closures in the latest and greatest) lacks first class functions (historically; haven't paid attention to the latest), so it relies on classes.

This does not prove that functional languages don't need to support classes as well. Many do support classes, going back to Common Lisp and other Lisps where macros and syntax extensions make it straightforward to provide the kind of sugar proposed for Harmony's classes.

  • The module pattern allows a chunk of data and functionality to be parceled into an object which can be used anywhere without compromising the integrity of the module's code. This ability to package up mini-eco-systems frees the developer from constraints of context that would occur if the same logic were defined as part of a class.

This an independent point. ES.next adds modules for such use-cases. That does not mean prototypal inheritance sugar via classes has no benefit.

c) How does the class concept square (no pun) with objects like Math, which is not a constructor and \therefore will not, I assume be considered a class? Is Math.pow a const now? If so how will that work when Math is not a class? If not that seems like an inconsistency.

Math is not now and never has been a class in the "represented by a constructor function, with a .prototype" sense. Yes, it has a [[Class]] internal property discoverable via Object.prototype.toString, "Math". This is something we're working to make meta-programmable independently of other interpretations of [[Class]] anyway, for DOM self-hosting. See www.wirfs-brock.com/allen/things/es5-technial-notes-and-resources

tl;dr.... Unchained prototypes: Invaluable concept; would be obfuscated by class syntax. Chained prototypes: Class literals might fix the syntax but the not the concept. JavaScript has better, easier tools already.

The concept of "chained prototypes" has been in JS from day one. Class syntax adds nothing new semantically.

I don't buy the argument (advanced by some in this discussion group) that as long as nothing is removed from the language, new features can't hurt. JavaScript has fewer keywords and a more lightweight syntax than almost any other mainstream language. For many of us that is hugely appealing and paradoxically it makes JavaScript enormously expressive. C++ syntax has everything but the kitchen sink (maybe that too) but as a language I suspect it bores most of use to sobs. Less is more.

Sure, we are avoiding the kitchen sink. Again, I'm not in favor of classes with so many open issues -- for them to make progress, we need to simplify and also sanity-check against how prototypal inheritance is used (e.g., with respect to class-side inheritance).

But this does not mean Object.create is enough, any more than the module pattern, a leaky abstraction based on closures, obviates the need for a module system.

Earlier in this thread, Brendan said "I claim we can do little to control the outcome in terms of adoption...the classical OOP patterning in many devs' brains, count more than our exhortations." That's sad because I think many ex-classical programmers (myself included) grew tired of implementation inheritance as the dominant paradigm and gravitated to JavaScript (and also functional languages) as a refuge from the stodgy tyranny of bloated hierarchies.

I think you're mixing up categories. It's not up to TC39 to change developers' brains by leaving out conveniences for common patterns, if those patterns are not inherently unsafe or otherwise bad.

Even unchained prototype use-cases where there's a constructor function to initialize per-instance state benefit from class syntax.

The classical OOP ship may have sailed, but its taking on water. I'm not convinced that "users don't care and they want to write new C and have it just work" - I think that does a disservice to our community. JavaScript is on the upward path, Java on the down. We're fostering a generation of converts from classical OOP to something more elegant, agile and useful. It would be a shame to throw in the towel now.

At this point you've lost me. In no sense is class as prototypal sugar "classical OOP" that differs observably from prototypal inheritance in JS. Even class-side (constructor) inheritance would be done via <| or proto. There's nothing new and different here. No vtbls, no interfaces as in Java, no non-property "fields" or "final methods".

I think you're mixing up good skepticism of OOP-is-all-you-need with classes-as-prototypal-sugar FUD. Would changing "class", the reserved word (we can't really unreserve and pick another), to "constructor", help? If not, why not?

Allen's efforts with <| and generalized 'super' also reinforce the point that classes are prototypal inheritance sugar, and only that. Jeremy's work with CoffeeScript demonstrates the same point. There is no magic runtime implementing classical OOP differently, other than class-side (constructor) inheritance via a little property-copying helper code, for want of <| or universal writable proto.

I think you are protesting too much.

# Brendan Eich (14 years ago)

On Jul 2, 2011, at 2:04 PM, Brendan Eich wrote:

Remember, I'm the one who is a little sad about classes making the ES.next cutoff. Also I agree with dherman that they have too many option

"open", of course

issues and would benefit from reductions ("minimal classes", see ).

esdiscuss/2011-June/015559 et seq.

# Angus Croll (14 years ago)

Good to know you share some concerns (esp. with respect to class side inheritance, too many options, and classical OOP as a dominant methodology).

The difference is just induction from 1 to 2 (assuming you don't extend

Object.prototype, which ES5 allows you to do now without breaking for-in loops -- from 2 to 3 if you do extend Object.prototype). Is a difference in degree really a difference in kind or meaning? I don't think so

I'd argue its a difference in meaning - the proof is that if you removed 'extends' and 'super', the class sugar would support unchained prototypes only. In fact maybe I could live with 'class' alone (though it might give noobs the wrong idea about JavaScript vs Java) - but it's 'extends' and 'super' which add legitimacy to the classical OOP approach.

Some empirical results: "...Studies of C++ programs mention a maximum DIT

of 8 and a median of 1, whereas Smalltalk has a median of 3, and maximum of 10. Figure 6 shows that in all but four sites, the median prototype chain length is 1

Sure, proto chaining was always supported but my guess is that officially sanctioning 'extends' and 'super' will raise the median prototype chain length through the roof - and I can't see how that's good for the language

Mixins require copying properties, and there's nothing lightweight (in

usable syntax or runtime cost) in that.

As an aside I'm not wild about the mixin by copying strategy and have advocated mixin by delegation as an alternative. javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins

I think you are protesting too much.

Yeah you might be right. I hope so. And I'm probably biased because Java taught me to abhor impl. inheritance On the other hand I held back from writing to this list for the longest time until I was certain I had a serious concern. You made a great language, I'd be sad to see it misused because folks were drawn to the wrong kind of sugar - I know there are a bunch of others who don't write to this list but share my concern.

Angus

# Brendan Eich (14 years ago)

On Jul 2, 2011, at 4:10 PM, Angus Croll wrote:

I'd argue its a difference in meaning - the proof is that if you removed 'extends' and 'super', the class sugar would support unchained prototypes only. In fact maybe I could live with 'class' alone (though it might give noobs the wrong idea about JavaScript vs Java) - but it's 'extends' and 'super' which add legitimacy to the classical OOP approach.

Indeed Waldemar has proposed (starting at the July 2008 "Harmony" TC39 meeting in Oslo) that we cut back to zero-inheritance classes.

OTOH Mark has worked on a traits-based mixin extension to class syntax based on the work he and Tom Van Cutsem have done (traitsjs.org).

Classes don't just mean one thing. The "lint brush" effect whereby the syntax picks up sevearl styles of inheritance is a real cost to consider.

Some empirical results: "...Studies of C++ programs mention a maximum DIT of 8 and a median of 1, whereas Smalltalk has a median of 3, and maximum of 10. Figure 6 shows that in all but four sites, the median prototype chain length is 1

Sure, proto chaining was always supported but my guess is that officially sanctioning 'extends' and 'super' will raise the median prototype chain length through the roof - and I can't see how that's good for the language

This is an odd point of view. You think convenient syntax for something possible already will make more silly, tyrannical, ultimately costly single-inheritance hierarchies -- and the lack of such syntax will help steer people toward mixin libraries (all the major JS libraries have some such system, IIRC)?

Why would people overuse classes and single-inheritance? If the convenience of the syntax is an attractive nuisance, should we make convenient declarative syntax for mixins? That was hung off the class proposal but separated out and deferred to help get the basics into ES.next.

It's hard to quantify risks here.

Mixins require copying properties, and there's nothing lightweight (in usable syntax or runtime cost) in that.

As an aside I'm not wild about the mixin by copying strategy and have advocated mixin by delegation as an alternative. javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins

That's still copying, but into the prototype for best sharing (and the power-constructor pattern helps share once-per-mixin-per-method closures).

What if there are conflicts? traitsjs.org takes a "high integrity" approach, throwing on conflict and freezing aggressively. There are many choices. Just letting things silently conflict with last mixin winning seems like a footgun. A composition-time check that throws, but without freezing for all time after, seems like a good compromise.

I think you are protesting too much.

Yeah you might be right. I hope so. And I'm probably biased because Java taught me to abhor impl. inheritance

I learned the hard way via C++ :-P.

On the other hand I held back from writing to this list for the longest time until I was certain I had a serious concern. You made a great language, I'd be sad to see it misused because folks were drawn to the wrong kind of sugar - I know there are a bunch of others who don't write to this list but share my concern.

Thanks for articulating it. We need to work on quantifying (however coarsely) the trade-offs with no classes vs. prototypal-only classes vs zero-inheritance vs traits.

# Bob Nystrom (14 years ago)

I agree with Brendan's replies, but just to add some more color:

On Sat, Jul 2, 2011 at 11:46 AM, Angus Croll <anguscroll at gmail.com> wrote:

The concept itself is very simple - a dynamic archetype to be shared by all my instances: my prototype changes, my instances know about it. I would not want to hide such a smart, simple concept behind the more complex and (in this scenario) less meaningful concept of class.

To me, that's pretty close to a "class": a thing that describes the properties and behavior of a set of objects. Sure, you can roll in all of the other shenanigans that Java et. al. include (final, static, nested classes, etc.) but the real kernel is "we have a bunch of objects that are similar and we want to define that similarity in one place."

JS does so through MyType.prototype and class-based languages do it in a class definition. The class syntax proposal just gives you a nicer notation for the former.

a) The syntax may be fixable but the logic isn't. Inheritance hierarchies

are gnarly.

  • Nine times out of ten it's a re-use technique masquerading as a classification of types.

Agreed, completely. Deep inheritance hierachies are almost always a guarantee of misery. But the 1 in 10 case where OOP inheritance has been a big success is... UI frameworks. Considering how important that use case is to JavaScript, supporting it gracefully seems reasonable to me.

  • Mixins allow objects to borrow from an unlimited number of other objects

regardless of lineage, while still allowing for organization of functions by type.

I'm a huge fan of mixins/traits/multiple-inheritance ( journal.stuffwithstuff.com/2011/02/21/multiple-inheritance-in-javascript). I very often find single inheritance frustrating.

I don't like "classic" mixins because they make it too easy for a later mixin to stomp over the methods of an earlier one. Traits solve that nicely, and I'd like to see them in JS at some point. If we had them, I'd use them heavily.

At the same time, I probably bear the most responsibility for talking Mark into taking them out of the class proposal. My reasoning was this:

  1. Getting any declarative "kind of thing" syntax into JS is fantastically hard, as you can see.
  2. We'll need something like that to hang traits/mixins off of.
  3. Traits are complex enough that they may sink the whole proposal.

So my take was: start with a simple class syntax that lets you declaratively express how JS is being written right now with single inheritance. If we can get that into ES6, we'll be in a much better position to try to work traits into ES7. (And even if traits never get in, I think we'll have made a large fraction of the world's JS simpler and easier to read.)

It's also worth remembering that this class proposal doesn't take away any of the flexibility JS has. Your mixin system and my weird little MI one will still work in an ES6 that has a class syntax. It's just that if you're following the common path of defining constructor functions and adding functions to its .prototype, then you'll have a simpler syntax for doing so.

We're paving a footpath here, not building a fence around it. You're always free to wander off it into the forest.

Earlier in this thread, Brendan said "I claim we can do little to control

the outcome in terms of adoption...the classical OOP patterning in many devs' brains, count more than our exhortations." That's sad because I think many ex-classical programmers (myself included) grew tired of implementation inheritance as the dominant paradigm and gravitated to JavaScript (and also functional languages) as a refuge from the stodgy tyranny of bloated hierarchies.

I too grew tired of implementation inheritance, but not classes. I still really like class-based languages, I just like ones like Python and C# (and JS if you're loose about "class") that augment that with first-class functions and other ways to compose behavior.

JavaScript is on the upward path, Java on the down. We're fostering a

generation of converts from classical OOP to something more elegant, agile and useful. It would be a shame to throw in the towel now.

I think you're making a false dichotomy here. "Classes" doesn't always mean Java's "sign everything in triplicate, here comes the Carpal tunnel" level of boilerplate. Nor does it always mean C++'s "better write a style guide before you coworker uses some hideous dark corner of the language" level of complexity/flexibility ("comflexibility"?). Consider Smalltalk, CLOS, Python, Ruby, or C#. You can have classes in a beautiful language.

# Dmitry A. Soshnikov (14 years ago)

On 12.07.2011 0:18, Bob Nystrom wrote:

I agree with Brendan's replies, but just to add some more color:

On Sat, Jul 2, 2011 at 11:46 AM, Angus Croll <anguscroll at gmail.com <mailto:anguscroll at gmail.com>> wrote:

The concept itself is very simple - a dynamic archetype to be
shared by all my instances: my prototype changes, my instances
know about it. I would not want to hide such a smart, simple
concept behind the more complex and (in this scenario) less
meaningful concept of class.

To me, that's pretty close to a "class": a thing that describes the properties and behavior of a set of objects. Sure, you can roll in all of the other shenanigans that Java et. al. include (final, static, nested classes, etc.) but the real kernel is "we have a bunch of objects that are similar and we want to define that similarity in one place."

JS does so through MyType.prototype and class-based languages do it in a class definition. The class syntax proposal just gives you a nicer notation for the former.

It turns our that making decisions though the prism of some concrete implementation, programmers make incorrect judgment about some techniques of code reuse. Ideological code reuse "class" may have several implementations. In static second-class classes, it can be method-calls technique with static (and known) addresses. In contrast in first-class dynamic systems it can be message passing technique based on delegation (i.e. based on prototypes).

But still, regardless implementation, the concept of a class assumes just a classified code reuse, no more, no less. That is, (1) creation of many instances with the same state, (2) ability to mark the instances with the classification tag.

Thus, the (2) is not even required. Once you've started to create many instances with the same state -- you already program with the concept of a class, i.e. using classified code reuse. E.g.:

var pointA= {x: 10, y: 20}; var pointB= {x: 30, y: 40}; var pointC= {x: 50, y: 60};

I already created (in mind, virtually) the concept of a point class. That's it.

And all the other stuff -- is just the syntactic improvements of this concept. There no other reasons. Just very practical reason -- efficient code reuse. Don't wanna write this copy-pasted points -- move them to a function (as you always do, when a code is repeated more than two times) -- and you get constructors of JS. Don't like that classification is called not with the word "class" -- rename it to "class" and add another sugar. So -- just the sugar matters. And without the sugar, that's said -- only practical code reuse technique.

I hope this small table will help: gist.github.com/977034 Once again -- statics method-calls vs. dynamics and delegation/message passing -- are just the techniques. But a "class" is a concept, which can be implemented in any form. From this viewpoint JS had, have, and will have (sugared now) classes. Because a "class" is not a special keyword "class" in a lang, it's not the ability to create classes with this keyword, but class -- is the ability to classify. And how it's done -- either with "class" keyword or with the "function" keyword

# Axel Rauschmayer (14 years ago)

I am warming up to class literals: they should help IDEs and will make future extensions easier (mixins etc.). Note that they are not mutually exclusive with prototypes as classes.