Skip to content

Instantly share code, notes, and snippets.

@goatslacker
Forked from BrendanEich/minimalist-classes.js
Created November 2, 2011 08:04

Revisions

  1. @BrendanEich BrendanEich revised this gist Nov 2, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion minimalist-classes.js
    Original file line number Diff line number Diff line change
    @@ -216,7 +216,7 @@ class Monster {

    attack(target) {
    log("The monster attacks " + target);
    return attackHelper(target);
    return @attackHelper(target);
    }

    isAlive() {
  2. @BrendanEich BrendanEich revised this gist Nov 2, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion minimalist-classes.js
    Original file line number Diff line number Diff line change
    @@ -58,7 +58,7 @@ class Color {
    ...
    }

    // REMOVED: prototype data properties are a footgun, Alex Russell points to
    // DUBIOUS: prototype data properties are a footgun, Alex Russell points to
    // hazards involving mutable objects. I observe that non-writable prototype
    // data properties are unheard of, likely because you can't shadow them by
    // assignment.
  3. @BrendanEich BrendanEich revised this gist Nov 2, 2011. 1 changed file with 31 additions and 18 deletions.
    49 changes: 31 additions & 18 deletions minimalist-classes.js
    Original file line number Diff line number Diff line change
    @@ -45,25 +45,28 @@
    // First, basic usage from a real-world library (Three.js)

    class Color {

    // Commas are a curse in class syntax, not a blessing as in current object
    // literal syntax. But with method definitions as proposed for ES6 object
    // literals, commas could be optional. Allen proposed this at the last TC39
    // meeting.
    //
    // Problems if commas are optional after data properties, but after methods
    // should be ok.

    constructor(hex) {
    ...
    }

    // Prototype data properties, introduced by var or const at class body level.
    // Rationale: object literal syntax requires comma separation and has no way
    // to define a const that's clearly in Harmony. Classy languages do not need
    // no steenkin' commas, but we do want const in ES.next.
    // REMOVED: prototype data properties are a footgun, Alex Russell points to
    // hazards involving mutable objects. I observe that non-writable prototype
    // data properties are unheard of, likely because you can't shadow them by
    // assignment.
    //
    // An objection even I have made: we should not conflate property definition
    // syntax with lexical binding declaration syntax. But the cat's out of the
    // bag with 'var' in global code in ES1-5, and it's hard to beat "const" as
    // the keyword for initialize-only data properties.
    // But if we must have them, we could use object literal syntax for writable
    // data properties as follows:
    //
    // One fair objection: this syntax may mislead people into thinking methods
    // lexically close over prototype properties declared this way. A calculated
    // risk, what can I say?

    var r = 1, g = 1, b = 1;
    r: 1, g: 1, b: 1,

    copy(color) {
    ...
    @@ -98,7 +101,7 @@ class Fox extends Animal {
    //
    // http://wiki.ecmascript.org/doku.php?id=harmony:object_literals#individual_extension_summary

    static const CONSTANT = 42;
    static const CONSTANT: 42,

    static classMethod() {
    ...
    @@ -163,9 +166,9 @@ class Monster {
    this.health = value;
    }

    var numAttacks = 0;
    numAttacks: 0,

    const attackMessage = "The monster hits you!";
    const attackMessage: "The monster hits you!"

    }

    @@ -188,6 +191,11 @@ class Monster {
    // (properties to most people) are quite rare. Use ES5's Object.defineProperty
    // if you must. This avoids ugly fights about read before constant instance var
    // initialization too.
    //
    // OOP often requires factoring common subroutines into private methods, so the
    // 'private' keyword should be supported as a method prefix, just as 'static'
    // is. Should we allow 'static private' methods too? Same rationale applies if
    // you have two static (public) methods with a large common subroutine.

    class Monster {

    @@ -202,8 +210,13 @@ class Monster {
    return @name === other@name;
    }

    private attackHelper(target) {
    ...
    }

    attack(target) {
    log("The monster attacks " + target);
    return attackHelper(target);
    }

    isAlive() {
    @@ -217,9 +230,9 @@ class Monster {
    @health = value;
    }

    var numAttacks = 0;
    numAttacks: 0,

    const attackMessage = "The monster hits you!";
    const attackMessage: "The monster hits you!"

    }

  4. @BrendanEich BrendanEich revised this gist Nov 2, 2011. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions minimalist-classes.js
    Original file line number Diff line number Diff line change
    @@ -92,6 +92,11 @@ class Fox extends Animal {
    // in case nested classes are wanted (right now I do not want them, since a
    // class at class-body level would declare a prototype inner class and not a
    // static inner class).
    //
    // Class-side properties inherit, just as constructor-side properties do with
    // the <| proposal:
    //
    // http://wiki.ecmascript.org/doku.php?id=harmony:object_literals#individual_extension_summary

    static const CONSTANT = 42;

  5. @BrendanEich BrendanEich revised this gist Nov 2, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion minimalist-classes.js
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,7 @@
    // Harmony always stipulated classes as sugar, so indeed we are keeping current
    // JavaScript prototype semantics, and classes would only add a syntactic form
    // that can desugar to ES5. This is mostly the same assumption that Jeremy
    // chose, but I've stipulated ES5.
    // chose, but I've stipulated ES5 and used a few accepted ES.next extensions.

    // Where I part company is on reusing the object literal. It is not the syntax
    // most classy programmers expect, coming from other languages. It has annoying
  6. @BrendanEich BrendanEich revised this gist Nov 2, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion minimalist-classes.js
    Original file line number Diff line number Diff line change
    @@ -28,7 +28,7 @@
    //
    // http://wiki.ecmascript.org/doku.php?id=harmony:private_name_objects
    //
    // Also visbile throughout: the method definition shorthand syntax proposed at
    // Also visible throughout: the method definition shorthand syntax proposed at
    //
    // http://wiki.ecmascript.org/doku.php?id=harmony:object_literals#object_literal_property_shorthands
    //
  7. @BrendanEich BrendanEich revised this gist Nov 2, 2011. 1 changed file with 169 additions and 94 deletions.
    263 changes: 169 additions & 94 deletions minimalist-classes.js
    Original file line number Diff line number Diff line change
    @@ -1,37 +1,79 @@
    // Here is a proposal for minimalist JavaScript classes, humbly offered.

    // There are (at least) two different directions in which classes can be steered.
    // If we go for a wholly new semantics and implementation, then fancier classical
    // inheritance can be supported with parallel prototype chains for true inheritance
    // of properties at both the class and instance level.

    // If however, we keep current JavaScript prototype semantics, and add a form that
    // can desugar to ES3, things must necessarily stay simpler. This is the direction
    // I'm assuming here.

    // If we want to have static class bodies (no executable code at the top level),
    // then we would do well to reuse the known and loved JavaScript idiom for
    // fixed lists of properties -- the object literal.
    // A response to jashkenas's fine proposal for minimalist JavaScript classes.

    // Harmony always stipulated classes as sugar, so indeed we are keeping current
    // JavaScript prototype semantics, and classes would only add a syntactic form
    // that can desugar to ES5. This is mostly the same assumption that Jeremy
    // chose, but I've stipulated ES5.

    // Where I part company is on reusing the object literal. It is not the syntax
    // most classy programmers expect, coming from other languages. It has annoying
    // and alien overhead, namely colons and commas. For JS community members who
    // don't want classes, either proposal is "bad", so let's focus on the class
    // fans who will use this feature.
    //
    // Therefore I propose giving classes their own new kind of body syntax, which
    // is neither an object literal nor a function body. The main advantage is that
    // syntax for class elements can be tuned to usability, even as it desugars to
    // kernel semantics drawn from ES5 extended by both the super and private names
    // proposals.

    // Not shown below is the super proposal at
    //
    // http://wiki.ecmascript.org/doku.php?id=harmony:object_initialiser_super
    //
    // which composes as expected when 'super' is used in methods defined in a
    // class instead of in an object literal.
    //
    // In contrast, you will see hints of the private name objects proposal at
    //
    // http://wiki.ecmascript.org/doku.php?id=harmony:private_name_objects
    //
    // Also visbile throughout: the method definition shorthand syntax proposed at
    //
    // http://wiki.ecmascript.org/doku.php?id=harmony:object_literals#object_literal_property_shorthands
    //
    // For programmers who want classes as sugar for constructors and prototype, I
    // hope this proposal wins.
    //
    // To be perfectly honest, I personally would be happy with Allen Wirfs-Brock's
    // "Exemplars" approach and no classes. But for programmers coming from classy
    // languages, I believe "minimalist classes" will tend to be too minimal to be
    // usable out of the box. They'll want more.
    //
    // Brendan Eich, 1-Nov-2011

    // First, basic usage from a real-world library (Three.js)

    class Color {

    constructor: function(hex) {
    constructor(hex) {
    ...
    },

    r: 1, g: 1, b: 1,
    }

    copy: function(color) {
    // Prototype data properties, introduced by var or const at class body level.
    // Rationale: object literal syntax requires comma separation and has no way
    // to define a const that's clearly in Harmony. Classy languages do not need
    // no steenkin' commas, but we do want const in ES.next.
    //
    // An objection even I have made: we should not conflate property definition
    // syntax with lexical binding declaration syntax. But the cat's out of the
    // bag with 'var' in global code in ES1-5, and it's hard to beat "const" as
    // the keyword for initialize-only data properties.
    //
    // One fair objection: this syntax may mislead people into thinking methods
    // lexically close over prototype properties declared this way. A calculated
    // risk, what can I say?

    var r = 1, g = 1, b = 1;

    copy(color) {
    ...
    },
    }

    setRGB: function(r, g, b) {
    setRGB(r, g, b) {
    ...
    },
    }

    setHSV: function(h, s, v) {
    setHSV(h, s, v) {
    ...
    }

    @@ -42,115 +84,148 @@ class Color {

    class Fox extends Animal {
    ...
    }

    // Note that "Animal" here is a class object (constructor function) in its
    // own right. Fox.prototype is set to an instance of Animal that has been
    // constructed without calling its constructor function -- this is the
    // usual two-step setting-up-a-prototype shuffle.


    // There is no special syntax for setting class-level properties, as they are
    // relatively rare. Just add them to the class object itself:

    Fox.CONSTANT = value;
    // The special syntax for defining class-level properties and methods uses a
    // prefix keyword. The 'static' keyword is reserved and somewhat hated (or at
    // least considered a misnomer), but I went with it instead of 'class' to
    // avoid confusion in overloading the c-word, and to be more future-friendly
    // in case nested classes are wanted (right now I do not want them, since a
    // class at class-body level would declare a prototype inner class and not a
    // static inner class).

    static const CONSTANT = 42;

    // Note that the right-hand side of a class definition is just an expression,
    // an object literal is not required. You can be fully dynamic when creating a
    // class:
    static classMethod() {
    ...
    }
    }

    class Student objectContainingStudentProperties
    console.log(Fox.CONSTANT);

    // Or even:
    Fox.classMethod();

    class Protester merge(YoungAdult, WorkEthic, Idealism, {
    student: true
    })

    // The point I'm trying to make being that the own properties of the right hand
    // side, however they're derived, become the prototypal properties of the resulting
    // class.
    // Classes are expression forms as well as declaration forms. As with function
    // expressions, the name is optional in a class expression.

    const frozenClass = Object.freeze(class {...});

    // Similarly, class definitions are themselves expressions, and anonymous classes
    // are equally possible:
    // Named class expressions are supported too. The name is bound only in the
    // scope of the class body, as for named function expressions.

    animals.push(class Fox {});

    // An anonymous class expression with superclass specification, after Jeremy's
    // gist, but with an explicit and required body.

    var subclass = function(parent) {
    return class extends parent;
    return class extends parent {
    ...
    };
    };


    // Naturally, classes can be built up programmatically in this fashion.

    var generateModelClass = function(columns) {
    // Unlike Jeremy's proposal, classes cannot be built up programmatically by
    // abutting an expression to a class head. That's too dynamic, it doesn't work
    // with the 'super' proposal. So any synthesis of a class body must use eval
    // or equivalent. At this time, I do not propose adding a Class constructor
    // analogous to Function, but I believe it could be added without issue.

    var definition = {};

    columns.forEach(function(col) {
    definition['get' + col] = function() {
    return this[col];
    };
    definition['set' + col] = function(value) {
    return this[col] = value;
    };
    });

    return class definition;

    };


    // Finally, the Monster class from the current nutshell proposal
    // As in Jeremy's gist, here's the Monster class from the harmony: proposal
    // (http://wiki.ecmascript.org/doku.php?id=harmony:classes#the_proposal_in_a_nutshell)
    // ... sans unnecessary restrictions:
    // ... sans unnecessary 'public' keyword prefixing!

    class Monster {

    constructor: function(name, health) {
    constructor(name, health) {
    this.name = name;
    this.health = health;
    },
    }

    attack: function(target) {
    attack(target) {
    log("The monster attacks " + target);
    },
    }

    isAlive: function() {
    isAlive() {
    return this.health > 0;
    },
    }

    setHealth: function(value) {
    setHealth(value) {
    if (value < 0) {
    throw new Error("Health must be non-negative.");
    }
    this.health = value;
    },
    }

    numAttacks: 0,
    var numAttacks = 0;

    attackMessage: "The monster hits you!"
    const attackMessage = "The monster hits you!";

    }

    // Now there's one more problem to address, which could be deferred, but which
    // falls under "batteries included" to some (not all) of the cohorts of classy
    // programmers using JS or coming to it fresh in the future. And that is the
    // cost of this[kPrivateName] given const kPrivateName = Name.create(...) where
    // module Name from "@name" has been declared.
    //
    // Instead I propose we support private kPrivateName, ...; as a special form
    // only in class bodies (for now) that both creates a private name object and
    // binds it to a lexical const binding that may be accessed on the right of @
    // in methods defined in a class.
    //
    // For class-private instance variables, obj@foo is supported as well (with no
    // LineTerminator to the left of @ in this case, or it's short for this[foo]).
    // See the new sameName method for an example of infix and prefix @ in action.
    //
    // There is no const instance variable declaration. Non-writable instance vars
    // (properties to most people) are quite rare. Use ES5's Object.defineProperty
    // if you must. This avoids ugly fights about read before constant instance var
    // initialization too.

    class Monster {

    private name, health;

    // I think that's about the run of it. Note what is left out: public / private /
    // static / frozen / const properties and their ilk. Personally, I'm of the view
    // that all of these modifiers are deeply undesirable in a language as dynamic
    // as JavaScript and won't be much used, if added ... but I also think that
    // getters and setters should be deprecated and removed.
    constructor(name, health) {
    @name = name;
    @health = health;
    }

    sameName(other) {
    return @name === other@name;
    }

    // If public / private / static / frozen / const must be a part of class syntax
    // in JS.next, then they must be valid prefixes for object literals as well --
    // and can easily be used to define classes with those properties under this
    // proposal.
    attack(target) {
    log("The monster attacks " + target);
    }

    // There are no new semantics here, and these classes can easily be transpiled
    // into ES3 if needed -- just simpler declaration of constructors with prototypal
    // properties and correctly configured prototype chains.
    isAlive() {
    return @health > 0;
    }

    // tl;dr
    // Classes are a new expression with the form ([] means optional):
    // class [name] [extends parent] [expression]
    setHealth(value) {
    if (value < 0) {
    throw new Error("Health must be non-negative.");
    }
    @health = value;
    }

    var numAttacks = 0;

    const attackMessage = "The monster hits you!";

    }

    // As in the harmony:classes proposal, 'const class' freezes all of
    // 1. the class binding, if named declaration rather than expression,
    // 2. the class prototype induced by the const class,
    // 3. the constructor method of the const class.
    //
    // 'const class' also seals instances constructed by the constructor, as if the
    // last line of the constructor method were Object.seal(this), called using the
    // original values of Object and Object.seal.

    const class Frosty {
    }
  8. @jashkenas jashkenas revised this gist Nov 1, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion minimalist-classes.js
    Original file line number Diff line number Diff line change
    @@ -109,7 +109,7 @@ var generateModelClass = function(columns) {

    class Monster {

    constructor: (name, health) {
    constructor: function(name, health) {
    this.name = name;
    this.health = health;
    },
  9. @jashkenas jashkenas revised this gist Nov 1, 2011. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion minimalist-classes.js
    Original file line number Diff line number Diff line change
    @@ -44,9 +44,14 @@ class Fox extends Animal {
    ...
    }

    // Note that "Animal" here is a class object (constructor function) in its
    // own right. Fox.prototype is set to an instance of Animal that has been
    // constructed without calling its constructor function -- this is the
    // usual two-step setting-up-a-prototype shuffle.


    // There is no special syntax for setting class-level properties, as they are
    // relatively rare. Just add them to the class object (constructor function):
    // relatively rare. Just add them to the class object itself:

    Fox.CONSTANT = value;

  10. @jashkenas jashkenas created this gist Nov 1, 2011.
    151 changes: 151 additions & 0 deletions minimalist-classes.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,151 @@
    // Here is a proposal for minimalist JavaScript classes, humbly offered.

    // There are (at least) two different directions in which classes can be steered.
    // If we go for a wholly new semantics and implementation, then fancier classical
    // inheritance can be supported with parallel prototype chains for true inheritance
    // of properties at both the class and instance level.

    // If however, we keep current JavaScript prototype semantics, and add a form that
    // can desugar to ES3, things must necessarily stay simpler. This is the direction
    // I'm assuming here.

    // If we want to have static class bodies (no executable code at the top level),
    // then we would do well to reuse the known and loved JavaScript idiom for
    // fixed lists of properties -- the object literal.

    // First, basic usage from a real-world library (Three.js)

    class Color {

    constructor: function(hex) {
    ...
    },

    r: 1, g: 1, b: 1,

    copy: function(color) {
    ...
    },

    setRGB: function(r, g, b) {
    ...
    },

    setHSV: function(h, s, v) {
    ...
    }

    }


    // To create a class with its prototype chain set correctly:

    class Fox extends Animal {
    ...
    }


    // There is no special syntax for setting class-level properties, as they are
    // relatively rare. Just add them to the class object (constructor function):

    Fox.CONSTANT = value;


    // Note that the right-hand side of a class definition is just an expression,
    // an object literal is not required. You can be fully dynamic when creating a
    // class:

    class Student objectContainingStudentProperties

    // Or even:

    class Protester merge(YoungAdult, WorkEthic, Idealism, {
    student: true
    })

    // The point I'm trying to make being that the own properties of the right hand
    // side, however they're derived, become the prototypal properties of the resulting
    // class.


    // Similarly, class definitions are themselves expressions, and anonymous classes
    // are equally possible:

    animals.push(class Fox {});

    var subclass = function(parent) {
    return class extends parent;
    };


    // Naturally, classes can be built up programmatically in this fashion.

    var generateModelClass = function(columns) {

    var definition = {};

    columns.forEach(function(col) {
    definition['get' + col] = function() {
    return this[col];
    };
    definition['set' + col] = function(value) {
    return this[col] = value;
    };
    });

    return class definition;

    };


    // Finally, the Monster class from the current nutshell proposal
    // (http://wiki.ecmascript.org/doku.php?id=harmony:classes#the_proposal_in_a_nutshell)
    // ... sans unnecessary restrictions:

    class Monster {

    constructor: (name, health) {
    this.name = name;
    this.health = health;
    },

    attack: function(target) {
    log("The monster attacks " + target);
    },

    isAlive: function() {
    return this.health > 0;
    },

    setHealth: function(value) {
    if (value < 0) {
    throw new Error("Health must be non-negative.");
    }
    this.health = value;
    },

    numAttacks: 0,

    attackMessage: "The monster hits you!"

    }


    // I think that's about the run of it. Note what is left out: public / private /
    // static / frozen / const properties and their ilk. Personally, I'm of the view
    // that all of these modifiers are deeply undesirable in a language as dynamic
    // as JavaScript and won't be much used, if added ... but I also think that
    // getters and setters should be deprecated and removed.

    // If public / private / static / frozen / const must be a part of class syntax
    // in JS.next, then they must be valid prefixes for object literals as well --
    // and can easily be used to define classes with those properties under this
    // proposal.

    // There are no new semantics here, and these classes can easily be transpiled
    // into ES3 if needed -- just simpler declaration of constructors with prototypal
    // properties and correctly configured prototype chains.

    // tl;dr
    // Classes are a new expression with the form ([] means optional):
    // class [name] [extends parent] [expression]