A Bit of Fun: JavaScript Behaviour in Ruby

We’ve been writing a lot of JavaScript lately (actually, CoffeeScript, but thats beside the point). The things that threw me when we first learned JavaScript late last century was… well everything actually. When we learned JavaScript my previous programming experience was BASIC, Pascal, C and Perl. All of which are basically proceedural languages (don’t get me started on perl’s bless function). Thus, we spent a lot of our time (like a decade!) not understanding JavaScript’s object model, or closures.

Over the last few years we’ve been rediscovering the joy of programming in JavaScript that we first felt back when it was called LiveScript and we could actually dynamically change the contents of the page in a limited way. These days we bring with us a much better understanding of OO and Functional principals as well as JavaScript’s take on these.

One of the things we have always loved about Ruby is the way that you can bend it to your will, no matter how insane that will may be. So yesterday while we were in the shower we suddenly wondered how hard it would be to get Ruby to behave as it if had a prototypical object model and properties like JS.

Functions

So how are we going to implement our functions in Ruby? Well Ruby already has something that behaves very like functions. Blocks. Importantly for us, blocks are also closures in Ruby, which bring us a bunch of extra functionality for free, meaning we don’t have to reach into any bindings to simulate them. Phew!

So let’s just start by defining a function method that captures and returns the block passed to it:

1
2
3
def function(&block)
  block
end

So far so good.

Objects

Next we need an object class that implements JavaScript style properties. Well JavaScript objects are in many ways very similar to Ruby’s Hash class, except for using the dot notation for property look-up. This is easily simulated Ruby’s method_missing to dynamically simulate getters and setters.

1
2
3
4
5
6
7
8
9
10
class Hash
  def method_missing(method, *args)
    if method =~ /=$/
      # Protip: $` means "the string before the last Regex match"
      self[$`.to_sym] = args[0]
    else
      self[method.to_sym]
    end
  end
end

Okay, this is pretty cool. We can now call getter and setter methods on our Ruby hashes and they’ll behave as if they’re properties behind the scenes. And because we’ve “duck punched” this behaviour into Ruby’s Hash class we can take advantage of Ruby’s hash literal syntax that looks a hell of a lot like the JavaScript object literal syntax:

1
2
3
4
5
lol = {
        whatLang: function() {
          console.log("Is this Ruby, or JavaScript?")
        }
      }

In order to further confuse the gentle reader, let’s implement console.log in Ruby:

1
2
3
4
5
6
7
  def console
    Object.new.tap do |o|
      def o.log(*args)
        puts(*args)
      end
    end
  end

Now we can even call our object’s function. We can’t call the function using () because Ruby will think they’re for arguments to our simulated whatLang method, not the function. However, we can use the fact that Proc aliases [] to call to give us a close simulcrum.

1
lol.whatLang[]
1
Is this Ruby, or JavaScript?

Prototypes

Early on, one of the things I said I wanted was to give objects prototypes, well, it turns out this is pretty easy to do. We just need to modify Hash’s [] property accessor slightly to check for a prototype property and if it’s there ask the prototype for it’s property when it’s not present on the child object:

1
2
3
4
5
6
7
8
9
10
11
class Hash
  alias _retrieve_property []

  def [](key)
    if has_key? :prototype
      _retrieve_property(key) or _retrieve_property(:prototype)[key]
    else
      _retrieve_property(key)
    end
  end
end

Now Hash will ask it’s prototype for a property that it doesn’t have.

1
2
3
4
5
6
7
8
9
10
11
12
13
person = {
  fullName: function() {
              console.log(this.firstName + " " + this.lastName)
            }
}

james = {
  firstName: 'Emmett',
  lastName: 'Brown'
}
james.prototype = person

console.log(james.fullName.inspect)
1
#<Proc:0x007fadf738b808@(irb):33>

this and function calling.

So this is pretty sweet, we can create an object with some properties, set it as the prototype of another object and that object will now retrieve properties from its prototype (and its prototype’s prototype, on and on up the chain).

The only problem we have is that of context. When you call a function via a property you want this to refer to that property’s object. But there is no this variable in the execution context of the block when we call it, so we need a way to inject it. The best way to do this is to use Ruby’s instance_exec to allow us to call a proc in the context of an arbitrary object.

So firstly, let’s create a Function class, which will store away the block and allow us to execute it in it’s own context:

1
2
3
4
5
6
Function = Struct.new(:this,:__block__) do
  def call(*args)
    instance_exec(*args,&__block__)
  end
  alias [] call
end

Next we need to modify our function method so that our block is wrapped in our new Function object:

1
2
3
def function(&block)
  Function.new(nil, block)
end

Now we need to modify the [] method on Hash to inject itself on property retrieval:

1
2
3
4
5
6
7
8
9
10
11
class Hash
  def [](key)
    value = if has_key? :prototype
              _retrieve_property(key) or _retrieve_property(:prototype)[key]
            else
              _retrieve_property(key)
            end

    value.is_a?(Function) ? Function.new(self, value.__block__) : value
  end
end

Now we should be able to call our fullName function and get the correct result:

1
2
3
4
5
6
7
8
9
10
11
12
13
person = {
  fullName: function() {
              console.log(this.firstName + " " + this.lastName)
            }
}

james = {
  firstName: 'Emmett',
  lastName: 'Brown'
}
james.prototype = person

console.log(james.fullName[])

Outputs:

1
Emmett Brown

And finally, an apology

So there’s our whirlwind tour of implementing prototypical inheritance and a (very simplified) portion of the JavaScript object model in Ruby. We need to take a moment to apologise to Matz for doing this to his beautiful language.

Needless to say, if we ever see code like this in production we will mock you mercilessly. You have been warned.

Comments