Tuesday, July 27, 2010

Initial Impressions on Ruby

I took occasion the last couple of weeks to introduce myself to Ruby.  I had an exercise as impetus:
  • given 3 .csv data files, each one of which is delimited differently,
  • then produce a report of the data in 3 different sort orders.
The vast landscape of tutorials, books and other literature on Ruby notwithstanding, it was easy to pick The RSpec Book as my basic guide.  It is the only book with an emphasis on both micro- and story-tests, and counts Dan North, David Chelimsky and Dave Astels as authors.  (I am unsure who wouldn't want to follow those guys.)  With that as my guide I would find expert directions on driving solutions from tests.

I also required a language reference that could boot me up as quickly as possible.  This required a little more comparative analysis along the landscape of Ruby resources...along with some fitful starts and detours.  I eventually settled on Design Patterns in Ruby -- because it had a short chapter on Ruby basics in preparation for the discussion of patterns, and because I could use the patterns themselves as examples of good Ruby usage -- and Rails for Java Developers -- because it helped me transfer my Java and C# competency.

What first impressed me was the speed of the interpreter.  It had been many years since I worked with an interpreted language.  Likewise, I remember the vast migration of developers from Microsoft Basic to Borland Delphi Pascal because the compiled code offered execution speeds that were a drastic improvement over interpreted Basic.  I was thus pleasantly surprised to see my Cucumber and RSpec tests execute with speeds comparable to my JUnit tests.  Now, I'm sure the caveat between the speed of compiled and interpreted languages remain, and that the Java compiler will remain appealing to developers for that reason.  However, the speed of the interpreter distinguished Ruby in my mind.

Another impression regards the richness of the Ruby language.  I test-drove a solution to the same problem in Java with 32 tests and 9 domain classes.  My Ruby solution had 15 Cucumber scenarios (story tests) containing 51 steps and 23 RSpec examples (micro tests), so there were a lot of tests, however, I ended up with only 3 domain objects:
  • one to accept the command line input;
  • a report object to print the report; and
  • a parser object to parse the input.
Moreover, the report object only had 3 methods, and the parser object only had 9.  If I knew the language more intimately, then I'm sure the objects would've been even smaller.

What I mean by richness of the language is illustrated by my application's entry point:

def begin command_line
  abort USAGE if command_line.empty?
  abort FILE_NOT_FOUND if not command_line.all? {|file| File.exists?(file)}
  record_sets = command_line.collect {|file| File.open(file).readlines}
  parsers = record_sets.collect {|records| Parser.new(records)}
  Report.new(standard_output, parsers).print
end

Note how expressive are the guard clauses and the novel way in which Ruby allows to use the "if" conditionals after the clause.  Note also the expressive way in which booleans are asked -- with a question mark.

But for me, since my background is in Mathematics, I was really taken by the way in which the syntax adopts parts of naive set representation.  In mathematics, I can specify the set of all Integers between 1 and 10 as follows:
{∀  i ε I  |  1 <  i  < 10 }  
This reads:
for each i an element of the set of all Integers such that 1 <  i  < 10.
I found myself reading enumerators on arrays and hashes in the same way:
 zero_thru_ten = [] # initialize to empty array
 zero_thru_five = [0, 1, 2, 3, 4, 5]
 # for each element, i, append (2 * i) to array
 zero_thru_five.each { | i | zero_thru_10 << i * 2 } 
 zero_thru_ten # => [0, 2, 4, 6, 8, 10]
As it is common in mathematics to refer to elements of sets by x or y, or elements of matrices by i and j, I found that my refactorings often renamed references from those terms.  For example, here's a refactoring that I missed:
    def parse_input
      parsed = elements.collect { |i| columns.collect { |j| i[j] } }
      parsed.collect { |i| date(i); gender(i); i }
    end

                              
This code should be much more legible.  Nevertheless, the facility that Ruby provides for easily operating on arrays and matrices reminds me a great deal of my first programming language -- APL -- which was designed with matrix mathematics in mind.

(More to follow.)