Learning Ruby

brian_d_foy on 2002-10-29T07:35:56

I have been playing with Ruby this week because Mike Stok has written a nice "Ruby for Perl Programmers" article for The Perl Review. I started to port Business::ISBN to Ruby, and the code is turning out to be a lot smaller and easier to read than the original Perl.

Some of this might be the Second Version Effect, but I do not think so. Ruby hides some of the things that I have to do explicitly in Perl. I am learning a lot about what I should have done with Business::ISBN, but I think Ruby has some definite advantages over Perl for this task. I am starting to think of it as all the good parts of Perl and Python without the bad parts of Perl or Python.

In the Perl Business::ISBN fix_checksum method, although I do not have to store the object in $self, it is better than working with $_[0]. In Ruby, the variable self is automatically the object which received the message. In Perl, I have to call methods with an explicit object, so even if I did not use $self, I would still have to use $_[0]->_checksum. In Ruby, the method is automatically called on the object, so a lot of typing disappears.

Another great thing about Ruby is that everything is truly an object, so strings have methods. Not only that, strings are sequences which I can treat sort of like Perl arrays---no need for substr()!

Since Ruby eliminates a lot of the typing, I also feel comfortable removing a lot of blank lines. Ruby does away with the curly braces and the semicolons, sort of like Python, without the fascist indention scheme. Ruby encourages lots of small, tight code mostly because its statements are terser.

The Perl fix_checksum

sub fix_checksum
	{
	my $self = shift;

my $last_char = substr($self->{'isbn'}, 9, 1);

substr($self->{'isbn'}, 9, 1) = $self->_checksum;

return $last_char eq $checksum; }


The Ruby fix_checksum

def fix_checksum
	before         = @isbn[9..9]
	@isbn[9..9]    = _checksum
	return before != @isbn[9..9]
end


Loops are really different in Ruby because it has a lot of different sorts of builtin iterators. Perl has some basic iterators, but they are pretty dumb. They do not know about objects, do not pass on the element index, and so on. In the Perl version of the _checksum method, I need a klunky foreach and substr() again. In Ruby, though, I can call an iterator directly on an literal integer. I use the step() method to generate the values from 10 to 2 by steps of -1. In Perl I need the range operator and reverse to get the same list. The Smalltalk style blocks really appeal to me too.

Ruby does not do automatic string to number conversion like Perl does, and as I have been playing with Ruby I have really started to appreciate this. At the end of the Ruby version I have to explicitly turn the checksum variable into a string.

The Perl _checksum

sub _checksum
	{
	my $self = shift;

my $sum = 0;

foreach( reverse 2..10 ) { my $index = 10 - $_; $sum += $_ * substr( $self->{isbn}, $index, 1 ); }

my $checksum = ( 11 - ( $sum % 11 ) ) % 11;

$checksum = 'X' if $checksum == 10;

return $checksum; }


The Ruby _checksum

def _checksum
	sum = 0
	10.step( 2, -1 ) {
		|n|
		m = 10 - n
		sum += n * @isbn[m..m].to_i
		}
	checksum = ( 11 - ( sum % 11 ) ) % 11
	checksum = 'X' if checksum == 10
	return checksum.to_s
end


Is Perl losing me to Ruby? I will wait to answer that---I need to try it for another couple of weeks. I gave up on Python because I could not stomach its whitespace rules and lack of CPANitude. In any case learning other languages teaches me about whatever my favorite is. Even if I do not end up using Ruby more than Perl, I think I will be a better programmer.

If you are interested in checking out Ruby, I recommend Ruby In a Nutshell, which is a decent class reference, and Programming Ruby. I have gone through all of the Ruby books, which I will review along with Mike's article, and these are the ones you want to start with.