mobcode

Exploring ruby class and metaclass relationships

22 December, 2006

nested dolls

The Ruby class structure is difficult to understand without drawing pictures. Three class relationships are important:

  1. class
  2. superclass
  3. metaclass

In order to sort it out I consider three categories of objects:

  1. plain objects
  2. classes
  3. metaclasses

Plain objects are just plain old instances of objects. So if I have a Dog class and this code:

d = Dog.new

Then d is a plain object.

In this example Dog is a class. In Ruby, classes can usually be considered as just special types of objects. In the following code, Dog is a class and Animal is a class.

class Animal
end

class Dog < Animal
end

Other relevant classes include Class itself and Object.

That leaves metaclasses. Here is a good explanation of metaclasses (after reading his stuff I feel like I should write in the wonderful gibberish that he uses… alas, I don’t have that talent so I have to settle for plain old boring English). The basic story of metaclasses goes something like this: Plain objects hold state, not behavior. An object’s methods are defined in its class. The behavior of all Dog instances is defined in the Dog class. A metaclass is a special class that allows behavior to be defined at the instance level. Adding a method to the metaclass of object d, creates a method that will only apply to object d.

Similarly, a class can have a metaclass. This is analogous to the situation I just described. Methods on the metaclass only apply to a single instance of an object, in this case the object happens to be a class. Methods on a class metaclass end up looking a lot like class methods from Java.

The bottom line is that every object in Ruby can have a metaclass. This includes plain old objects, classes, and metaclasses. They can all have metaclasses that define methods just for them.

I keep saying they can have a metaclass. The first time a metaclass is referenced it will be created. Until then it does not exist.

I use this bit of code (also from why) to access an object’s metaclass:

class Object
  def metaclass
    class << self
      self
    end
  end
end

The syntax, class <<, is used to access an object’s metaclass. This small bit of code adds a method called metaclass to the base class, Object. This new method uses the class << construct to get access to the current object’s metaclass and then returns that metaclass. Within the context of the class << construct, self refers to the metaclass.

Finally, as I said, a metaclass itself can also have a metaclass. So you can keep adding metaclasses to your heart’s content.

Those are the three types of objects; now the three types of relationships.

The class of an object tells you what type of object it is. The class of plain objects is simple, it is their class. The class of a class is easy, it is always Class. This includes special classes such as Object. The class of Object is also Class. Similarly the class of all metaclasses is also Class.

class diagram

The superclass relationship is mostly straightforward too. Plain objects don’t have a superclass. The superclass of classes is what you would expect. It follows the normal object-oriented inheritance structure. In Ruby, Class has a superclass of Module which has a superclass of Object. All classes descend from Object.

superclass diagram

The superclass of metaclasses is a bit complicated. Based on experiments that I conducted with Ruby 1.8.5 I found that the metaclass inheritance structure changes in surprising ways. In the simple case, a plain old object’s metaclass starts out with a superclass of the object’s class’ metaclass.

This also applies to a class metaclass which means by default a class metaclass has a superclass which is the Class metaclass.

metaclass superclass diagram

However, when a metaclass is added to the object’s metaclass, the superclass of the object’s metaclass changes to point to the next metaclass out. With the outermost metaclass pointing to itself as superclass.

metaclass superclass diagram

The final wierdity is that the metaclasses of Object and Module have a superclass of Class. This explains how metaclasses end up in the Class hierarchy. And this too changes once a metaclass is added to the metaclass of Object or Module.

Charting out the inheritance structure of metaclasses is complicated by the fact that metaclasses are created on demand and when they are created they change the metaclass inheritance structure. So simply retrieving a metaclass to see what its superclass is causes the inheritance structure to change. Fun.

NOTE: These results are different than what the docs say.

If superclass is complicated, the metaclass relationship is quite simple. Every object’s metaclass is its own unique metaclass. This applies to plain objects, classes, and metaclasses.

metaclass diagram

Now all of these diagrams can be combined to get the whole picture of how plain objects, classes, and metaclasses relate to each other.

final diagram

These are the conventions in the diagrams:

  1. plain objects are ovals
  2. classes are white squares
  3. metaclasses are blue squares
  4. class relationships are shown as lines pointing to the north-west
  5. superclass relationships are lines pointing up
  6. metaclass relationships are lines pointing to the left

More complete code to explore these relationships:

class Animal
end

class Dog < Animal
end

class Object
  def metaclass
    class << self
      self
    end
  end
end

d = Dog.new

def dump (d, code) 
  puts "#{code} = #{eval code}"
end

dump d, "d.class"
dump d, "d.metaclass"

puts
dump d, "d.metaclass.class"
dump d, "d.metaclass.superclass"
dump d, "d.metaclass.metaclass"

puts
dump d, "d.class.class"
dump d, "d.class.superclass"
dump d, "d.class.metaclass"

puts
dump nil, "Class.class"
dump nil, "Class.superclass"
dump nil, "Class.metaclass"
Comments (3)

This is the battle to be fought

15 December, 2006

braveheart

As a developer it is easy to look around the web and get distracted by all the cool stuff going on. Maybe you read one of the many stories about the guys starting web sites in their bedroom who are bought out for millions. Or maybe you read about the success of the guys of whom it appears that everything they touch turns to gold. You get distracted and think that if only you were over there then you could do great work.

You have to realize that the place where you are, right here, in the midst of whatever mundane tasks and bizarre organizational issues you face. This is the battle to be fought.

So stop dreaming and fight today’s battles.

If you are caught in an organization that “meets” to death, then your battle is to make the meetings more productive, or make them stop happening, or at least make them short.

If you are on a team where nobody can get organized, not even you, then that is the battle. Fight to get yourself organized and help the team do the same.

If you are wading through piles of legacy code, then that is the battle. Learn ways of getting legacy code under control.

If you develop in a code-and-fix manner, then the battle is to get the discipline to follow good practices.

So don’t get distracted and dream about being in a great job. Don’t deceive yourself into thinking that if it weren’t for these pesky issues you could solve some great problem. And don’t deceive yourself into thinking others have it easy and only you have to deal with a messy reality. Realize that fighting the battle in front of you gives you experience and skills to go on to the next battle. Realize that behind the glossy success story you read, someone has fought the same mundane battles you are facing today.

Comments (1)