-
-
Save BrendanEich/1332193 to your computer and use it in GitHub Desktop.
// 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 { | |
} |
allenwb
commented
Nov 3, 2011
via email
@mythz: null default cuz C# did that? LOL -- dude, this is JS.
It's got nothing to do with copying, it's got to do with the principal of least surprise and what JS devs expect. If that happens to be undefined
- so be it, but since we're making classes it's not a bad idea to replicate the behaviour of other classy languages.
I think I starting to understand David proposal, (not the same as liking...) "public" is the replace for his earlier "prop" suggestion, but optional.
Not quite. I was proposing "prop" or "proto" as an alternative to "var" when Brendan was still looking for a comma-free way to declare data properties on the prototype. Things evolved quickly and my "prop" suggestion is no longer meaninful.
My proposal for "public" is strictly by analogy to "private", and to enable the ability to leave out "this"
I don't like that public foo:x vs foo: x changers how you can access foo.
What I imagine is that programmers who want this kind of classy syntax will do in practice is declare all their class members with either public or private and then always use the @ prefix instead of typing this. So yes, there's a difference, but I'm guessing that it won't come up that often for programmers who are using this new syntax.
When I proposed it, I thought that 'private' was just a declaration form like private {x,y,z}, not a modifier that could be used before individual class members. And I imagined that public was the same way. It does seem like a win to allow them before individual class members as well. And if we're doing that, then making declared class members public by default also seems like a good idea. Brendan addresses that upthread: https://gist.github.com/1332193#gistcomment-60988
@BrendanEich I see. I kind of always associate "batteries included" with a huge standard library, but then, I haven't seen the term used out of that context before.
I don't see less minimalism as a bad thing, given such decisions clearly increase the expressivity of the language, without making it overtly complex at the same time.
@mythz: We're not replicating anything from C#. Certainly not C# classes! This seems like another case of the 'class' keyword misleading.
@allenwb: I meant "deleting" as a text-editing operation to rewrite the public/@ code to use this.foo. The |public foo| declaration by itself serves only to bind a @-space variable named 'foo'. It does not create this.foo -- you need a constructor assignment to do that.
/be
Am I the only one that likes JavaScript (relatively) the way it is? maybe this fluff adds value for some people but yikes :s
@visionmedia You could be. Sugar to easily express commonly used patterns is never a bad thing. The option to do it the current way will always be there.
@mythz it is a bad thing, look at c++ it's a huge mess
@visionmedia Please cite a specific example of a bad language feature in C++ added to express a commonly used pattern?
@mythz are you serious??..
Yes. posting here to prevent spamming the list just to repeat myself.
you've worked on c++ projects right?
Yes, using a strawman argument pointing at C++ as a whole as the reason to not to include useful and popular language features does not re-inforce your point. Please cite a specific example.
c++ is the example, derived from C I would expect you know the differences. One is simple, (mostly) elegant, one is a massive clusterfuck beast from hell
You did it again. I'm leaving this circular discussion.
@mythz are you serious??.... you've worked on c++ projects right?
c++ is the example, derived from C I would expect you know the differences. One is simple, (mostly) elegant, has few concepts, few rules, one is a massive clusterfuck beast from hell
@visionmedia Hardly so. I believe a good part of the community is a bit averse to most of the extensions that have been proposed for ES.next. Some of them, however, are needed for the new use-cases of JavaScript. The language has grown, a lot, and so has its user base. For some people or some problems, a certain different set of features is needed — which I do believe class literals don't fit any, but whatever.
And while I would rather have JavaScript be closer to Scheme (R7RS-small) in philosophy, I can't say plenty of the features proposed have became a requirement: standardised module handling; traits; destructuring assignments; better object literals; etc. I still can't see the value in some of them (yes, quasiquoting, I'm looking at you!), though.
@visionmedia: if you want to argue seriously, do it. Otherwise you're just doing sports/beer advocacy of the "tastes great!" / "less filling!" variety, which is noise at best.
I said at the SPLASH keynote Q&A last week, in reply to David Ungar's question, that JS won't grow like C++ has. We use C++ at Mozilla, just as Google and Apple and Microsoft do. C is not an option. We do not subset aggressively but we do avoid some features that are not quite portable, and others that are simply not necessary. We do make heavy use of auto class helpers (RAII), copy constructors, template meta-programming, abstract base classes (interfaces). Anyway, let's get back to JS.
It's one thing to say "I am fine with function", quite another to say "and you should be too". The Harmony agenda is about serving long-standing usability problems that many (not all) users report, without adding new semantics if we can at all avoid it. For classes as sugar on top of the prototypal pattern, this means 'class D extends B' as sugar for the D.prototype = new B; song and dance, and super (which needs detailed and careful design).
If OO JS using prototypes is not something you use, godspeed. But for those who do use it, current .prototype/super ad-hoc and open-coded solutions are too verbose and bug-prone. Saying "don't do OO JS that way" doesn't work. It's either "here is a better way" or "you get nothing more than in JS today". Jeremy along with others including some of us on TC39 are trying for "here is a better way".
/be
@BrendanEich eh? To be honest, Brendan, if you ditch constructors entirely, and include a small set of utilities in the API: basically Object.extend, Object.create and Object.clone (create + extend); it's not really verbose. In fact, with a few utility functions, object composition in JS becomes more or less a Joy. The only thing I still hold a grudge against is the lack of a short function notation for object literals, so one don't need to do foo: function foo(){ ... }
;
tl;dr; I don't see how (minimalistic) class literals hold any more value than object exemplars.
var foo = clone(Clonable {
toString: function toString(){ return '[object Foo]' }
})
var instance = foo.copy() // or foo.new() or whatever else
// would become
var foo = clone(Clonable, {
toString(){ return '[object Foo]' }
})
// Or yet
var foo = Clonable <| {
toString(){ return '[object Foo]' }
}
var instance = new foo()
Super is a concern, yes. Though I don't think I've needed it much, going with hardcoded object names is a terrible thing. But then, super is not dependent on class literals, exclusively.
Which leaves class literals only worth for a few reasons:
- Enforced shape of the instances — which I'm not sure why. Familiarity for classical programmers? Optimisations?
- Possibility of having a nicer syntax to support new features (type guards, super, traits, etc). I believe there's only so much you can do with the object literals syntax before its definition starts to resemble a Perl program.
- Tooling.
Did I miss something? Because really, I don't see the value of adding new class literals in the minimalistic form, if they don't cover the second use case, as the others are easily achieved through object literals with the newest extensions, with on-par syntax — in terms of flexibility and terseness =/
@BrendanEich so "better" is cluttering a grammar just to save a few key-strokes here and there? From what I've experienced typing is not a bottleneck when it comes to writing software. IMHO it should be visually explicit, so ditching punctuation does not necessarily help there, and while larger keywords like "function" are certainly annoying to type, they're easy to identify. It's not so much that these proposals bug me (aspects do of course), but to me it seems like the grammar aspect is largely geared towards what is trendy now, so what's next? introducing more ambiguity to save even more keystrokes? perhaps I'll just have to hope Lua gets it's place in the browser sometime in the future.
@killdream: I like exemplars too, and (see my intro comment, specifically "To be perfectly honest, I personally would be happy with Allen Wirfs-Brock's "Exemplars" approach and no classes" -- you did read that, right?). It seems to me you're replying in place of @visionmedia here, though, which is kind of a gist foul.
Note, however, that calling functions like clone is not the same as writing declarations or expressions. It's harder to optimize and it requires runtime analysis in general. That's not just hard for implementors, it is hard for users in the large. Also, it is more verbose if you count characters or keystrokes. Count apples to apples.
/be
@visionmedia I don't think the sugar is included for the sake of "typing less", but for making, in fact, programs more explicitly on what they do, as opposed to how they do. Dropping the function keyword doesn't necessarily fit that though, but it removes the clutter from reading — I say clutter because that's already defined by the surrounding context and can be easily grasped, imho. Less clutter usually means that it's easier to read a piece of code. I mean, did you ever try something that has comment annotations everywhere in the body of a function? It still irks me when a function has any comment in the body of a function, but I think that's just me.
@visionmedia: who said anything about "typing"? 'function' especially with 'return' of short expression bodies is 14+ chars of overhead for reading, never mind writing. It's incredibly verbose compared to nearby languages. No one needs to read 14 letters over and over just to make a functional abstraction "easy to identify".
You didn't actually make a particular technical argument. "Typing" is a straw man. Try again, or save it for twitter :-|.
/be
The other thing that bugs me about class literals, is that mid-file you have no immediate clue what you're looking at. Which in some projects is no big deal, class per file, but many projects (like node) include several "classes" in a file. For this reason I really prefer the verbosity of Foo.prototype.bar =
, the context is clear.
@killdream sure but that's a hugely opinionated aspect of any language, to some js looks much better than ruby/py/cs in terms of readability, and to some the opposite, not much we can do about that I suppose other than take a real poll of people using the language and not just relying on fanboys.
@BrendanEich for sure, implicit returns would be great for that reason, CS's -> and friends look fantastic in small doses, but I'm assuming you've seen large bodies of code as well? some of us want to gouge our eyes out with all due respect. If it were not a language we all pretty much have to use I really wouldn't care, I would move on to something I consider nicer, but whether you agree or not any opinion is valid, WE are the users, maybe even writing more JavaScript than yourself.
@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.
@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?
@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
@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.
@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.
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.
@mythz like I said... looks great in small doses, and then you gouge your eyes out...