Infrequently Noted

Alex Russell on browsers, standards, and the process of progress.

Class Warfare

And so we're at an impasse.

At the last TC39 meeting, after spending what felt like an eternity on the important topic of UTF-16 encoding, the last day hastily ended with two topics that simply could not be any more urgent: can we get something done about data mutation observation -- the underpinnings for every data binding system worth a damn -- and classes.

As you might imagine, classes got at least one cold shoulder, but this time we were able to suss out the roots of the objection: the latest majority-agreed proposal ("Maximally Minimal Classes") isn't "high water mark" classes and, since so much has been given up to get something moved down the field, classes are no longer worth having. In this post I'll outline a bit of the debate so far, current state, and hopefully convince you that the anti-classicist's arguments are weak.

But first: why classes at all?

There's an ambient argument in the JS world that "we don't need no stinking classes". That all we must do to save the language is teach people the zen of function objects and closures and all will be well with the world...and if we lose some people, so what? I don't want to tar everyone who sympathizes with the beginning of that sentiment with the obvious negatives of the last bit, but that's the overall gist I get. In this view, classes are an unnecessary and lead people to consider JS code the "wrong" way. I've written before about how in ES6 class means function, but that doesn't mollify the discontent.

But then why any language feature at all? Why isn't assembler good enough for everyone? Or C? Or C++?

Turns out the answer is "human frailty". Or put a different way, the process of cognition depends on very limited amounts of short-term stack space in our wetware, and computing languages are about making the computer hospitable for the human, not about telling the system what to do. Our tradeoffs in languages would look much different if we could all easily recall 20 or 30 things at a time instead of 5-10. Languages are tools for freeing creative people from registers and stacks and heaps and memory management and all the rest; all while trying to keep the creative process grounded in the reality that it's memory words in a Von Neumann architecture; without that grounding we'd end up too disconnected from the system to deliver anything practical.

Abstractions, high-level semantics, types...they're all pivots, ways of considering sets of things while working with an individual example of that thing mentally. You don't consider every case all at once and don't remember everything about the system at one time; instead you focus on the arguments to the current function which creates it's own scope. All of these mechanisms create ways of limiting what you need to think about. They ease the burden of associating some things with other things by allowing you to consider smaller cases and allowing you to create or break limitations as they become variously helpful and harmful to getting something done. And we have lots of words to describe the things we find painful about dealing with the overhead of remembering; of a lack of directness and locality: "spaghetti code", "unmaintainable", "badly factored", "tightly coupled", etc. Where there's a new fad in programming, odds are high that it is being touted as a solution to some aggregate behavior which causes a reasonable local decision to become globally sub-optimal. This is what language features are for and why, when you see yourself using the same patterns over and over again, something is deeply wrong.

I'll say this again (and imagine me saying it sloooooowly): languages evolve in response to the pain caused by complexity. The corollary is that language which don't evolve to remove complexity, or which add it in day-to-day usage cause pain. Pain is almost always bad.

Complexity takes many forms, but the process of disambiguation when looking at a syntactic form that relies on a pattern is one of the easiest to take off the table. You'll see this in my work in lots of areas: Web Components make the relationship between markup and eventual behavior less ambiguous when you read the HTML, Shadow DOM creates an isolation boundary for CSS and HTML that make it easier to consider a "thing" in isolation when building and using them, classes in JS make it easier to read some bit of code and not be tripped up by the many meanings of the word function, and many of the features I've advocated for in CSS (mixins, hierarchy, variables) are factoring mechanisms that make it simpler to pivot between repeated stuff and the ad-hoc visual semantics of the document that mean so much to the design language.

Not only should we write off the luddites, we should consider every step towards lower complexity good and steps towards complexity and slower understanding to be bad and actively harmful. Tortured claims about how "you aren't going to need those things" need to be met with evidence; in this case, what do the pervasive libraries in the ecosystem do? Yeah, those are complexity to be taken off the table. The agenda is clear.

