ICE09 . playing with java, scala, groovy and spring .

Playing with Spring

Groovy vs. Ruby – the CSV shootout (7L7W day 3)

Posted by ice09 on October 18, 2011

Nope, there is no rant here. Even more, there is no CSV shootout. However, a little CSV is here, it’s even Meta-CSV somehow.

Ok, let’s clarify this: I just started reading the book “Seven Languages in Seven Weeks” and I already like it. It’s exactly written for someone on my language-knowledge level, ie. someone who tried and used several languages and is pretty good at getting the differences between OO and functional languages, but has difficulties to explain what differentiates Clojure from Scala from Haskell from Io besides the different kinds of typing (static vs. dynamic). Someone who has heard of but never knew what prototypes are about and what specialities each language defines (the movie comparison also helps a lot. But be aware that Prolog developers have to relax a little and accept to be compared to Rain Man).

So, the first day is about Ruby. I know Groovy pretty well, so I thought that would be easy. It was. But, I realize that the subtile differences matter a lot. In short, I feel that Ruby is much more consistent (you will see by the sample). However, I like Groovy more – after all, for a Java developer it’s the much better fit. But also, I like the even more magic – which I’d hate in big production systems but really love for scripting.

So, the sample is a CSV reader – the Ruby part is much longer than it has to be, but it should show the Metaprogramming mechanisms. And I like them a lot, even more than the Groovy ones (though Categories are really cool for scoping the Metaprogramming).

class CsvRow
  attr_accessor :values, :keys
  
  def initialize( keys, values )
    @keys = keys
    @values = values 
  end
  
  def method_missing(id, *args)
    if (id==:to_ary) 
      then return @values 
      else return @values[@keys.index(id.to_s)]
    end
  end
end

module ActsAsCsv
  def self.included(base)
    base.extend ClassMethods
  end
  
  module ClassMethods
    def acts_as_csv
      include InstanceMethods
      include Enumerable
    end
  end
  
  module InstanceMethods   
    attr_accessor :headers, :csv_contents
    
    def each &block
       @csv_contents.each{|csvRow| block.call(csvRow)}
    end
    
    def read
      @csv_contents = []
      filename = self.class.to_s.downcase + '.txt'
      file = File.new(filename)
      @headers = file.gets.chomp.split(';').collect{|s| s.delete("\"")}
      
      file.each do |row|
        values = row.chomp.split(';').collect{|s| s.delete("\"")}
        @csv_contents << CsvRow.new(@headers, values)
      end
    end
    
    def initialize
      read 
    end
  end
end

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

m = RubyCsv.new
m.each { |it| puts it.Kontonummer }

I will not explain how this works here, there are bazillions of resources of people really knowing Ruby. The most important fact is how the Metaprogramming works – and it does it exactly as expected. Even more, it does it right. The best fact to me is that upon including the module (which unfolds itself into the base class very nicely), the include method is called automatically. There is no dependency of the RubyCsv on it’s Mixin (and there shouldn’t be!).

So, this is pretty cool. How do I achieve this in Groovy? This is diffficult, there are no modules in Groovy. Of course, Metaprogramming is easy in Groovy, but I want it to mimick the Ruby script.

The most I can get is by using the Groovy 1.6 @Mixin annotation like this:

class CsvRow {
    def keys = []
    def values = []
    
    def propertyMissing(String name) { 
        values[keys.indexOf(name)]
    }
}

class ActAsCsv {
    public headers = []
    public csvRows = []
    
    def read(def instance) {
        new File("rubycsv.txt").eachLine {
            if (instance.headers.isEmpty()) {
                instance.headers = it.trim().split(';').collect{it.replaceAll("\"", "")}
            } else {
                def values = it.trim().split(';').collect{it.replaceAll("\"", "")}
                instance.csvRows << new CsvRow(keys:instance.headers, values:values)
            }
        }
    }

    def iterator() {
        csvRows.iterator()
    }
}

@Mixin(ActAsCsv)
class GroovyCsv {
    GroovyCsv() {
        read(this)
    }
    //@Delegate ActAsCsv acter = new ActAsCsv()
}


def act = new GroovyCsv()
println act.collect { it.Buchungstext.reverse() }
act.each { println it.Kontonummer }

So, this is nice as well, but compared to Ruby it’s more clumsy. The worst thing is the trick with calling the Mixin in the constructor with the this reference. It does not work otherwise, since the fields cannot be set on the Mixin-GroovyCsv-instance itself. This is weird and costs time to debug. It is not a real dependency upon the Mixin itself, but it’s just superfluous.
Also, the nice template method style of including modules in Ruby is not the same. It fells better in Ruby (look up the Enumerable inclusion, it’s really nice). On the other hand, just having to declare iterator() the right way to get rid of all Metaprogamming to implement the Collection methods correctly is cool as well. Also, I found it cleaner to use the @Delegate possibility, which works exactly as expected (no this weirdness here).

It’s as always, there is no best way, I like both versions a lot. I still feel that Ruby feels cleaner, but I can achieve everything in Groovy with the same “expressiveness”. But I can use all Java and all it’s environment and tools as well – so jep, to me it’s the better choice. But I see where a lot of the good stuff in Groovy comes from.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: