Meta-programmeren met Ruby en Erik
Geplaatst door Danny Lagrouw za, 18 maa 2006 01:24:00 GMT
In de wervelende wereld van het webloggen zou je haast vergeten dat er ook nog dingen bestaan als mailinglists en nieuwsgroepen. En juist op het gebied van Ruby is daar vaak interessante informatie te vinden. Door Erik Veenstra (auteur van o.a. RubyScript2Exe) werd ik gewezen op zijn artikel over meta-meta-programmeren in Ruby, op comp.lang.ruby.
Meta-programmeren zou je kunnen omschrijven als ‘het schrijven van programmatuur die nieuwe programmatuur genereert’. Wie heeft nog nooit een programma geschreven dat een bestand wegschreef dat zelf ook weer programmacode bevat? In Ruby kan het bijvoorbeeld zo:
class Meta def test # laat random_cmd één van de drie expressies bevatten: random_cmd = %w(Time.now `dir` 12345)[rand(3)] # maak een nieuw bestand aan, testmeta2.rb: open('testmeta2.rb', 'w') do |f| # schrijf nieuwe programmacode die de expressie # print, weg in het bestand: f.print "puts #{random_cmd}" end # bonus: voer het nieuw verkregen programma uit: system('ruby testmeta2.rb') end end Meta.new.test
Niet echt elegant, maar het toont het principe, het werkt, en het kan zijn nut hebben. Waarom krijgt dit binnen de Ruby-wereld zoveel aandacht?
Omdat Ruby een dynamische taal is, is het helemaal niet nodig om een tweede bestand te gebruiken. Je kunt de nieuw gegenereerde programmacode live uitvoeren in het huidige programma. Bijvoorbeeld:
class Meta def test # laat random_cmd één van de drie expressies bevatten: random_cmd = %w(Time.now `dir` 12345)[rand(3)] # voer het nieuw verkregen programma uit: eval("puts #{random_cmd}") # er is geen stap 3! end end Meta.new.test
We gaan nog een stap verder. Je kunt blijkbaar binnen de bestaande programmastructuur wat extra code uitvoeren, zoals in het bovenstaande code. Zou je ook de programmastructuur zelf kunnen uitbreiden? Bijvoorbeeld door een extra method toe te voegen? En kun je die nieuwe method dan ook aanroepen, binnen de originele programmatuur? In Ruby wel:
class Meta eval("def extra_test; puts 'dit is de extra test'; end") end Meta.new.extra_test
Tenslotte kun je tijdens het uitvoeren van een class definitie code laten uitvoeren, zodat je de classdefinitie programmatisch kunt uitbreiden of veranderen. Zo zou je bijvoorbeeld in een module een method attr_accessor
kunnen definiëren, zodat je die in je classes kunt gebruiken om getters en setters te genereren:
class Module def attr_accessor(property) module_eval "def #{property}=(p);@#{property}=p;end" module_eval "def #{property};@#{property};end" end end class Klant attr_accessor :naam end k = Klant.new k.naam = 'Jan' puts k.naam
In dit geval ben je niet de eerste die op het idee komt, maar Erik Veenstra gebruikt dit principe voor het maken van ‘wrapper methods’: meta-methods die je automagisch om bestaande methods kun laten uitvoeren. Hij noemt dit meta-meta-programmeren (het programmeren van programmatuur die je kunt gebruiken voor meta-programmeren?). Eigenlijk een soort aspect oriented programming, als je het mij vraagt. Lees vooral zijn artikel—of kom op 18 mei naar RubyEnRails 2006, waar Erik een sessie over meta-programmeren zal verzorgen. (Meer nieuws volgt; nog even geduld a.u.b.)