Java and Ruby partisans: You’d think they’d get along better
There are honest critiques of Ruby. I’ll put that up front. It’s a fun language, but there are definitely areas which appear to have been designed by martians. But when Java developers lash out at Martin Fowler’s assertion that things like Array#first and Array#last are useful, their point tends to get buried in a flurry of misunderstanding. That’s a shame, since it’s a good point, too.
For example, take Elliotte Rusty Harold’s criticism of the Array class in Ruby. Much of what he says is true: Array#abbrev is almost useless, and there’s plenty of cruft to consider in Ruby’s standard classes. Making matters worse is that the documentation for these classes (outside of the Pickaxe book) is piss-poor, ranging from non-existent (REXML?) to delirious (YAML’s pony parade), to pathologically laconic (TMail). Given this wealth of targets for critique, it’s odd that Elliotte chose to bag on Array, which is by contrast well-documented. Finally, the vast majority of his issues with the Array class are due to misunderstandings of the methods (which can be blamed on the documentation) and of Ruby conventions and idioms (which can be blamed on him not being a Ruby programmer).
For example:
On Array#*:
“Returns a new array built by concatenating the int copies of self.” I’ve thought and I’ve thought, and I still can’t figure out when you might need this.
Removing this method would mean removing matrix multiplication (v. useful), which is the real point of Array#*. This is a nitpick for the purpose of nitpicking; it wouldn’t alter Array’s method list at all.
On Array#<=>:
A comparison between arrays. However, without me stating it here, can you understand how to tell whether two arrays are greater than, equal to, or less than each other? There’s more than one possibility here. This needs to be pulled out into a separate interface that allows for different comparison algorithms.
Um… it uses the <=> method from the object of each element, which means that the comparison algorithms it uses are object-specific. The “underspecification” here is that comparison operators are implemented at the class level where it belongs, not at the primitive level, like Java.
On Array#compact!:
But compacting an array and sometimes returning nil? Does anyone ever use this? There’s also a perfectly good method to compact an array and return it, without ever returning nil. That’s a lot more useful. I only care whether the list now contains any nil objects or not, not whether it once contained nil objects.
First, the exclamation point denotes this method as a “destructive” method, meaning it operates on the object in question rather than returning the results of its operation. Having a destructive version of #compact means we don’t have to constantly do silly things like blah = blah.compact all the time.
Second, in Ruby nil and false both evaluate to false in a boolean condition. So if you want to know if any elements are nil and then remove them, #compact! comes in handy:
results = do_something_complicated
if results.compact!
puts 'One or more of the results were nil.'
else
puts 'Everything was A-OK!'
end
On Array#to_a:
Is Ruby not object oriented? Is there some deep, dark reason we can’t just use an instance of the subclass as an instance of the superclass?
See Object#to_a, from which Array descends.
On Array#each_index:
Am I missing something, or is this just a for loop across the array indexes? Why not just use a for loop, or whatever Ruby’s equivalent is?
If I was a Java programmer, code blocks would confuse and frighten me too. Why not a loop? Because we have code blocks; it’s Ruby. More importantly, this functionality is provided by the Enumerable module, and can be very useful.
On Array#at:
As near as I can figure, this is exactly the same as using array brackets except it uses a method call. If there is a difference, it’s pretty subtle. DRY, anyone?
First, the square brackets is a method call. Second, #at is faster and is intended for use when you need to access one specific element quickly. Array#[] is quite a bit more flexible, but slightly slower. Third, this isn’t repetition because Array#[] uses the underlying code of Array#at when passed a single integer as a parameter. DRY applies to the number of times you write the code, not the number of times you call it.
On Array#pop, #push, #shift, and #unshift:
Someone pushed a stack into the list. Didn’t we learn from that mistake in Java? (OK. push is really just append, but still, pop? And if you have pop why do you need last? Does anyone here remember DRY?)
Pop isn’t the same thing as last, and he’d know this if he’d paid attention. Array#pop removes and returns the last element, whereas #last returns the last element. And which is repeating yourself more: adding two methods to Array to wrap all FILO stack functionality, or hauling off and adding a separate class? In which ways do an Array and a Stack differ? Well, those two methods. Not every data structure needs its own class, especially when it’s just variations on a theme. Need a Queue? You could add another class with its own API, or you could add two more methods: #shift and #unshift. FIFO and FILO, and you only added a total of four methods. Seems kinda straightforward to me.
Now, Elliotte’s a smart guy, but it looks like he went to the doc page, saw a big list of methods, and freaked out. And that’s a shame, since there are a lot of methods in Array which could use the boot, either into another module (or a requireable module, like they did [poorly] with Date and its friends), or into the special hell designated for poorly-thought-out methods.
My hit list for Array?
#length: Stick with#size, since it’s the Ruby idiom.#assocand#rassoc: Provide a simple transition to aHashinstead.#fetch: If anything, this should be named#at!.#zip: By martians, for martians. What the hell do people do with this?#abbrev: See#zip. Twice.
As you could have guessed, I’m a fan of Ruby’s rich method lists, because they reduce the amount of code that I have to write, and let me focus more on the actual problem at hand and less on working around the language. If I had a nickle for each time I’ve typed something like this:
for(var i=0;i<somethingBig.count-1;i++) {
print(int_to_str(i) + ": " + somethingBig.items[i]);
};
I would have gladly paid that nickle to substitute it with this:
something_big.each_index do |i|
puts "#{i} : #{something_big.items[i]}"
end
Not a huge thing, but it matters.
February 25th, 2006 at 2:04am
Don’t dis on my Array#zip ;-) I have had to use that one a few times. For one use its a cheap way to iterate over two arrays at once without using a Generator.
a = ["foo", "bar", "baz"]
b = ["qux", "jim", "bob"]
a.zip(b).each{|ar| puts “#{ar[0]}:#{ar[1]}”}
#result:
foo:qux
bar:jim
baz:bob
February 25th, 2006 at 1:03pm
Ezra:
a = ["foo", "bar", "baz"]
b = ["qux", "jim", "bob"]
a.zip(b).each{|ar| … }
zip takes a block, you can just write: a.zip(b) { |ar| … }
February 25th, 2006 at 2:10pm
nice! thanks.
July 31st, 2006 at 10:32am
something_big.each_index do |i|
puts “#{i} : #{something_big.items[i]}”
end
Funnily enough, this particular construct is actually handled more elegantly in PHP:
foreach ($something_big as $i => $item) {
echo “$i: $item”
}
January 19th, 2007 at 7:53am
otiose I believe this more closely matches your php block;
something_big.each { |i,item|
puts "#{i}: #{item}"
}