Dwemthy’s Array is a great example of the metaprogramming capabilities of Ruby.
At the heart of Dwemthy’s Array is the Creature class, and at its heart is the following code:
def Creature.traits( *arr ) return @traits if arr.empty? attr_accessor *arr arr.each do |a| class_eval %{ class << self def #{a}( val ) @traits ||= {} @traits[#{a.inspect}] = val end end } end
This code declares a static (class) method that, when called with a list of symbols, creates a class method for each symbol that, when called, lazily instantiates a class variable, with the symbol’s string as a key and the method’s argument as the value.
The next chunk looks like this:
class_eval %{ def initialize self.class.traits.each do |k,v| instance_variable_set( "@" + k.id2name, v ) end end } end
This section defines an instance method called initialize that will iterate through the list of traits (stored as a class variable) and create instance variables for each entry, named after the key, assigning them to each value. The initialize method is called by default when an object is instantiated.
In a nutshell, each subclass of Creature provides a prototype template that allows multiple instances to be instantiated with copies of the same data. The Creature class also allows for easy addition of new traits. Calling the traits method more than once will append the new values to the collection.
There’s a line in Creature like so:
traits :life, :strength, :charisma, :weapon
Creature and all its subclasses share the above common traits.
One of the subclasses, Rabbit, starts like this:
class Rabbit < Creature traits :bombs
life 10 strength 2 charisma 44 weapon 4 bombs 3
It adds an additional trait called ‘bombs’. and sets the initial trait values for its instances by calling the class methods defined by the meta code in Creature.
One important point to note is that the ‘traits’ class variable is defined per-subclass, so each subclass (subclass, not object instance) gets its own local collection of traits.
Okay. Now I think I understand how it works. So can it be done in Smalltalk? I don’t know yet.
That’s Part 2.