Dwemthy’s Array Sidebar 1

Hmm. VisualWorks has namespaces, which Smalltalk-80 does not, and they seem to have changed the way subclasses are defined. According to my copy of the purple book, a subclass is created by sending a class the message ‘subclass:’. It should be possible to implement a version of subclass in the Creature class that adds the extra code.

However, VisualWorks wants me to send ‘defineClass:’ to a namespace. Problem with that is I want to change the way subclasses of a particular class are defined. If I change the code in the NameSpace class that will affect every class that is ever created thereafter.

I hope VisualWorks hasn’t sacrificed this flexibility. I think that the original implementation was the correct one (classes create their children), and that it should have been possible to introduce namespaces without changing it, or at least get the method in NameSpace to delegate to the original code. Currently both mechanisms exist, although the original one appears to have been left there just for backwards compatibility.

Hopefully James or someone who knows more about VisualWorks than I do will read this and set me straight.


Added later: James happily responded. How many other product managers would personally respond to some random blogger’s idle meanderings?

Exploring Dwemthy’s Array (Part 1)

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.