Mary, Meta
Today Mary Ruby goes Meta. We explored some techniques for using Ruby to create, er, more Ruby.
My previous posts on the 7 Languages in 7 Weeks have been of an unsustainable length so I am going to aim for more succinct offerings for the rest of the exercise.
Metaprogramming
I confess I’m still waiting for the penny to drop with metaprogramming. To me it still just looks like any other language feature. And yet there are great claims made about it’s utility in re-use and constructing DSLs. The former is clear but the path to the latter is still occluded to me. More examples and applications of it in the wild will hopefully bring me enlightenment.
Metaprogramming is presented as a way of using Ruby to write Ruby. After a mere three days learning the language I find this description unhelpful. Surely we don’t want to get into eval’ing strings of Ruby? And, indeed, none of the samples and exercises we explored went down this road. So what’s up with this description?
What we can do with metaprogramming and Ruby’s open classes is package up code wholly independent of it’s eventual “host object” allowing us to focus completely on the module we are implementing.
The Exercises
We are provided with a simple CSV parsing application and our task is to modify it such that each row is now encapsulated in a new CsvRow object that allows access to each field via method_missing. This will allow us to write the following:
csvParser = RubyCsv.New csvParser.each do |row| puts "Field1 value: #{row.Field1}" end
This is my solution to this problem. The latest version can be found on github. The file it uses as input is also available on github.
#!/usr/bin/ruby module ActsAsCsv def self.included(base) base.extend ClassMethods end module ClassMethods def acts_as_csv include InstanceMethods end end module InstanceMethods def prepare @filename = self.class.to_s.downcase + '.txt' File.open(@filename) do |csv_file| @headers = csv_file.gets.chomp.split(', ') end end def _read_headers end def each File.open(@filename) do |csv_file| csv_file.gets #skip headers csv_file.each do |row| yield CsvRow.new(@headers, row.chomp.split(', ')) end end end attr_accessor :headers attr :filename def initialize prepare end end end class CsvRow attr :row_data def initialize(headers, row) @row_data = {} i = 0 headers.each do |header| @row_data[header] = row[i] i += 1 end end def method_missing name, *args @row_data[name.to_s] end end class RubyCsv # no inheritance! You can mix it in include ActsAsCsv acts_as_csv end m = RubyCsv.new puts m.headers.inspect m.each do |csvrow| puts "one: #{csvrow.one}" puts "two: #{csvrow.two}" puts "three: #{csvrow.three}" end
Teh Codez
As ever the github area for day 3.
I struggle with that definition of “metaprogramming” too. What would you say to the following alternative definition?
Normal programming is using code to manipulate data.
Metaprogramming is using code to manipulate code.
That avoids defining metaprogramming in terms of “writing” code which, as you point out, has connotations of string evaluation.
Yeah, that’s definitely better.
My main issue, that which is frustrating me most, is that I still can’t see what the fuss is about with metaprogramming.