But back to classes and JS.

We've been debating various forms of classes in JS since the late '90s. That's right; we've been circling this drain for more than a decade. In that time, nearly every permutation of class system that I'm familiar with has been debated as the rightful heir to the reserved class keyword. From a parallel inheritance structure (non-prototypal) to "just prototypes like we have them today", from "classes as constructor body" to "classes as object literals with constructors", from frozen to gooey and unbaked, from classes as nominal types to totally untyped, we've seen it all. In all of this, the only constant has been failure. TC39 has continued, for more than a decade, to fail to field any class syntax (yes, I have my slice of blame-cake right here). In that time, JS has gone from being important to being critical to the construction of large systems; both on the client and the server. As this has happened, we've seen function pressed into service not only as lambda and method, but module, class, and pretty much every other form of ad-hoc composition system that's needed at any point. We're overdue for adding language-level support for all of these things. The committee has shown great ability to extend and expand upon idioms already in the language, taking syntax as a starting point and using it create new room for reducing pain (the spread operator and destructuring are great examples). Yet still many on the committee, notably Luke Hoban of Microsoft and Waldemar Horwat of Google, worry that a form of class in ES6 that doesn't make coffee for you while also shooting rainbows out of it's behind won't be worth the syntactic space it takes up. I would have worried about this too back in '08 when I first attended a TC39 meeting, but no longer. This is a group that is good at incremental change. Rainbow-shooting technology takes a while to build; meanwhile, wouldn't it be great if we could get a cup of joe around here? We've waited long enough to get something meaningful to build on and with. I want a hell of a lot more than what the Maximally Minimal Classes proposal adds, but they're a starting point, and there will be more ES versions.

So that's Argument #1: people will hate us if class doesn't do enough.

True, perhaps, but they will hate us anyway. Add classes and Mikeal Rogers personally organizes the pitchfork and torch distribution points and leads the march. Don't add classes and the world at large thinks of JS as a joke and a toy -- where'd that giant library/transpiler get to, anyway? You need something after all. Add them and Mikeal can keep not using classes to his heart's content, and if we add classes in the style I prefer (and which Max/Min Classes embody), he can use them as old-skool functions and never care how they were declared; all the while making JS more productive for folks who don't want to buy into a whole lifestyle to get a job done.

What's Argument #2? That instances created from classes aren't frozen. That is to say, it's still possible to add new properties to them later. You know, like every other JS object.

If you're thinking "yes, but isn't there Object.freeze() already?", you're not alone. What you're witnessing here is a debate about what's considered good, not what's possible. You can manually freeze/seal objects to your heart's content, but what the (tiny) minority that is strenuously making argument #2 is demanding is that nobody be allowed to use the word class to create a constructor function body unless the resulting object is automatically frozen. They are willing to hold up the entire train until this preference is mollifed, and are unhappy with any short syntax which does not bless their preferred style.

In fairness, I also would be grumpy to see things go a direction I don't prefer, but consider the following syntaxes that could be deployed in a future version to create frozen classes, none of which are precluded by the Max/Min proposal:

// "const" causes instances to have their properties
// frozen on exiting the constructor
const class Point {
  constructor() { ... }
}

// Declaring a const properties member causes the // constructor to check for those only, freezing exiting // the constructor class Point { constructor() { ... } const properties { // Object literal ... } }

And I'm sure you can think of a couple of others. The essential distinguishing feature, and the thing that is holding the entire train up, is that the word class without any embellishment whatsoever doesn't work this way. Yes, I know it sounds crazy, but that's where we're at. You can help, though. Erik Arvidsson has implemented Max/Min classes in Traceur and you can try it out in the live repl. If you like/hate it, won't you please let the es-discuss mailing list know? Or just post in the comments here for posterity. We know that these classes need many more features, but your thoughts about this syntax as a starting point couldn't be more timely.