Seven Languages in Seven Weeks – Ruby – Week 1

Monday, January 3, 2011

Star Ruby (The-Delong-Star-Ruby)It is the end of the first “week” with the book. Due to very busy pre holidays period and holidays it actually took me a month to get through it. I hope things will be less hectic in coming weeks so that I can live up to the book’s title.

So week one with Ruby then. Everyone interested in software development has almost certainly come across this language. It has captured the interest of disgruntled masses with very productive Rails framework back in 2005. I remember seeing the infamous blog demo videocast and being blown away by the fact that this guy seems to be able to knock up a blog from scratch in about 15 minutes. What I was even more impressed with was how clean and sensible the code he was writing was. I tinkered around with Ruby for a little bit back then, but I got pulled more deeply into the world of Java at that time, as as this was lingua franca in company where I worked and the idea was that it was more important for me to master that than a side language.

So what did I miss out on by doing that?  For one Ruby is a pure Object Oriented programming language which means that everything including literals is an object. It uses strong and dynamic (duck) typing strategies. This allows for polymorphism by functionality rather then by declared types; which gives you more freedom and less typing at the expense of compile time type checking. All the classes in Ruby are open and can be extended, including classes such as String and NilClass. It supports mixins (think interfaces with implementation) as a workaround for multiple inheritance. Provided APIs are rich and frequently provide same functionality via different methods. Ruby and it’s APIs rely on code blocks (closures, higher order functions – how ever you want to call them) heavily.  It also provides good metaprogramming facilities.  All of this makes it a wonderful and expressive language, but at the same time it gives you all the rope you want to hang yourself.

Bruce Tate (author) organised each of the chapters into 3 “days” per language each followed by self-study a.k.a. “homework”.

On day 1 Bruce introduces Ruby and some of it’s syntax. We learn about the fact that it is purely OO and strongly “duck” typed. Even this little knowledge is enough to have so decent fun with Ruby. For instance one of the homework exercises requires you to print your name 10 times, which can be accomplished very elegantly with a following one liner:

10.times { puts 'Dalibor' }

We can do this as Ruby is purely OO language which means that literal 10 represents an object of Fixnum class. We can lookup API and discover times method that takes a code block; which we use to print the name. This solution is very elegant and also reads very well. With the exception of cryptic puts keyword it could be read by anyone and understood immediately, which is probably not the case with your “good old” for loop.

On day 2 both standard collections get introduced Array and Hash. Ruby rich API strategy gets introduced on the example of Array which can be treated as any of the following: array, stack, queue, set or a linked list. Code blocks get explained (whops! – I already started using them in day one, but on the other hand we are encouraged by author to explore). Function and class syntax gets explained, which is not very surprising past the fact that due to dynamic typing types don’t get declared. What is more interesting is how Ruby tackles the multiple inheritance by providing mixins. Mixins get included in a class and extend it with their own behaviour. As they get effectively included in the host class they can access and depend on their functionality. It is explained how Comparable and Enumerable use this mechanism to provide very rich APIs to classes that include them – all the host classes have to provide are implementation of <=> operator and each method. An example of a task given on day 2 is implementation of a simple grep tool which I solved like this:

#!/usr/bin/env ruby

unless ARGV.size == 2
    puts 'Provide name of the file to grep and what to grep for'
    exit
end

counter = 0
File.foreach(ARGV[0]) do |line|
    counter += 1
    puts "#{counter}: #{line}" if line.include? ARGV[1]
end

In this example I’ve used two mechanisms I find interesting. One is String variable substitution – this is a functionality provided by double quoted strings which allows variables values to be used as part of the string by wrapping them in #{} (see line 11 for example).  Another one is file access using code block. By using foreach method that takes a code block file gets opened and closed for us and executes provided code block for every line in the file. This is quite a lot of functionality from a one method and is also very flexible. I could do something similar in Java, but I would probably have to use an anonymous inner class which would be very verbose.

On day 3 metaprogramming hits the stage. Two mechanisms are explained – metaprogramming via method_missing method. This method gets invoked every time a method that does not exist is invoked on an object. By inspecting the method and arguments passed we can provide implementation for methods that don’t formally exist on the class. Other (arguably cleaner) mechanism is using modules. This mechanism involves mixing a module into class which then modifies the host by adding new capabilities. This is used by Rails framework to provide ActiveRecord functionality.
Metaprogramming can be demonstrated by following homework assignment. RubyCsv class has ActAsCsv module mixed in. When module is included in a class included method gets invoked. This in turn extends base class (RubyCsv) with ClassMethods module that contains acts_as_csv method. When this method gets invoked it includes InstanceMethods module that reads a csv file and initialises instance variables. It also provides each method that returns instances of CsvRow objects, which uses method_missing to provide “getters” for values based on headers.

#!/usr/bin/env ruby

module ActsAsCsv

  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    def acts_as_csv
      include InstanceMethods
    end
  end

  module InstanceMethods

    def read
      @csv_contents = []
      filename = self.class.to_s.downcase + '.txt'
      File.open(filename) do |file|
          @headers = file.gets.chomp.split(', ')

          file.each do |row|
            @csv_contents << row.chomp.split(', ')
          end
      end
    end

    attr_accessor :headers, :csv_contents

    def each &block
        @csv_contents.each do |line|
            block.call CsvRow.new(@headers, line)
        end
    end

    def initialize
      read
    end

  end

end

class CsvRow

    def initialize headers, row
        @data = {}
        headers.each_index do |index|
            @data[headers[index]] = row[index]
        end
    end

    def method_missing name, *args
        if @data.has_key? name.to_s
            @data[name.to_s]
        end
    end

end

class RubyCsv  # no inheritance! You can mix it in
    include ActsAsCsv
    acts_as_csv
end

m = RubyCsv.new
puts m.headers.inspect
puts m.csv_contents.inspect
m.each {|row| puts "#{row.capital} is capital city of #{row.country}"}

country, capital
Slovenia, Ljubljana
United Kingdom, London

So if we use provided sample csv file the result we will get will be:

["country", "capital"]
[["Slovenia", "Ljubljana"], ["United Kingdom", "London"]]
Ljubljana is capital city of Slovenia
London is capital city of United Kingdom

So what are my thoughts on Ruby after my brief encounter with the language. I am impressed with flexibility, expressiveness and productivity of the language. I can see how it can be extended to provide very rich and readable APIs and DSLs. Language feels very malleable due to its metaprogramming support. On the other hand, as much as duck typing helps with keeping the code simple, it does leave me feel a bit exposed to all sorts of type issues, it makes writing IDEs like I’m used to difficult and surely doesn’t help with performance. As language allows the programmer to take many liberties with it, I think it warrants quite a lot of discipline and good understanding of the entire codebase of the project you are working on. This makes me think, that working an a project that uses Ruby would probably be pure delight if the team would be knowledgeable and disciplined. I have a sneaky suspicion that it would be a right nightmare to work on a project involving legacy code and a suboptimal team. Especially if there are members that think they know more than they actually do and get entangled in all the rope Ruby will happily provide.

3 Comments

  1. Miha Plohl says:

    Dalibor, greetings!

    So You are not becoming rubyist, yet?

    :)

  2. Dalibor "Bore" Novak says:

    Ola Miha!

    Not just yet, but I am enjoying my explorations. Have you ever tinkered with Io? So far it seems even more flexible than Ruby to me; and dare I say it – more fun.

    Cheerio.

Leave a Reply to Miha Plohl Cancel reply

Pingbacks & Trackbacks

  1. Dalibor Novak » Seven Languages in Seven Weeks – Io – Day 1 - Pingback on 2011/01/18