-
-
Save allenwb/1332734 to your computer and use it in GitHub Desktop.
less minimalism, richer leather
This file contains hidden or 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
//work in progress | |
// A response to jashkenas's fine proposal for minimalist JavaScript classes. | |
// and BrendanEich's Rich Corinthian Leather alternative proposal | |
//intro and justifications still to come | |
// 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 = { | |
// The body of a class declaration is an object literal. However the value | |
// that is bound to the "class name" is the constructor method defined by | |
// by the object literal rather than the object itself. = is used to indicate that | |
// this is a non-hoisted initializing declaration just like let and const. The = has | |
// other significance that will become apparent in subsequent examples. | |
// | |
// The object described by the literal is the prototype object that is the value of the constructor's | |
// "prototype" data property. If a constructor method is not explicitly specified | |
// one is automatically generated. | |
constructor(hex) { | |
... | |
} | |
// Prototype data properties, are just object literal property definitions, seperated | |
// by commas. Readonly properties are designated by preceding the property definition | |
// with a # (this was proposed at the last TC39 meeting). | |
// Rationale: object literal syntax already exists in JS as a way to describe the properties | |
// of objects. Having a separate syntax for describe properties in a class definition just as | |
// unnecessary redundancy and confusion. | |
// | |
* // 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? | |
r : 1, g : 1, b : 1, | |
copy(color) { | |
... | |
} | |
// comma after method bodies are optional | |
setRGB(r, g, b) { | |
... | |
} | |
setHSV(h, s, v) { | |
... | |
} | |
} | |
// To create a class with its prototype chain set correctly: | |
class Fox = Animal <| { | |
... | |
// The same "set literal [[Prototype]] operator" that is described in | |
// http://wiki.ecmascript.org/doku.php?id=harmony:proto_operator | |
// (whether ultimately written as <|, <:, beget, proto, or something else) | |
// is used to specify the superclass of the new class. | |
// | |
// The object reference on the LHS of <| identifiers the superclass. It may be | |
// function, an non-function object, or a class (a class is really this a function). | |
// The [[Prototype]] of the class object (the constructor) and the class prototype | |
// are set appropriately depending upon whether the "superclass" is specified as | |
// a function (with a prototype property), an object with a prototype property, or | |
// an object with a constructor property. | |
// 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; | |
} | |
var 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. | |
// | |
// 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; | |
constructor(name, health) { | |
@name = name; | |
@health = health; | |
} | |
sameName(other) { | |
return @name === other@name; | |
} | |
attack(target) { | |
log("The monster attacks " + target); | |
} | |
isAlive() { | |
return @health > 0; | |
} | |
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 { | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment