Byzantine Reality

Searching for Byzantine failures in the world around us

Ruby as the Joker

A while ago, I compared the C programming language to a miniature nuclear weapon from Fallout 3. It’s still an apt metaphor, as the language does give you more than enough power to take care of the biggest tasks ahead of you and blow yourself to kingdom come in the process. It takes an expert to really use it but once you figure it out, you can avoid a lot of the pitfalls involved.

Ruby, on the other hand, is an entirely different beast. It relates more to one of DC’s most famous supervillains, the Joker.

On first glance, the Ruby programming language looks harmless. Fun, even! At a very rudimentary level, it takes a lot of things we loved from Perl and dumped the things we didn’t like. And for the last few months, that’s what I thought of the language. Highly readable, writable, and maintainable. So it should be perfect, right? Not quite. The other day I stumbled upon this little gem and knew I had to pick it up:

The book is still in beta, but I highly recommend it. Metaprogramming Ruby is extremely easy to read, especially considering the topics involved. It dishes out all of Ruby’s hidden powers and the complete wackiness the language offers. It reveals Ruby’s true nature, not just as Perl++, but as the Joker itself.

But how does it make this huge transition? It’s simple. Ruby will let you do anything to anything, with only the most minimal of restrictions. The following short list will be no surprise to expert Rubyistas, but to someone from other programming languages, this is baffling to the point that it will remind you of the Joker. For example:

  • Need the String class to have new methods? Add them in yourself! (open classes / monkeypatching)
  • Want to intercept method calls to methods that don’t exist and add your own logic in? Go for it! (method_missing)
  • Want to dynamically add methods or remove methods from a class or object? Not a problem! (method def and undef)
  • Need to make a domain specific language? Go right ahead! (instance_eval)

Any of these language features are well outside of the range of what other languages make it possible to in the same amount of time. I’ve always been interested in Java’s Reflection capabilities, but disliked it for the difficulty of use and the huge performance hit you take using it. Yet with Ruby’s extremely dynamic nature, you’re already taking the performance hit, so why not get super-easy reflection? This is the core of the wackiness involved and the heart of the Joker analogy: you can take something that everyone knows to do X and instead make it do Y, which can either be very similar to X or quite different. Part of the AppScale code does just this! Look at this simple code:

class Exception
 
alias real_init initialize
 
def initialize(*args)
    real_init
*args
   
File.open("/root/appscale/.appscale/exception.log", "w+") {
     
|file| file.write(message)
   
}
   
Syslog.open("appscale") { |syslog|
      syslog
.debug(message)
   
}
 
end
end

With this code, whenever an exception is thrown anywhere in any Ruby code, we log it to a file. Now, if my code crashes, I can always see what caused it! In our case specifically, we don’t have handles to standard out and standard in so we simply couldn’t run it at the console and get the message when it crashes, so this gets around the problem very nicely. The downside is that since this affects ALL Ruby code, we’ll see exception messages about libraries we use at a lower level that throw exceptions as part of their normal operation.

Now, a word of caution. These techniques get a lot of heat for being dangerous, and it’s completely true. If you use a tool the wrong way, you’re likely to hurt yourself pretty badly. In C, if you don’t manage your memory effectively, it will come back to bite you. And although this looks way different, the principle is the same. And to its credit, the book does an excellent job of telling you the techniques and the problems that can come up with each technique. It presents examples of the technique gone wrong and asks you to fix them as exercises. The answers show you that very simple mistakes can cause egregious problems with these powerful tools (again, just like in C).

Yet it’s clear to me that the techniques are necessary. Do I use them everyday? Of course not. I use them when I need to use them, and at no other times. Just like with any other tool, there’s a time and a place for it, but knowing as many tools as possible is key to being an effective programmer.

I’ve also had a lot of trouble finding a good way to pick up these metaprogramming techniques, and this book is bar-none the best way to do it. Paolo Perrotta makes it painless to learn Ruby’s most complex secrets and use them in practical applications. Each chapter uses a number of RubyGems as references to show how others have used these techniques, and once one of the next betas comes out, will also cover Rails and the magic they pull off. So check it out if you know Ruby a little bit; you’ll be surprised at what it has to offer.