Tuesday, November 20, 2007

My Experiments with Ruby

Old ways will always remain unless some one invents a new way and then lives and dies for it -Elbert Hubbard

Somedays ago, in my college days, I involved into a discussion with my pals about the level of heights reached by programming languages, frameworks and design tools these days. That was the time when we were introduced to Rational Rose and were fascinated the way it creates the skeleton code of our design. One of my friends, out of his fantasy, said, "You see Prem, some or other day, there will arrive a tool out of the cloud nine, which can simply translate the requirement specifications into chunks of codes. The Engineers' job would be just to wrap it and ship it out to the customer." We laughed with the typical dreams of full-time graduate student-engineers. Although that was a fantasy, I fear that Geekory is in His way to deliver such a tool to his fellows. Going through the language Ruby, without any surprise, reminded me of this discussion!!

As I was already saying, I started to look into Ruby. I was not used to any languages like SmallTalk, Scala, Perl, Python or PHP more than an extent, just had stints. The documentations and tutorials claim that Ruby is, atleast at its granular level, similar to the languages mentioned above. I don't know about it. But involving into thousands of lines of coding in .Net, when I suddenly looked into Ruby, it seemed to be a simple yet powerful language. The words mean it!

Better than speaking lots of words, I would explain you with an example. I used a typical example used in the tutorial for Ruby, from the book Programming Ruby.

To simply put, we will be simulating a song collection, with provisions to append, and delete, in both Ruby and C#. We shall then compare the lines of codes for both and the time spent on them. The result is the words of the authors of the book in its preface:
Our job is to solve problems, not spoonfeed compilers, so we like dynamic languages that adapt to us, without arbitrary, rigid rules. We need clarity so we can communicate using our code. We value conciseness and the ability to express a requirement in code accurately and efficiently. The less code we write, the less that can go wrong. (And our wrists and fingers are thankful, too.)

It happens in Ruby really, and here they in turn say:
These are bold claims, but we think that after reading this book you'll agree with them. And we have the experience to back up this belief.


Coming back to our song collection example, here is the small list of what we are gonna do:
1. A primitive object for the need, Song, with Track-name, artist, and duration as its members
2. A collection class which contains a list of songs, SongList, with methods to add a song, delete a song, and list a subset of the songs.

Simple!! But, what it takes to implement in a language like C#, which is backed by a "Spoon-feed compiler", is not that simple.

Here is the implementation of that in C#:


class Song
{
private string _name;
private string _artist;
private int _duration;

public Song(string name, string artist, int duration)
{
_name = name;
_artist = artist;
_duration = duration;
}

public string Name
{
get{ return (_name == null) ? string.Empty : _name; }
}

public string Artist
{
get{ return (_artist == null) ? string.Empty : _artist; }
}

public int Duration
{
get{ return _duration; }
}

public override string ToString()
{
return _name + " " +
_artist + " "+
_duration.ToString();
}
}


class SongList
{
private ArrayList _songs = new ArrayList();
public ArrayList Songs
{
get{ return _songs; }
}

public Song this[int index]
{
get{ return _songs[index] as Song; }
}

public SongList Append(Song aSong)
{
_songs.Add(aSong);
return this;
}

public void deleteFirst()
{
if(_songs.Count != 0)
_songs.RemoveAt(0);
}

public void deleteLast()
{
if(_songs.Count != 0)
_songs.RemoveAt(_songs.Count - 1);
}
}


class Test
{
[STAThread]
static void Main(string[] args)
{
SongList testSongs = new SongList();
testSongs.Append(new Song("Song1", "Artist1", 234))
.Append(new Song("Song2", "Artist2", 123))
.Append(new Song("Song3", "Artist3", 456))
.Append(new Song("Song4", "Artist4", 908));
for(int i = 0; i < testSongs.Songs.Count; i++)
Console.WriteLine(testSongs[i]);
}



Here are the facts of implementing this:

  1. It took me nearly 30 minutes

  2. Two trivial compiler hurdles

  3. One trivial run-time hurdle

  4. 80+ lines of code

  5. Most of the lines getting out of the developer's window

  6. The most important, am experienced for 2 years in C# and for nearly 7 years in C++



And I felt shy for these figures, because when I tried the same in Ruby, the following code resulted:


class Song

attr_reader :name, :artist, :duration

def initialize(name, artist, duration)
@name = name
@artist = artist
@duration = duration
end

def to_s
"Song: #{@name}--#{@artist} (#{@duration})"
end

end

class SongList

attr_reader :songs

def initialize
@songs = Array.new
end

def append(aSong)
@songs.push(aSong)
self
end

def deleteFirst
@songs.shift
end

def deleteLast
@songs.pop
end

end

list = SongList.new
list.
append(Song.new('title1', 'artist1', 1)).
append(Song.new('title2', 'artist2', 2)).
append(Song.new('title3', 'artist3', 3)).
append(Song.new('title4', 'artist4', 4))

puts list.songs[0...3]



See how readable Ruby is.
Here are the figures for this implementation:


  1. Took just 10 minutes

  2. No compiler hurdles

  3. No run-time hurdles

  4. Just 40+ lines of code

  5. Each line having not more than a few words typically 2 or 3

  6. The most important than any other, I just started to practise Ruby yesterday !!



Stunning!! Frankly and truly, these figures are real. And most of the Ruby programmers could agree with it easily.

Now you can see that the implementation time has decreased one-third (C#:Ruby) and LOC decreased by half, and my experience is negligibly small with Ruby. Smile out :)

With increasing complexity, the implementation in C# turns to be a bottleneck, but hopefully not in Ruby, as the authors of the book claim. Here are the words for you again:
Our job is to solve problems .... The less code we write, the less that can go wrong ...


Heartfelt thanks to Thoughtworks for throwing lights on Ruby!!

There maybe pitfalls in Ruby too, but they are probably shadowed. If you come across any pitfalls of Ruby, I welcome you to post it here.

4 comments:

Piyush said...

The most important than any other, I just started to practise Ruby yesterday !!
Thats because u copied the code from the book...word by word!

premblogger said...

ha ha ha.. good one.

Your point is valid but it still takes just the same time and effort, if done now w/o copying the code, if am right.

Anonymous said...

Hi Prem,

To prove that i am dare to do this! :-)

Do you say that Ruby is much better than C#? if not then why you have compared the ruby code with C#. you should have done that with python/perl codes.

Kr,
Muthu

premblogger said...

This is a million dollar question.. and not just about comparing Ruby with C-family (or perhaps, C++ family as you would stick to), but a question which I could rephrase as "Why are you comparing Programming Languages?" You are confused with the good old differences between Interpreted languages and Compiled languages.. Such a classification not at all exists in the programming language's world today.. follow my next post buddy.. i have justified my point with appropriate proofs.