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.