Implementing Ruby type system on JavaScript

An one day project on exploiting JavaScript prototypal nature.

You could argue that JavaScript type system is a lot more powerful than Ruby one, or that it doesn’t need classes. And indeed, you’re right. But sometimes, you don’t need a powerful type system, just one that helps you write better e clearer code. Ruby give us that.

Today, I don’t see any NodeJS framework comparable with Rails. In fact, you can blame the platform immaturity compared to Ruby, but the truth is that JavaScript is not even a bit as expressive as Ruby, and this is where Rails main power comes from.

I won’t show every implementation detail here, code will be posted just for reference. The complete project can be found here.

How Ruby works

Ruby has an strange but powerful type system as you can see in the diagram above took from Ruby 2.2.0 documentation.

Ruby Class inheritance diagram

The vertical arrows represent inheritance, and the parentheses metaclasses. All metaclasses are instances of the class ‘Class’. I almost had brain damage while trying to understand how this is supposed to work, a quick look at MRI source code helped a bit.

A simple way to compare this with JavaScript is saying that instance methods contained in a Class are functions in the prototype and instance methods in the metaclass/class methods are functions as properties in the constructor function.

So, this:

class Foo
def hello
puts "Hello"
end
  def self.world!
puts "World!"
end
end
Foo.new.hello
Foo.world

Becomes this:

function Foo() {}
Foo.prototype.hello = function() {
console.log("Hello");
};
Foo.world = function() {
console.log("World!");
};
new Foo().hello();
Foo.world()

Bending JavaScript natives

Now comes then fun, how to make all objects behave like Ruby? Just mess with Object prototype.

In Ruby, the root class isn’t Object, but rather BasicObject. They’re almost the same except that Object mixes with Kernel giving all those nice functions.

BasicObject declaration comes as this:

var BasicObject = function BasicObject(){};
BasicObject.prototype = Object.create(null, {
constructor: {
enumerable: false,
writable: false,
readable: true,
value: BasicObject
}
});

Now, we have to make Object inherit BasicObject. To do this, we override Object prototype prototype(?):

Object.setPrototypeOf(Object.prototype, BasicObject.prototype);

Now {} instanceof BasicObject yields true.

Module and Class

Module and Class classes are very simple, they can be implemented as follow:

var Module = function Module(){};
util.inherits(Module, Object);
var Class = function Class(){};
util.inherits(Class, Module);

Metaclasses

Now comes the main problem, in JavaScript, when you subclass some class using just the prototype, the “class methods” are not inherited. This is because there is no concept of inheritance and classes in JavaScript.

To come over this, we must change the constructor function prototype to something inheritable.

var inheritMetaclass = function(constructor, base) {
// Inherit the metaclass(class methods)
constructor.metaclass = Object.create(base.metaclass, ...);
    // Change the constructor prototype
Object.setPrototypeOf(constructor, constructor.metaclass);
};

Tidying up the inheritance diagram

Remember that nice inheritance diagram? Now it’s time to make our type system looks like that.

The standard inheritance already works(i.e.: Class inherits Module that inherits Object that inherits BasicObject), but metaclasses aren’t linked yet.

inheritMetaclass(Object, BasicObject);
inheritMetaclass(Module, Object);
inheritMetaclass(Class, Module);

The catch is: BasicObject metaclass is a Class itself. To fix this, BasicObject metaclass must inherit from Class, but how? It would become a loop. The solution seems quite strange, it should inherit from Class prototype, not it’s metaclass.

inheritMetaclass(BasicObject, Class.prototype);

Our inheritance chain now looks like this:

BasicObject inherits nothing.
Object inherits BasicObject.
Module inherits Object.
Class inherits Module.

BasicObject metaclass inherits Class.
Object metaclass inherits BasicObject metaclass.
Module metaclass inherits Object metaclass.
Class metaclass inherits Module metaclass.

Pff, that was a bit overwhelming.

JSRuby in action

JSRuby is the final implementation of all topics discussed in this article. With it, this:

require('jsruby');
var FooClassMethods = Module.create();
FooClassMethods.prototype.world = function() {
console.log("World!");
};
var Foo = Module.create();
Foo.included = function(klass) {
klass.extend(FooClassMethods);
};
Foo.prototype.hello = function() {
console.log("Hello");
};
var Bar = Class.create(function Bar() {});
Bar.include(Foo);
var Baz = Bar.create(function Baz() {});
Baz.prototype.old = function() {
console.log("Old");
};
var baz = new Baz();
console.log("###");
baz.hello();
baz.old();
Baz.world();
console.log("\n$$$");
console.log(baz.constructor.name);
console.log(baz.constructor.superclass.name);
console.log("\n@@@");
console.log(Baz.constructor.name);
console.log(Baz.constructor.superclass.name);
console.log("\n%%%");
console.log({} instanceof BasicObject);

Outputs this:

###
Hello
Old
World!
$$$
Baz
Bar
@@@
Class
Module
%%%
true

Bonus: __missingMember__

Another cool Ruby feature is missing_method, it gives great flexibility when creating dynamic objects.

Until ES6/Harmony, there wasn’t any way to implement this kind of tool without some non-standard stuff like Firefox’s __noSuchMethod__. Now with direct proxy support this becomes easy.

Different from Ruby— where almost everything is callable without parentheses and referencing self— in JavaScript methods and variables shares the same namespace. A method like missing_method doesn’t make much sense as it won’t work in case where you need to return something that isn’t a function(e.g.: implementing a Hash).

Implementing a trap to catch a missing property is easy using prototypes. You just need to place a Proxy in the prototype chain with a get trap. Placing the Proxy as the last item in the chain ensures that it will always be called.

var MissingPropertyTrapTarget = Object.create(null);
var MissingPropertyTrap = new Proxy(MissingPropertyTrapTarget, {
get: function(target, name, receiver) {
if (name == ‘__missingProperty__’) {
throw new Error(“WTF”);
}
        return receiver.__missingProperty__(name);
}
});

Now, any object that it prototype descends from MissingPropertyTrap, will have __missingProperty__ behaviour.

As we already hooked Object prototype, making BasicObject it’s base class, it becomes easy to add __missingMember__ to every object.

BasicObject.prototype = Object.create(MissingPropertyTrap, {
constructor: {
enumerable: false,
writable: false,
readable: true,
value: BasicObject
},
__missingProperty__: {
enumerable: false,
readable: true,
writable: true,
value: function(name) { return undefined; }
}
});

The default implementation is to just return undefined, keeping default JavaScript behaviour.

And the final result is:

Suddenly, JavaScript has bare words.

Conclusion

This implementation is far from being complete or accurate, but it was a nice exercise on learning more about Ruby type system and on finding ways to make JavaScript more expressive.

Apart from being complete unorthodox, this would be an implementation that I would use in production. Except for __missingMember__, it looks completely stable.

Probably if combining this with another language like LiveScript, or even with some macros for Sweet.js, more features could be implemented without hitting JavaScript lack of expressivenes.


You can see the final project here.

Show your support

Clapping shows how much you appreciated Jonathan Lima’s story.