Humane Interfaces and Duck Typing

by Ryan Leavengood

A few weeks ago Martin Fowler started a miniature firestorm by putting an entry in his bliki about Humane Interfaces. The irony was that the negative opinions it generated were not so much about humane versus minimal interfaces, but about the fact that Ruby's Array class has 78 methods versus the 25 declared in Java's java.util.List class.

The main critic of Ruby's Array class was Elliotte Harold, who among other things called it "about three times as bad as a 25 method List class." The thing is, as a Java programmer I can understand Mr. Harold's arguments, and I think they might be valid in Java. But they are not valid in Ruby. You see programming in Ruby is a completely different experience to programming in Java. I should know, as I like to say I was "born and raised" on Java (it was my first major programming language), yet I've also been programming Ruby since 2001.

One of the biggest differences between the two languages is Java's static versus Ruby's dynamic typing. This is also one of the biggest reasons a 78 method Ruby class is perfectly fine in Ruby (I would go so far as to say wonderful) whereas the same in Java might be a nightmare. Let's take a look at a code example (from IRB, aka Interactive Ruby):



irb(main):001:0> list = []
=> []
irb(main):002:0> list << 1
=> [1]
irb(main):003:0> list << "two"
=> [1, "two"]
irb(main):004:0> list[0]
=> 1
irb(main):005:0> list.last
=> "two"
irb(main):006:0> stack = []
=> []
irb(main):007:0> stack.push "a"
=> ["a"]
irb(main):008:0> stack.push "b"
=> ["a", "b"]
irb(main):009:0> stack.pop
=> "b"
irb(main):010:0> queue = []
=> []
irb(main):011:0> queue.unshift 1
=> [1]
irb(main):012:0> queue.unshift 2
=> [2, 1]
irb(main):013:0> queue.shift
=> 2



Here I have list, stack and queue data structures. Yet they are all really Ruby Arrays. Nowhere above do I need or care about what actual class these objects are, I just call the methods I expect and things just work. This is frequently called "duck typing" in the Ruby community (and I'm sure other places), because as long as it quacks and walks like a duck, we are happy. In other words, an object only need respond properly to a given set of methods to satisfy the programmer's requirements. Whether it is an Array or a QuizzleBick under the scenes is irrelevant.

This is where Mr. Harold's Java prejudices show most obviously, and also it is clear as crystal that he is not a Ruby programmer, and he just cannot "get" what it means to be one. He is so upset by Ruby's Array class behaving like a List and Stack and Queue that he fails to see how perfectly this idea fits in with Ruby and the way it is programmed.

In the same way that learning new programming languages can open one's mind, it seems using one for too long can close your mind as well.

5 Comments

riffraff
2005-12-23 04:08:37
You have to check out his latter article.



I mean, I was almost falling off my chair when I read his rant about
Array.new which creates copies of an object.
"I never needed an array containing copies of an object".
I guess he did not understood that zero is an object in ruby, or his habits of array initializing are really strange.


No to mention that he thinks the Abbrev module is builtin.

Ryan Leavengood
2005-12-23 07:10:36
Hey riffraff,


Yeah, that is actually the article I meant to link to in the second link above, but I had linked to the December 6th article. I've updated the blog post.


I'm sure he knows his Java stuff quite well, but it is clear he just doesn't get Ruby.

Eric
2005-12-23 12:58:26
It seems that your major justification is that Ruby is different than Java and that Mr. Harold doesn't understand Ruby. But saying that it is different is not a particularly strong argument. Why is it better to use an Array for these three conceptually different data structures?


I can give you a reason why it's not very good. With a queue you need to manipulate both ends of the "list". One end is where new items are inserted, the other end is where items are removed. Depending on the underlying implementation, this can be a very costly operation. If, as in most arrays, one has to move each element up a notch or down a notch during either of the operations, then this is costly and inappropriate for a queue. A linked list implementation, though, is very efficient.


In Java you can use a LinkedList for a queue (there is no separate Queue class as I recall). But for more array like operations one may opt for an ArrayList. Having those options from the get-go seems very powerful to me.


By the way, your queue examples is screwed up. Basically you're demonstrating a stack a second time, although the operational end is the begining of the array rather than the end of the array.


So is Ruby being different the strongest argument you can muster to justify this?

Ryan Leavengood
2005-12-23 13:46:12
Hi Eric,


Firstly you are right about the queue example, though to justify myself I was just working off of Mr. Harold's examples of the methods he dislikes in Ruby's Array class, and to quote:



array.shift and array.unshift(obj, ...)


Now the list has turned into a queue!



I suppose the proper queue would be a unshift to add elements and pop to delete them. Or a << and a shift.


But frankly we are talking fairly minor semantics here because after coding Ruby for a while now, I feel it is premature optimization to worry about the nature of a data structure in the early stages of coding. I know this goes against a lot of what we are taught in computer science classes at university, but frankly a lot of theoretical aspects of computer science just aren't very pragmatic or applicable in the real world. In addition knowing how the internals of a data structure work goes against object oriented principles of data hiding and using black box structures.


If I needed a queue for a Ruby program I was writing, I would start with a Ruby array and then benchmark, and if I found that the array was slowing things down because it wasn't optimized for being a queue, I would consider writing a C extension to provide that queue (or see if one was already written by someone else, which is probably the case.) But I seriously doubt this would be required, because unless we are talking about huge queues (something extremely rare in my experience), Ruby's Array class would rarely be the bottleneck. Plus if speed is so important I might not use Ruby, since I will freely admit it isn't as fast as most other languages.


But there is a reason for that, and it plays into my argument against the class specialization you talk about in Java: Ruby is a very high-level language. I don't want to worry about having to pick the right data structure for my problem when a basic Array will do the job nicely. I don't want to worry about having to refactor code because I made the wrong choice in the beginning and am further constrained by statically declared types that must be changed (I've never needed Eclipse-style refactorings when doing Ruby.)


I just want to solve my problem with easy to write, easy to read, easy to maintain code.


Now that is just me. Maybe you and Mr. Harold like more control over your code and how it works behind the scenes. You like to work at a lower level. That is fine, and in some cases I would be in your camp (hardware interfacing, game programming, high speed code, etc.)


But most of the time I'd prefer the high level of Ruby. And working at that high level is different than working at the lower level of Java or C. You may not think that is a valid argument, but until you try it I don't think you can truly understand how different it really is.

tomhath
2006-03-30 05:23:01
Shouldn't queue be using unshift and pop? (I assume you wanted a FIFO, rather than a LIFO which is just a stack?)