January 8, 2007
There’s something about Ruby
What continually excites and amazes me about Ruby is how expressive it is, how extensible it is, and how elegant it is.
Because you can extend any class/object in Ruby, and because everything is an object, you can essentially refine or redefine the language and its idioms to suit your thinking or preferred way of expression.
Case in point – if you deal with collections, ranges or subclasses you will have used one of the the following expressions (Ruby in Java-esque style):
x >= 0 && x < n obj.kind_of? SubClassA || obj.kind_of? ClassB some_range.include? -1 some_hash.keys.include? "ABC" symbol_array.include? :a_symbol
Here are the same expressions using a more Ruby-like idiom:
(0...n).include? x [SubClassA, SubClassB].include? obj.class some_range.include? -1 some_hash.include? "ABC" # Hash.include? is an alias for has_key? symbol_array.include? :a_symbol
Certainly more succinct than the first set of expressions, but are they really more expressive?
In particular, the [SubClassA, SubClassB].include? obj.class to me just sounds, well…funny.
All of the above more or less translates to “does set S include element E?“. Maybe it’s just me, but in my mind it’s just more intuitive to rephrase that question as “is E part of S?”
What I want is to be able to call some method on any object asking it whether it’s part of some collection or range.
Fortunately, it’s a cinch to ‘hack’ this into Ruby so I can use it any time I need or feel like it, and that’s what I’m about to do.
First off, let’s say we want to call this method part_of?, and alias it as in for brevity.
Now, we want to be able to call this method on any object, and pass it any other object that might respond to the include? message/method. This means: arrays, hashes and ranges.
All this sounds more complicated than it is when implemented, so let’s jump right into the code:
class Object # Returns true if the given hash, array or range # includes this object. def part_of? (set) return set.include? self end alias in part_of? end
Let’s dissect the above line by line.
The first line tells Ruby we’re ‘redefining’ the primitive class Object. That’s right, we’re telling Ruby that we’re extending Object which is the superclass of all other classes.
The next line (ignoring the Rdoc-style comments) says “We’re defining the method part_of? which accepts a parameter called set“. Because we’re defining it in class Object, this method becomes available to any other object or class from now on – including primitive numbers/integers, string literals, etc.
The body of the method simply says, “Does set include this object?”. The return keyword is redundant – I just wrote it in for clarity.
That line takes advantage of a nifty feature of Ruby (and other dynamic languages such as Python) known affectionately as Duck typing – “if it walks like a duck and quacks like a duck, it must be a duck.”
Let me explain. In Ruby method calls are actually passed as messages. So, as long as set responds to the include? message (i.e., it defines an include? method), our code works.
It’ll work for arrays, ranges, hashes, and any other object in the future that we define or use as long as it responds to include?.
The final line simply tells Ruby “Make the method named in mean the same as the method named part_of?“.
All in all, with the above six (functional) lines of code we can now rewrite our original expressions as:
x.in 0...n obj.class.in [SubClassA, SubClassB] -1.in some_range "ABC".in some_hash :a_symbol.part_of? symbol_array
Again, maybe it’s just me, but aren’t the above expressions just a little more understandable at first glance? They’re at least shorter, for the most part.
Also, note that we can now write the range 0...n without enclosing parentheses – which you may or may not like. So use parentheses if you want!
This is what makes Ruby powerful – and fun! Because it’s extensible and expressive, you can use it, even abuse it, however you want. If you do it properly, well, then it can still remain… elegant.
In short: while Ruby is already quite expressive, because of its extensibility there’s nothing stopping you from making it even more expressive to you!
Expressiveness => Clarity => Maintainability => Productivity*
*But you can’t write that in Ruby! Yet. I’m still trying to figure that out. =)
Alistair Israel started learning programming using BASIC on a Sinclair TRS-80. That was many years ago. He’s still learning.