Open Source Adventures: Episode 61: How Opal Ruby represents classes

In the previous episode we checked how Opal Ruby represents basic data types, let's now define some classes and see what they compile to.

Simple Person class

Let's start with a very simple class:

class Person
  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def to_s
    "#{@first_name} #{@last_name}"
  end
end

person = Person.new("Alice", "Ruby")
puts "Hello, #{person}!"
nil

With wrappers, it gets compiled to this monstrosity:

Opal.queue(function(Opal) {/* Generated by Opal 1.5.0 */
  var self = Opal.top, $nesting = [], $$ = Opal.$r($nesting), nil = Opal.nil, $klass = Opal.klass, $def = Opal.def, person = nil;

  Opal.add_stubs('new,puts');

  (function($base, $super) {
    var self = $klass($base, $super, 'Person');

    var $proto = self.$$prototype;

    $proto.first_name = $proto.last_name = nil;


    $def(self, '$initialize', function $$initialize(first_name, last_name) {
      var self = this;


      self.first_name = first_name;
      return (self.last_name = last_name);
    }, 2);
    return $def(self, '$to_s', function $$to_s() {
      var self = this;

      return "" + (self.first_name) + " " + (self.last_name)
    }, 0);
  })($nesting[0], null);
  person = $$('Person').$new("Alice", "Ruby");
  self.$puts("Hello, " + (person) + "!");
  return nil;
});

Let's try to unpack some things:

  • $$('Person') does constant lookup.
  • all methods are defined with $ prefix to reduce conflicts with JavaScript, so we have .$initialize, .$to_s, .$new, .$puts and so on
  • instance variables are surprisingly mapped directly, so first_name instance variable is just .first_name
  • this is assigned to local variable self when method is entered, as in pre-ES6 JavaScript this would get changed a lot. This technique was actually used a lot by hand-written pre-ES6 JavaScript code as well. It's rare nowadays.

Inheritance

Let's try to inherit from Person:

class Person
  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def to_s
    "#{@first_name} #{@last_name}"
  end
end

class Cat < Person
  def to_s
    "Your Majesty, Princess #{super}"
  end
end

cat = Cat.new("Catherine", "Whiskers")
puts "Hello, #{cat}!"
nil

This is not going to be very readable:

Opal.queue(function(Opal) {/* Generated by Opal 1.5.0 */
  var self = Opal.top, $nesting = [], $$ = Opal.$r($nesting), nil = Opal.nil, $klass = Opal.klass, $def = Opal.def, $send2 = Opal.send2, $find_super = Opal.find_super, cat = nil;

  Opal.add_stubs('new,puts');

  (function($base, $super) {
    var self = $klass($base, $super, 'Person');

    var $proto = self.$$prototype;

    $proto.first_name = $proto.last_name = nil;


    $def(self, '$initialize', function $$initialize(first_name, last_name) {
      var self = this;


      self.first_name = first_name;
      return (self.last_name = last_name);
    }, 2);
    return $def(self, '$to_s', function $$to_s() {
      var self = this;

      return "" + (self.first_name) + " " + (self.last_name)
    }, 0);
  })($nesting[0], null);
  (function($base, $super) {
    var self = $klass($base, $super, 'Cat');


    return $def(self, '$to_s', function $$to_s() {
      var $yield = $$to_s.$$p || nil, self = this;

      delete $$to_s.$$p;
      return "Your Majesty, Princess " + ($send2(self, $find_super(self, 'to_s', $$to_s, false, true), 'to_s', [], $yield))
    }, 0)
  })($nesting[0], $$('Person'));
  cat = $$('Cat').$new("Catherine", "Whiskers");
  self.$puts("Hello, " + (cat) + "!");
  return nil;
});

As you can see the generated code is completely unreadable, and barely uses any JavaScript OOP features. Which makes sense as pre-ES6 JavaScript OOP was hell, and that's what Opal targets. I'm not sure if targetting ES6+ would improve things much.

If you look closely, you can see some support for yield even though we're not using it for our case.

Story so far

All the code is on GitHub.

Coming next

In the next episode we'll see how Ruby2JS compares to Opal Ruby.