Ruby is Object Oriented?
Well, for better or worse, I've been doing a lot of Ruby coding lately.
One of the first great selling points that Ruby developers like to flaunt is that their language is 'truly object oriented' in that everything is an object, even numeric literals. Really? Cool! Well, I've done a lot of object oriented programming over the last twenty years... You know, languages like Java, C++, Objective-C, ObjectPascal, and yes, even a little SmallTalk.
With the exception of SmallTalk, all of these languages feature visibility modifiers that allow you to identify a method as public, protected, or private, and all of the languages present consistent behavior when you tag your methods as such. In particular, private methods allow you to write code that is specific to the internal implementation details of your class, and those methods are never exposed outside of your class.
Ruby has private methods too... or does it? Take this code for example:
class BaseClassAccording to the traditional rules of object-oriented inheritance, what should the last line of this snippet display? If you were thinking "base class private," you'd be thinking like an object oriented programmer who has spent more than three minutes writing code in Java or C++, but according to Ruby, you'd be completely fucking wrong.
private
def private_method
puts "base class private"
end
public
def public_method
private_method
end
end
class SubClass < BaseClass
private
def private_method
puts "sub class private"
end
end
d = SubClass.new
d.public_method
Why? Well, Ruby, while it doesn't allow you to call private methods from outside of their classes, does allow you to override them in subclasses. That's okay, other OOP languages allow you to do that as well, except for one thing: other OOP languages don't add private methods to the class's virtual method table, and so there's no risk involved in defining methods of the same name in descendants.
Ruby makes it even worse by making private methods accessible to subclasses. That's what protected methods are for! So what used to be the proper way of hiding implementation details from consumers of your APIs has now become a fucking minefield. The Ruby developer stance regarding this mess is "well, you should be familiar with the code you're extending, or you should just write an adapter."
I'm sorry, I thought the value of object-oriented programming was to allow developers to utilize or inherit the public functionality of APIs, while remaining buffered from the internal details of those APIs. It was supposed to save us time and allow us to concentrate only on the functionality that we needed to implement rather than the inner workings of functionality that we're leveraging.
As Ruby developers, I suppose if I decided to open a socket and send a byte buffer from point A to point B, you'd also recommend that I learn the internal workings of the Linux kernel? In that case, I should probably also know the inner workings of the BSD kernel as well, because it also has a socket() call.
Ok, so let's say I follow your advice and I refer to the code that I'm extending and purposely avoid the definition of methods that duplicate the names of private methods in a base class. What happens if the upstream vendor of an API that I'm using decides to add a private method to their base class that duplicates the name of a method in my class? Should they keep tabs on the thousands of potential 3rd party extensions to their classes? Should we be forced to perform code reviews every time we update a dependent API?
What the fuck, Ruby people?! I shouldn't be seeing "sub class private." Fix this!
Technorati Tags: C++, Java, Objective-C, Programming, Ruby, SmallTalk

3 Comments:
This is not at all different from Objective-C or Smalltalk. Smalltalk, Objective-C and Ruby all dispatch messages to methods dynamically; this means a subclass can override a superclass method and the subclass method will be called, regardless of whether
"Protected" and "private" modifiers are really derived from C++ and its more static interpretation of object-oriented programming (and inherited by Java and C#) rather than something inherent to object-oriented programming.
In fact, Alan Kay - a strong proponent of always using dynamic dispatch, and the coiner of the term "object-oriented" - might even say that it's "protected" and "private" methods that aren't OO.
Untrue.
Methods defined in the implementation section of an Objective-C class that are not declared in its interface are inaccessible to consumers of that class and are effectively private methods. You may have noticed that I specifically excluded SmallTalk from the visibility discussion, although SmallTalk has similarly elegant workarounds.
The dynamic dispatch that you're describing might be interpreted as polymorphism, but polymorphism should be a design option rather than an environmental straightjacket. While dispatch is powerful, without the ability to impose restrictions on its exposure, it can be downright dangerous. In the presence of many and disparate maintainers, and in the absence of compartmentalized visibility for important and/or essential functionality of a code base, nothing but eventual breakage can result.
In the case of Ruby, this potential breakage is amplified by the fact that there may be many layers of downstream usage, and a modification such as I have described, at any point in that chain, can break an entire system, potentially crippling thousands of end-user applications. Compounding this is the fact that downstream developers may have absolutely no idea why the APIs they use no longer work. Short of being hyper-vigilant regarding the code of others, the workaround, as Ruby developers claim, is to completely abandon object oriented programming principles by writing wrappers.
So tell me, Chris, how can you assign the moniker ‘Object Oriented' to a language that encourages its users to abandon OOP principles, instead engaging in the extra work that OOP was meant to free us from? Or is it that they’re just working around the language's inherent failings?
You say that 'protected' and 'private' are a creation of C++ and Java, and that Alan Kay would consider them to be anti-OOP, so then please explain to me (1) why Ruby even implements them at all, and (2) why it deviates so thoroughly from its influences considering the fact that you believe visibility to not be part of Object Oriented principles?
Of course, you'll disagree with me right up until you get bit in the ass by this very situation. After weeks of trying to figure out why your methods are being called inappropriately and at random times, you'll have to jump into your code and rename those conflicting methods... It's a shame that humans tend to use similar names for functionality that may only be vaguely related, isn’t it? Now your ‘processList’’ method can’t be called ‘processList’ anymore. How about ‘process(your class name here)SpecificList’ instead? That'll work. Hell, you might as well just qualify all of your methods to avoid any potential future ambiguity.
Thereafter, you'll have to send out patches to your APIs, politely explaining to all of the users of your SDK that you've had to rename a method or five because an upstream vendor decided to add a private method to their class hierarchy that, in the 'sane' world wouldn't have hurt anyone, but that in your free-software-lets-do-everything-half-assed-and-call-it-a-feature world ends up causing downstream developers thousands of cumulative man-hours. I'm sure their employers will be happy about their language choice then.
And then you'll probably have to do it all over again a few months later... Same upstream developer? Different upstream developer? You won't know 'til it happens, but it'll keep you on your toes. Lucky all of us!
BTW, my apologies, as Objective-C exhibits the same behavior as Ruby in this case (with unpublished methods). Doesn't mean the behavior isn't wrong though.
The proper dispatching behavior should probably check the implementing class of a message source (if the message is being sent to/from self) and prefer an immediately available unpublished (private) method to those available from the terminating instance's dispatching table.
Effectively we're talking about the ability to shadow methods from the immediate source class, so long as the message/method is addressing self.
This would ensure sensible dispatching of necessary proprietary behavior while preserving the dynamic dispatching that one would otherwise expect. Less potential for breakage, your implementing classes don't accidentally call methods they never intended to call, nobody's the wiser and everybody's happy.
Post a Comment
Links to this post:
Create a Link
<< Home