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 variableself
when method is entered, as in pre-ES6 JavaScriptthis
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
Coming next
In the next episode we'll see how Ruby2JS compares to Opal Ruby.