Skip to content

Instantly share code, notes, and snippets.

@hontas
Forked from BrendanEich/minimalist-classes.js
Created October 23, 2012 10:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hontas/3938129 to your computer and use it in GitHub Desktop.
Save hontas/3938129 to your computer and use it in GitHub Desktop.
less minimalism, richer leather
// 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 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
// 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 visible 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 {
// 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) {
...
}
// 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.
//
// But if we must have them, we could use object literal syntax for writable
// data properties as follows:
//
r: 1, g: 1, b: 1,
copy(color) {
...
}
setRGB(r, g, b) {
...
}
setHSV(h, s, v) {
...
}
}
// To create a class with its prototype chain set correctly:
class Fox extends Animal {
...
// 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).
//
// 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,
static classMethod() {
...
}
}
console.log(Fox.CONSTANT);
Fox.classMethod();
// 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 {...});
// 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 {
...
};
};
// 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.
// 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 'public' keyword prefixing!
class Monster {
constructor(name, health) {
this.name = name;
this.health = health;
}
attack(target) {
log("The monster attacks " + target);
}
isAlive() {
return this.health > 0;
}
setHealth(value) {
if (value < 0) {
throw new Error("Health must be non-negative.");
}
this.health = value;
}
numAttacks: 0,
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.
//
// David Flanagan suggests keeping the unary-prefix @foo shorthand, but making
// the long-hand obj.@foo. Breaks E4X but no matter -- but the win is that the
// [no LineTerminator here] restriction on the left of unary-prefix @ is not
// needed. Also the references don't grep like email addresses, which counts
// with David and me. So I've incorporated David's suggestion. See other.@name
// etc. below.
//
// 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.
//
// 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 {
private { name, health } // can group a prefix, same for const and static
// note comma optional after closing brace, as for
// method definitions
public flair, // you should have 37 pieces of public flair
// Yes, shorthand for @name = name given formal param name, etc. -- just as
// in CoffeeScript!
constructor(@name, @health) {
@flair = 0;
}
sameName(other) {
return @name === other.@name;
}
private attackHelper(target) {
...
}
attack(target) {
log("The monster attacks " + target);
return @attackHelper(target);
}
isAlive() {
return @health > 0;
}
setHealth(value) {
if (value < 0) {
throw new Error("Health must be non-negative.");
}
@health = value;
}
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 {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment