Aadit M Shah

Gravatar

Standardizing Harmony Classes

Published: 21 May 2013 | Comments

Prototypal Inheritance in JavaScript has always been a puzzle for most programmers. Especially for those who come from a classical inheritance background. The problem is that classes work out of the box. You don't need to manually set up prototype chains or worry about overriding the constructor property. Hence ECMAScript Harmony classes are a promising new feature.

Don't get me wrong. Like every other seasoned JavaScript programmer I love prototypal inheritance. Nevertheless I agree that it's difficult. I feel really stupid saying that because personally I find it dead simple. However, for a person who has only worked with classes, setting up prototype chains is a daunting task. I was lucky to have studied prototypal inheritance before classical inheritance.

This is what I imagine a programmer from a classical inheritance background might say about prototypal inheritance in JavaScript:

JavaScript has no classes! Why? How am I supposed to create objects of a certain type? I don't want to manually create a new object literal for everything. A function can be used as a constructor! Shouldn't a constructor belong to a class? Everything must be inside the constructor! Wouldn't that make the constructor... big?

Why do I need to assign an instance of my base class - erm, constructor - to the prototype property of my derived constructor? Why is the prototype property so special? Why does inheritance have to be so complicated? Why does JavaScript have to be a ***** and use prototypes instead of classes like every other language does?

Indeed, why does JavaScript need prototypal inheritance anyway? Prototypes are commonly used for two purposes - to make one function inherit from another function, and to add new methods to all the instances of a function (monkey patching). Prototypal inheritance failed JavaScript in the first case - else we wouldn't be asking for classes; and extending any prototype beside your own is considered bad practice.

Seriously, ask yourself - does JavaScript really need prototypes? Most of the time it doesn't. In fact more than 90% of the programs written in JavaScript are better off using classical inheritance. The rest of the programs do not need inheritance at all. Again I'm not criticizing prototypal inheritance. I'm simply saying that prototypal inheritance is usually not required.

That being said prototypal inheritance is an integral part of JavaScript. It's what makes everything work. You see JavaScript is both a functional and an object oriented programming language, and these two paradigms don't mix well. For example you can't use new (an OOP concept) and apply (a functional concept) together. However since JavaScript has prototypal inheritance you can workaround it. This would be impossible to accomplish if JavaScript only had classical inheritance. More on that later.

Classical Inheritance in JavaScript

Wow. That was a long introduction. Are you still with me? Good. Alright, so now that we know why we need classes let's get into the nitty-gritty details. First things first - ECMAScript Harmony will have classes but it will still be a prototypal language. How? It's because you can simulate classical inheritance using prototypal inheritance. Hence classes will just be syntactic sugar for their prototypal equivalents.

Did you know that classical inheritance is actually a subset of prototypal inheritance? That's the reason you can simulate classical inheritance in JavaScript but you can't simulate prototypal inheritance in Java. It's also the reason programmers from a classical inheritance background find prototypal inheritance difficult, but programmers from a prototypal inheritance background find classical inheritance easy.

How do you simulate classical inheritance in JavaScript you ask? Take a look yourself (original script). It should take you just a minute to understand:

 1 Function.prototype.augment = function (body) {
 2     var base = this.prototype;
 3     var prototype = Object.create(base);
 4     body.apply(prototype, Array.from(arguments, 1).concat(base));
 5     if (Object.ownPropertyOf(prototype, "constructor")) {
 6         var constructor = prototype.constructor;
 7         constructor.prototype = prototype;
 8         return constructor;
 9     } return prototype;
10 };

That's it. Classical inheritance in 8 lines of code. Don't believe me? Let's see some examples:

 1 var Rectangle = Object.augment(function () {
 2     this.constructor = function (width, height) {
 3         this.height = height;
 4         this.width = width;
 5     };
 6 
 7     this.area = function () {
 8         return this.width * this.height;
 9     };
10 });

That looks a lot like a class doesn't it? In fact if you take away all the syntactic noise you'll be left with this:

 1 class Rectangle {
 2     constructor(width, height) {
 3         this.height = height;
 4         this.width = width;
 5     }
 6 
 7     area() {
 8         return this.width * this.height;
 9     }
10 }

Here we omitted extends Object as a class extends Object by default. Let's see an example of inheritance using this pattern:

1 var Square = Rectangle.augment(function (base) {
2     this.constructor = function (side) {
3         base.constructor.call(this, side, side);
4     };
5 });

Again, without the syntactic noise the above program would look like this:

1 class Square extends Rectangle {
2     constructor(side) {
3         super(side, side);
4     }
5 }

The moral of the story is that JavaScript already has classical inheritance. You can use classical inheritance in JavaScript right now without having to wait for implementations to support classes. ECMAScript Harmony classes are just syntactic sugar for the above code. This syntactic sugar is called maximally minimal classes. It's just a fancy name for the syntax of the class, so don't panic.

ECMAScript Harmony Classes

Wadler's Law claims that:

In any language design, the total time spent discussing a feature in this list is proportional to two raised to the power of its position.

0. Semantics
1. Syntax
2. Lexical syntax
3. Lexical syntax of comments

According to this law, TC39 (the ECMAScript design committee) must have spent twice as much time discussing the syntax of classes in ECMAScript Harmony rather than its semantics. This is evident both in the history and the specifications of Harmony classes. Unfortunately since this law holds true we're left with a really nice syntax for classes but really bad semantics.

See, the original syntax for Harmony classes (simply called classes) was considered too verbose. Hence they came up with a new syntax and called it minimal classes. Being yet dissatisfied they simplified the syntax even further, calling it maximally minimal classes. Fortunately they ran out of superlatives so I believe that this is the final syntax. Let's cross our fingers and pray it doesn't change.

Unfortunately they spent too much time deciding on the syntax of the language and too little time on the actual semantics. Hence although we have maximally mimimal syntax yet we have maximally brittle semantics. Semantics is much more important than syntax. Ask any LISP programmer. JavaScript semantics is much more important because it must be backwards compatible and easy to read, write and understand.

New Semantics for Maximally Minimal Classes

I believe the semantics for maximally mimimal classes are derived from CoffeeScript. That's disturbing. CoffeeScript is a nice designer language, but the JavaScript it generates is horrendous. No offense Jeremy but do generated classes really need to look like they do below. Personally I find this to be a very flaky solution, and reading so many underscores results in some serious brain-damage.

 1 var Rectangle, Square,
 2   __hasProp = {}.hasOwnProperty,
 3   __extends = function(child, parent) {
 4     for (var key in parent)
 5       if (__hasProp.call(parent, key))
 6         child[key] = parent[key];
 7 
 8     function ctor() {
 9       this.constructor = child;
10     }
11 
12     ctor.prototype = parent.prototype;
13     child.prototype = new ctor();
14     child.__super__ = parent.prototype;
15     return child;
16   };
17 
18 Rectangle = (function() {
19   function Rectangle(width, height) {
20     this.width = width;
21     this.height = height;
22   }
23 
24   Rectangle.prototype.area = function() {
25     return this.width * this.height;
26   };
27 
28   return Rectangle;
29 
30 })();
31 
32 Square = (function(_super) {
33   __extends(Square, _super);
34 
35   function Square(side) {
36     Square.__super__.constructor.call(this, side, side);
37   }
38 
39   return Square;
40 
41 })(Rectangle);

I guess the Rectangle class is okay and the Square class can be better, but what's with the __extends function? Why are you copying the static properties of the parent class to the child class? What if I add a static property to the parent class after I create the child class? Isn't it reasonable to expect the child class to have the newly defined property? Why don't you use Object.create? Wouldn't it be better to return __super__ from __extends? Here's a nice glitch:

1 class Rectangle
2     constructor: (@width, @height) ->
3     area: -> @width * @height
4 
5 class Square extends Rectangle
6     constructor: (side) -> super side, side
7     @__super__ = null

Now it's reasonable to expect the above code to work but it doesn't. The reason is that the super keyword above translates to Square.__super__ which I'm explicitly setting to null. CoffeeScript reserves __hasProp and __extends, but no such restriction is placed upon __super__. As a programmer however I should be allowed to use them all. That's a reasonable expectation.

This is what happens when the syntax of a language is given more importance than the semantics. ECMAScript Harmony is going down a similar path. To remedy this problem I propose we use a different semantics for maximally minimal classes. I propose we implement the augment method natively in every browser so that the translation of class syntax to semantics is simply a one-to-one mapping.

Ten practical advantages of using augment semantics over CoffeeScript semantics:

  1. The resulting code is easier to read, write and understand.
  2. The only function required is Function.prototype.augment.
  3. No distinction between the constructor and other functions.
  4. The augment method can also be used to create singletons.
  5. The augment method can be used to implement the module pattern.
  6. Easier to translate the class syntax to its equivalent semantics.
  7. Every function is automatically collected on the prototype object.
  8. No name clashes as the base class prototype can be given any name.
  9. The augment method is proven to be fast. Especially when implemented natively.
  10. Fully backwards compatible via my own augment library.

Conclusion

There are lots of classical JavaScript inheritance patterns out there - John Resig's simple JavaScript inheritance pattern, JSFace, Classy, etc. However I believe augment trumps them all - perhaps not in features but definitely in design. It's small, simple, elegant and just what is required - nothing more and nothing less. All this wrapped up in a single function.

Perhaps semantics might be of little concern to you. Especially when the semantics of classes will be implemented natively. However backwards compatibility is important and augment allows for one-to-one mapping of Harmony classes to JavaScript that can be used today. In addition having a standard way to implement classical inheritance right now will definitely help people who're struggling with prototypal inheritance.

If you believe that augment should be implemented natively for the benefit of all then please comment to make a difference.

Comments