Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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 {
}
@robotlolita
Copy link

robotlolita commented Nov 4, 2011

@BrendanEich ah, yes. From what I gather, most of (the active people on) es-discuss seem to favour object exemplars over class literals.

About the verbosity of both approaches, I think the perceived verbosity is likely the same. Optimisations are a tough matter, indeed. But then, there's the awesome triangle operator :3

@visionmedia You could also say most of the aspects of a programming language's syntax are highly subjective. Even in the real poll of people using the language you're likely to find highly divergent opinions on what "feature x" should look like. Just take a look at the endless discussions about ASI, indentation style, brace placement style, and of course, "lol how do I wrote OOP in JS". The end result will be biased at the end, but the interesting thing about these discussions is that you can make them less of a one-man biased. People are always free to chip in es-discuss and, well, discuss stuff.

@tj
Copy link

tj commented Nov 4, 2011

@killdream if it's so speculative I dont see why there's so much effort involved in changing it. Everyone I know that uses JavaScript daily is absolutely fine with the syntax, perhaps even liking more than most languages they've used. On the other than almost every person I talk to in #node.js migrating over from ruby etc loves coffee-script because it's more familiar to them, I get that, but do we change an existing language just to satisfy people that are not taking any time to really learn the language? and what happens to be trendy now?

@BrendanEich
Copy link
Author

BrendanEich commented Nov 4, 2011

@visionmedia: I write JS. Please don't personalize in lieu of actual arguments. The calls for 'class' in JS are something I still find overloaded and even contradictory. I still suspect we won't get 'class' into ES.next. Some in the community will find this to be a terrible failure. You won't, but why are you more important?

Again, the thing that bugs me most is when "freedom" is defined as telling me what I must do in my code by telling the standards body not to extend the language. That's not freedom at all.

If you have to read my code that uses extensions you personally don't use, and this is a bad problem for you, then stop reading (and using) my code. That's freedom -- you don't have to read my code, and I don't have to use yours.

To settle what to add to the language, we need better arguments than subjective ones that you contend are all equally "valid". We need objective arguments founded on common premises about costs to users and implementors of adding vs. not adding, and on how the extensions interact with the rest of the language.

/be

Copy link

ghost commented Nov 5, 2011

@visionmedia Good point about Coffeescript's implicit returns: I decided to spell out 'return' except in one-liners. IMHO -> is nicer than (function(x,y,z){...}) but there's gotta be a better way to do async programming. Edit: CS has some issues with ambiguity and more-than-one-way-to-do-it (e.g. and and &&), but I like it in general. I like its class syntax. Love everything-is-an-expression. Significant Indentation neatly sidesteps all these comma/semicolon debates... but that's too radical a change for JS/ES6, it should go without saying. Basically, I agree with you 100% about keeping JS simple, and anyone who wants something different can always use a transpiler like Coffeescript. It's pretty painless in practice. End Edit

Anyway, these gist comments are becoming unwieldy and going off topic from the proposal. I say, let @BrendanEich think things over in peace and come back with a revised proposal when he's good and ready. In fact I'd be perfectly happy if he comes back in several months and says here, this is ES6. I trust his judgement.

@robotlolita
Copy link

robotlolita commented Nov 5, 2011

@visionmedia I for one write JavaScript daily and have lost count of how many times I've wished the language would be more flexible and expressive in some parts — I curse everytime I need to write a library to support more than one JavaScript environment, did you see how much boiler-plate you have to write to support Browsers, Node and CommonJS? It's madness. I'd also like a better way to express object composition, which is what prototypes should be for. I'd also like a better way to write DSLs, which JavaScript alone just won't cut for some problems.

There are other several particular problems I just wish there would be a way to express myself better in the language — asynchronous code, pattern matching, multiple dispatching (I write in a function-heavy style), etc, etc, etc. Just as I struggle to use the language everyday for some particular cases, there are other people who do struggle, for different reasons. They are users of the language too, and they should get better ways of expressing themselves just the same.

As Brendan said so many times in es-discuss, freedom means I should be able to fence my yard or put walls on my house if I want to. It doesn't really affect you — unless you want to drop by uninvited, — you can still leave your house unfenced. That's okay too, because freedom doesn't work just from a single point of view. For which I suppose he meant that the JavaScript community is too heterogeneous. There are several users with different use-cases (yes, actual users of the language). They should have their voice heard just as well as the people who are fine with the language as it is. If we can reach a common ground where one feature that's introduced helps a certain group without making the language a pain to everyone else, I don't see what could be so terrible about introducing that feature — which doesn't mean I'm in favour of featuritis, but oh well.

@mythz
Copy link

mythz commented Nov 5, 2011

@visionmedia

Honestly I'm surprised at your anti-language-feature stance for reducing boilerplate since one of the biggest USP's of your popular express framework is that there is minimal typing and nearly no boilerplate. The advertised homepage example exemplifies this:

app.get('/', function(req, res){
    res.send('Hello World');
});

It looks even better in CoffeeScript:

app.get '/', (req,res) -> 
    res.send 'Hello, World'

Noisy repetitive syntax, masquerades the essence of your program and makes it less enjoyable to read / write, and if it's too disjointed and cumbersome to do (like prototypal inheritance) it's simply avoided for small classes. The class sugar in CoffeeScript demonstrates this where there are proportionally a lot more devs creating prototypal classes in a CoffeeScript app than what's done in a JS one, and that's simply a function of it being effortless to do.

@tj
Copy link

tj commented Nov 5, 2011

@mythz like I said... looks great in small doses, and then you gouge your eyes out...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment