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
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.