-
-
Save BrendanEich/1332193 to your computer and use it in GitHub Desktop.
less minimalism, richer leather
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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
@mythz like I said... looks great in small doses, and then you gouge your eyes out...