Summary: prototypes as classes

Brendan Eich brendan at mozilla.com
Sat Jul 2 14:04:18 PDT 2011


Remember, I'm the one who is a little sad about classes making the ES.next cutoff. Also I agree with dherman that they have too many option issues and would benefit from reductions ("minimal classes", see ).

On Jul 2, 2011, at 11:46 AM, Angus Croll wrote:

> By unchained prototypes I mean those that directly extend Object.prototype (note that  that every built-in prototype specified by ES5 is unchained). Unchained prototypes are gorgeous - one instance defining the properties of many. And it's really not that hard...once you understand the difference between Constructor.prototype and instance.__proto__ (or its symbolic successor) you're all set. The concept itself is very simple - a dynamic archetype to be shared by all my instances: my prototype changes, my instances know about it. I would not want to hide such a smart, simple concept behind the more complex and (in this scenario) less meaningful concept of class. 

The difference is just induction from 1 to 2 (assuming you don't extend Object.prototype, which ES5 allows you to do now without breaking for-in loops -- from 2 to 3 if you do extend Object.prototype).

Is a difference in degree really a difference in kind or meaning? I don't think so.

Some empirical results:

5.3 Prototype Chains
One higher-level metric is the length of an object’s prototype chain,
which is the number of prototype objects that may potentially be
traversed in order to find an object’s inherited property. This is
roughly comparable to metrics of the depth of class hierarchies in
class-based languages, such as the Depth of Inheritance (DIT) metric
discussed in [23]. Studies of C++ programs mention a maximum
DIT of 8 and a median of 1, whereas Smalltalk has a median of 3
and maximum of 10. Figure 6 shows that in all but four sites, the
median prototype chain length is 1. Note that we start our graph at
chain length 1, the minimum. All objects except Object.prototype
have at least one prototype, which if unspecified, defaults to the
Object.prototype. The maximum observed prototype chain length
is 10. The majority of sites do not seem to use prototypes for code
reuse, but this is possibly explained by the existence of other ways
to achieve code reuse in JavaScript (i.e., the ability to assign closures
directly into a field of an object). The programs that do utilize
prototypes have similar inheritance properties to Java [23].

Figure 6:



from

An Analysis of the Dynamic Behavior of JavaScript Programs
Gregor Richards Sylvain Lebresne Brian Burg Jan Vitek
S3 Lab, Department of Computer Science, Purdue University, West Lafayette, IN
{gkrichar,slebresn,bburg,jv}@cs.purdue.edu


Unit-length prototype chain, including Object.prototype -- meaning object initialisers and other new Object uses, not (new C) where C is a user-defined function -- cover a lot of ground.

But the truth is messier. Some JS libraries (and possibly compilers) use deeper prototype chains.

Calling these a mistake across the board is a bit much.


> - Nine times out of ten it's a re-use technique masquerading as a classification of types. Yes a shark is a fish (thanks @jb_briaud) and if you ever need a fish and a shark in your JavaScript then classical inheritance is for you. However if its re-use you want (and it almost always is) then implementation inheritance is tedious at best, a death-march at worst, because sooner or later the hierarchy becomes arbitrary and self defeating. Single-root trees are good for representing ancestry, I can attest that they are not good for defining re-use.

I'm as big a critic of classical OOP as anyone. No argument there, as a generalization.

But JS has C.prototype for user-defined function C, and that makes a two-level prototype chain. Some cohort then go beyond that to make three- or deeper chains. CoffeeScript makes this all pleasant. Why shouldn't ES.next?


> - Even when the intention is to model relationships between types the process is awkward. Most types have multiple characteristics but classical JavaScript allows for only one parent. Moreover classical hierarchies must be implemented top-down - a superclass must be created before it can be extended - yet classes closer to the root are by nature more generic and abstract and are more easily defined *after* we have more knowledge of their concrete classes subclasses. Hierarchy definition is by necessity iterative which does not play well with an existing code base.

No argument from me. Bottom-up, inductive, or constructive learning and system-building are usually best.

We shouldn't make a religious war here. JS has "classes" in its spec for all the built-ins, disclosed by Object.prototype.toString().slice(8,-1). These are represented in-language by constructor functions. Users define constructor functions too.

Since you are not out to prevent users from defining such "classes", why is syntax for the clichéd common version of prototypal inheritance beyond the pale?


> b) JavaScript already has cleaner, lightweight, more flexible options:
> 
> - Mixins allow objects to borrow from an unlimited number of other objects regardless of lineage, while still allowing for organization of functions by type. Mixins are way more flexible than inheritance chains - we can buy behavior types on demand with zero impact on the larger model. Prototypes facilitate Mixins, since a mixin is normally applied to an unchained prototype, another reason why new developers should continue to understand prototypes - 'class' is not a helpful concept where Mixins are concerned.

Mixins require copying properties, and there's nothing lightweight (in usable syntax or runtime cost) in that.


> - Delegation (call/apply) lets you grab any function whenever you need it. Classical Inheritance is necessary when your language constrains function usage by type hierarchy (Java, C++). JavaScript doesn't do this and doesn't need it.

This is logically unsound. Java (let's skip C++, it has functional programming and even closures in the latest and greatest) lacks first class functions (historically; haven't paid attention to the latest), so it relies on classes.

