Ruby Interfaces

I'm almost done with The C++ Programming Language. My eyes started to glaze over when I hit the long drawn-out descriptions of some of the STL classes. It might be good as a reference but it doesn't do much to keep me awake at 6:30 AM on the bus every morning. The last couple chapters about overall design methodology are nice though. I just finally realized the usefulness of private inheritance, for example. I also learned about NVI after googling for related info, which is interesting.

I got to thinking a bit about how C++ inheritance differs from Ruby. (The answer is "a heck of a lot", obviously.) For example Ruby lacks the concept of interfaces or abstract classes for the most part. There's no built-in mechanism to say "A subclass must implement this method". The closest I've seen (via the Ruby mailing lists) is something like

class Base
  def foo
    raise NotImplementedError
  end
end

class Sub < Base
  def foo
    puts "IMPLEMENTED"
  end
end

Rubyists tend to say that this is not a good thing to do in Ruby. One reason given is that if you leave method foo out of class Base, and you don't implement it in class Sub, you'll get an exception when you try to use Sub#foo anyways. But in that case you'll get a NoMethodError, not a NotImplementedError, which isn't as nice; the benefit of raising a NotImplementedError is to say "This method SHOULD do something, but it doesn't YET because the programmer hasn't gotten around to writing it", as opposed to NoMethodError which says "This method doesn't exist, and I have no idea if it should or not".

Someone on the mailing list also raised a good point that Sub#method_missing may cause breakage unless you implement a method foo that raises an exception explicitly. Otherwise a call to Sub#foo may or may not raise an exception at all, depending on what method_missing does.

In Ruby, even if you have a Sub object, or even a Base object, you don't really know that it actually has a method called foo at the moment anyways, because someone could have used Module.remove_method to erase that method out of the class. And even if there's a foo method in class Sub that does the right thing, any object of type Sub could override it using a singleton class. So even given you know the class of an object, you can't know ahead of time if the method exists, and if it does exist you can't know exactly what it does, which kind of defeats the purpose of trying to enforce interfaces through inheritance. And to confuse matters further, again on the topic of method_missing, even if a method isn't defined as a method it may still be handled. Consider if someone implemented class Sub like this:

class Sub < Base
  def method_missing(sym, *args, &b)
    if sym == :foo
      puts "IMPLEMENTED!"
    end
  end
end

In this case, you still get a NotImplementedError when you try to call Sub#foo, because it was defined via Base. So there's an argument to be made that if a method isn't implemented, leave it undefined and let every class deal with it as it sees fit.

Interfaces of this sort also goes against duck typing. As long as an object has a method called foo that "does the right thing", a Rubyist would expect that it can be used anywhere you'd use a Base or Sub object. In Ruby it's pointless to force people to subclass Base just so they inherit a method of a certain name. Given a lack of multiple inheritance in Ruby, being forced to subclass Base may very well make it impossible to get an object to pass an "is a Base?" test.

Ruby has mixins via Modules, which are kind of like interfaces and seem to be used in the general spirit of interfaces pretty often. But nothing is enforced by the interpreter when you use them, which can be fun to debug.

class Foo
  include Comparable
end

f = Foo.new
f2 = Foo.new

puts f < f2

That code is perfectly legal, but obviously doesn't work because <=> is not defined. It's up to you whether you define <=> or not. If you don't, the methods you mixed in from Comparable will just fail at runtime, but again, with a perhaps somewhat less-than-ideal NoMethodError.

In place of explicit interfaces, what we have in Ruby seems to be lots and lots of convention. If you make a method that just so happens to be called to_s, it just so happens that your class will work with lots of other methods that expect it to exist. This is nice, if you already know the conventions, but if you don't know the conventions, you're screwed. I'm in the process of training someone at work to use Ruby, and I'm often asked "Where are all these conventions written down? How was I supposed to know that?" That's a good question. There's no way to know it, other than to read lots of other people's code. In a type-checked language you have the benefit of having right in front of your face a bit sign saying "I'm an abstract class. Implement me."

I can see the benefit in (and often take advantage of) the flexibility of Ruby, but I can also see potential benefit in the strong type checking of C++, when it comes to dealing with interfaces. I haven't written enough C++ to know which appeals to me more though.

July 25, 2007 @ 1:18 AM PDT
Cateogory: Programming

5 Comments

David Grant
Quoth David Grant on July 25, 2007 @ 4:09 AM PDT

How do you read that so fast? How much do you read per day? I haven't been reading it every day on the bus, only 1/5 days per week (still working on another novel). In about 4 hours (I think) I've gotten through 4 chapters.

Brian
Quoth Brian on July 25, 2007 @ 6:14 AM PDT

My bus ride is 1 hour each way, five days a week, so I have quite a lot of time to read there. I read about a chapter a day.

Even though I "read" everything, whether I retained it all is highly questionable. I don't think you really ever finish reading a book like this. But I already knew C++ to some degree before starting so that helped.

gerald kamper
Quoth gerald kamper on August 17, 2008 @ 11:44 PM PDT

class Base def initialize raise NotImplementedError.new("sayHallo not implemented yet") unless (respond_to? :sayHallo) end freeze end

this seems to be a better solution for abstract classes in ruby - but it's still an ugly workaround... :)

dude
Quoth dude on May 05, 2009 @ 1:13 AM PDT

gerald kamper: rtfm

jsm
Quoth jsm on March 09, 2011 @ 1:48 PM PST

This is useful info. It helped me as I seek to understand Ruby.

Speak your Mind

You can use Markdown in your comment.
Email/URL are optional. Email is only used for Gravatar.

Preview