fingernails in oatmeal

Lisp has all the visual appeal of oatmeal with fingernail clippings mixed in.

— Larry Wall

Authors

Metaprogramming: Ruby vs. Javascript

I’ve recently been working more with Javascript outside of the browser, thanks mostly to node.js. To jump in and learn the language, I tried to emulate some of the metaprogramming I’d learned from working with ruby. I’m going to show examples in ruby and the equivalent code in Javascript. Let’s start by setting up some ninja objects.

This is all pretty self-explanatory. We’re using node.js to require the puts statement in Javascript.

Now that we have our objects, let’s reopen our class/prototype and add some new behavior. This was one of the first things I found interesting (and later dangerous (and later interesting again)) about ruby. Turns out this is a piece of cake in JS as well.

In ruby we reopen the class simply using the class keyword. In Javascript we grab the prototype for our object and just attach a new function.

Next up we’ll add some behavior to specific instances. We want to add a method for throwing stars only to the drew instance, not the adam instance. Here’s the code.

In ruby, we add a method to the drew object using def {instance_name}.{method_name}. In Javascript the code is almost identical to the code that adds methods to the prototype. We just attach a function directly to the instance rather than the prototype.

The next thing we’ll look at is calling methods dynamically. The ability to call a method with a name given to us at runtime is a powerful tool in DSL creation and metaprogramming. Here’s the code.

This is very trivial in both languages. Ruby allows us to call methods based on strings or symbols using the send method, whereas JS allows us to use the square bracket syntax to access members of objects with strings. We then tack on the parentheses to call the method which we’ve retrieved from the object.

Now, let’s get into a meatier topic: defining methods dynamically with closures. What we want is the ability to define a method dynamically (given a name) that is also a closure over the lexical scope at the point of method definition. This means we will have access to variables, methods, and classes that are available at the time of the method defintion. Read the code below and then I’ll describe what’s happening here.

Let’s look at the ruby code first. You may notice that we’re using send to call define_method. This is a trick (hack) in ruby that allows us to call private methods without self being the implicit receiver. define_method takes a name for the new method and a block which describes the body of the method. In ruby, this block is a closure which means we have access to the scope at the time of the method definition. In the code example above, we take advantage of having access to the scope by returning the color defined outside of the method. This would be impossible if our block was not a closure because there is no guarantee that we would have access to this variable at the time of the method call.

The most interesting thing about the Javascript example is that it is exactly the same as the example of adding a dynamic method to a class. There is no difference because Javascript functions are closures. This means we get the ability to capture the scope at the time of definition for free. The ubiquity of closures in Javascript is extremely powerful and, as we have seen so far, makes metaprogramming very easy.

Let’s end with one final example: defining a method dynamically on an instance that closes over local scope and accesses the instance’s state. Code first, description second.

We’ll talk about the ruby example first because it needs more explanation. You can see the first thing we do is reopen the Object class to define a method called metaclass. This method retrieves an object’s metaclass (or ghostclass (or eigenclass (or whatever))). You can think of a metaclass as a class definition specific to a single instance of a class. This means we can add methods to an object’s metaclass without adding the same behavior to all instances of that object’s class. So we grab our ninja’s metaclass and then define a method using define_method, identical to the previous example of define a method on the class. One interesting thing to note is that we are calling the name method in the body of our define_method block. This is to show that our method has access to the instance’s state.

To be honest, there’s not much to say about the Javascript example because it is so simple. We avoid the whole metaclass business because Javascript uses prototypal inheritance. This means that Javascript does not distinguish between classes/prototypes and instances and, therefore, we can add our desired behavior directly to the instance. We use the exact same technique for adding a method to the prototype, but this time we simply add the function directly to the instance. Again, this function is a closure. And again, this function has access to the instance’s state using this.

Thanks to this exercise, we’ve learned that Javascript has tremendous metaprogramming facilities. As Douglas Crockford said,

…JavaScript has more in common with functional languages like Lisp or Scheme than with C or Java.

Closures are so ingrained in the language’s design that metaprogramming seems to happen without even trying. This little exercise has left me very excited about the potential of Javascript not only as a great language for the web, but also as a powerful server-side language. You should be excited too.

Drew
  1. paginas-web-uruguay reblogged this from fingernailsinoatmeal
  2. stevegraham reblogged this from fingernailsinoatmeal and added:
    great post, literally...time favourites. I’m so tired
  3. rosario-skinkle reblogged this from fingernailsinoatmeal
  4. waku reblogged this from fingernailsinoatmeal
  5. razorsharp reblogged this from fingernailsinoatmeal
  6. lachstock reblogged this from fingernailsinoatmeal and added:
    Pretty good summary for anyone not familiar.
  7. subdigit reblogged this from fingernailsinoatmeal
  8. fingernailsinoatmeal posted this