This does not prove that functional languages don't need to support classes as well. Many do support classes, going back to Common Lisp and other Lisps where macros and syntax extensions make it straightforward to provide the kind of sugar proposed for Harmony's classes.


> - The module pattern allows a chunk of data and functionality to be parceled into an object which can be used anywhere without compromising the integrity of the module's code. This ability to package up mini-eco-systems frees the developer from constraints of context that would occur if the same logic were defined as part of a class.

This an independent point. ES.next adds modules for such use-cases. That does not mean prototypal inheritance sugar via classes has no benefit.


> c) How does the class concept square (no pun) with objects like Math, which is not a constructor and \therefore will not, I assume be considered a class? Is Math.pow a const now? If so how will that work when Math is not a class? If not that seems like an inconsistency.

Math is not now and never has been a class in the "represented by a constructor function, with a .prototype" sense. Yes, it has a [[Class]] internal property discoverable via Object.prototype.toString, "Math". This is something we're working to make meta-programmable independently of other interpretations of [[Class]] anyway, for DOM self-hosting. See http://www.wirfs-brock.com/allen/things/es5-technial-notes-and-resources


> tl;dr....
> Unchained prototypes: Invaluable concept; would be obfuscated by class syntax.
> Chained prototypes: Class literals might fix the syntax but the not the concept. JavaScript has better, easier tools already.

The concept of "chained prototypes" has been in JS from day one. Class syntax adds nothing new semantically.


> I don't buy the argument (advanced by some in this discussion group) that as long as nothing is removed from the language, new features can't hurt. JavaScript has fewer keywords and a more lightweight syntax than almost any other mainstream language. For many of us that is hugely appealing and paradoxically it makes JavaScript enormously expressive. C++ syntax has everything but the kitchen sink (maybe that too) but as a language I suspect it bores most of use to sobs. Less is more.

Sure, we are avoiding the kitchen sink. Again, I'm not in favor of classes with so many open issues -- for them to make progress, we need to simplify and also sanity-check against how prototypal inheritance is used (e.g., with respect to class-side inheritance).

But this does not mean Object.create is enough, any more than the module pattern, a leaky abstraction based on closures, obviates the need for a module system.


> Earlier in this thread, Brendan said "I claim we can do little to control the outcome in terms of adoption...the classical OOP patterning in many devs' brains, count more than our exhortations." That's sad because I think many ex-classical programmers (myself included) grew tired of implementation inheritance as the dominant paradigm and gravitated to JavaScript (and also functional languages) as a refuge from the stodgy tyranny of bloated hierarchies.

I think you're mixing up categories. It's not up to TC39 to change developers' brains by leaving out conveniences for common patterns, if those patterns are not inherently unsafe or otherwise bad.

Even unchained prototype use-cases where there's a constructor function to initialize per-instance state benefit from class syntax.


> The classical OOP ship may have sailed, but its taking on water. I'm not convinced that "users don't care and they want to write new C and have it just work" - I think that does a disservice to our community. JavaScript is on the upward path, Java on the down. We're fostering a generation of converts from classical OOP to something more elegant, agile and useful. It would be a shame to throw in the towel now.

At this point you've lost me. In no sense is class as prototypal sugar "classical OOP" that differs observably from prototypal inheritance in JS. Even class-side (constructor) inheritance would be done via <| or __proto__. There's nothing new and different here. No vtbls, no interfaces as in Java, no non-property "fields" or "final methods".

I think you're mixing up good skepticism of OOP-is-all-you-need with classes-as-prototypal-sugar FUD. Would changing "class", the reserved word (we can't really unreserve and pick another), to "constructor", help? If not, why not?

Allen's efforts with <| and generalized 'super' also reinforce the point that classes are prototypal inheritance sugar, and only that. Jeremy's work with CoffeeScript demonstrates the same point. There is no magic runtime implementing classical OOP differently, other than class-side (constructor) inheritance via  a little property-copying helper code, for want of <| or universal writable __proto__.

I think you are protesting too much.

/be


> 
> Angus
> 
> On Wed, Jun 29, 2011 at 3:08 PM, Brendan Eich <brendan at mozilla.com> wrote:
> On Jun 29, 2011, at 2:24 PM, Axel Rauschmayer wrote:
> 
> >> That's all neat in a kind of desugaring ftw, nerdcore way, but tl;dr -- users don't care and they want to write new C and have it just work (mostly; granted some crack the code, for a few good and bad purposes).
> >
> > Note that I am arguing from the perspective of a language that only has PaCs, versus a language that only has constructor functions. If you don’t, then we don’t disagree.
> 
> That was not clear, because we keep coming back to JS as it is. That's where I want to land, so arguing about what-if's and might-have-been's is not my cup of tea.
> 
> 
> > You argue that constructor functions are more intuitive at the user level (to *all* people) and that PaCs wouldn’t “just work”
> 
> I never wrote anything like either of those things, certainly not "to *all* people". Indeed I was the one pointing out that your universals did not apply to all people.
> 
> Last concrete disagreement we had was over new C() vs. C() in the current language being notably different from new C() vs. C.constructor() in the alternate-reality language with prototypes as classes.
> 
> /be
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20110702/7e643dba/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Figure 6.png
Type: image/png
Size: 58038 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20110702/7e643dba/attachment-0001.png>


More information about the es-discuss mailing list