Ruby's rich Array API

Posted by flori Wed, 21 Dec 2005 12:36:00 GMT

There's an ongoing discussion in the Blogsphere about Ruby's Array API, that was started by Martin Fowler.

Elliotte Rusty Harold obviously didn't like it as much as Martin did. His article is full of misunderstandings and it seems that the only research he did was skimming and scanning the Ruby Array documentation and trying to find something to dislike. (That would be anything, that's not like it is in Java.) I mean, if he truly had questions about Ruby, why didn't he ask on the Ruby Talk Mailing List? Rubyists are nice people, he would have gotten lots of answers in no time.

He seemed to dislike the Array API, because it offers a lot of useful methods that one would expect in several different data structures instead of a single one. From the Ruby user's point of view a richer API isn't bad: Blending queues, stacks, sets, arrays and lists in one class, only makes its instances more useful. You don't have to use Array#to_a on an array, that is used as a stack. But it sure pays off to have it available, if you want to output it for example:

puts *stack

(The splat operator * is calling #to_a to do its work.) Or to iterate over its elements

for elm in stack
end

which requires the #each method.

This is a pragmatic approach: You don't have to find a way to convert a stack into an array first.

One problem with this is that inheritance is more difficult, because a class that extends Array would have to support all of the Array instance methods. Inheriting from Array is a newbie error in Ruby - it's much better to delegate to an encapsulated Array instance instead, in order to restrict the supported protocol. There are several ways to do this in Ruby: manually, method_missing, forwardable.rb, delegate.rb, and you can easily define your own delegate meta programming method.

Another problem is that offering a full duck typing protocol compatible to that of Array for your own classes is a lot of work. It would of course be possible to offer a mixin, that only expects a few methods to be in your class, and then offers a default implementation of all of Arrays instance methods based on those. This wouldn't be as efficient in every case, but would make it at least more easy. But these difficulty only occurs, if you really want to write your own "quacks like an array" type of classes from scratch.

In Ruby it's quite easy to achieve the effect of restricting the protocol an object will understand by means of meta programming. It's possible to extract a class that only offers a subset of the original protocol:

Stack = Array.extract([
  :last,
  :push,
  :pop,
  :size,
  :clear,
  :inspect,
  :to_s
])

Now the new born Stack's instances don't respond to all the array methods anymore, while Stack is still based on Array's implementation.

s = Stack.new
s.push 1
s.push 2
s.push 3
s       # => [1, 2, 3]
s.last  # => 3
s.pop   # => 3
s       # => [1, 2]

To get a set of operations with names, that are usually used in computer science text books, renaming would be cool as well:

Queue = Array.extract([
  :first,
  :push,
  :shift,
  :clear,
  :size,
  :inspect,
  :to_s
]).rename(
  :push   =>  :enqueue,
  :shift  =>  :dequeue
)

Aliasing and defining new methods (without reopening the class) comes in handy as well:

List = Array.extract([
  :first,
  :last,
  :push,
  :insert,
  :delete,
  :delete_at,
  :[],
  :[]=,
  :size,
  :each,
  :length,
  :inspect,
  :to_s
], [ Object, Enumerable ]).rename(
  :push   =>  :add
).alias(
  :[]     =>  :get,
  :[]=    =>  :put
).define(:sum) { |*start|
  inject(start[0] || 0) { |s,x| s + x }
}

The List objects will still respond to all Object and Enumerable instance methods (second parameter to extract). You can mimic Java's get and put methods as well:

l = List.new
l.add 1
l.get 0       # => 1
l.put 0, 23   # => 23
l.get 0       # => 23

To create a tool set that supports this is quite easy: Just duping the Array class object and reconfigure it's protocol. It can be done in 42 lines of Ruby. Here's the implementation and some additional examples. Is it possible to do that in 42 lines of Java, too?

Tags  | 1 comment

Comments

  1. Avatar MoeD said about 3 hours later:
    His "rant" is rather... weak. Considering he seems to make his living from re-writing the same XML book over and over, I wouldn't pay it much attention. But your points are well taken, and well written. IMO, of course.

Comments are disabled