Tay Ray Chuan home archive

a re-examination of Ruby's reopenable classes

Mon, 2 Apr 2012 21:58:01 +0800 | Filed under design, ruby, scala

I used to be of the opinion that Ruby takes OOP to the limit, by allowing reopenable classes.

Take the factorial function. It operates on numbers, so conceivably, there should be a "factorial" message on receivers of type Number (to borrow from Smalltalk). Here's how it can be done:

class Fixnum
  def factorial
    return self if self <= 1
    self * (self-1).factorial
  end
end

Now, you can just do

5.factorial #120

But this approach doesn't lend itself to reuse - what happens if someone reopened and defined that method before you did? (Sounds like PHP's function_exists()?) Paraphrasing the author of DSLs in Action:

if you do not put <method> in <class>, someone else will.

In fact, this is termed somewhat derogatorily as "monkey-patching".

Recently, I've been digging into Scala, and just like how I felt about Ruby when I first touched it, I feel that Scala brings OO design to a whole new level.

Re-examining the above factorial, in Scala this is done through "rich" wrapper classes and implicit conversions, which is a more reusable approach as compared to reopening classes.

The above factorial would then be implemented in Scala like so:

class MyInt(x: Int) {
	def factorial(): Int =
		if (x <= 1) x else x * new MyInt(x-1).factorial
}

The final step is the implicit conversion to tell Scala to treat Int's as MyInt's:

implicit def int2myint(x: Int) = new MyInt(x)

Now you can do

scala> 5.factorial
res5: Int = 120

I guess this isn't such a great example since we have to construct a new MyInt instance on each recursive call to preserve the return type. But in cases where you need extra helpers/state info, you'll reap the benefits from having a "heavy", wrapper class.

blog comments powered by Disqus