Ruby's Eigenclasses Demystified
In Ruby, classes and objects are intricately connected in ways that might not be immediately obvious. Owing to their special use-case, it is easy to lose sight of the fact that classes are themselves objects in Ruby. What makes them different, vis-à-vis regular objects, is that they serve as a blueprint for object creation and also that they are part of a hierarchy of classes. That is, classes can have instances (objects), superclasses (parents) and subclasses (children).
If classes are also objects it follows that they must have a class of their own. In Ruby the class of all class objects is the class Class:
# one way to instantiate a new class Dog = Class.new # a more conventional way of class construction class Dog # some doggie behavior here end Dog.class => Class
The Method Lookup Path
The relationship between classes and objects becomes important in the context of the method lookup path. When you call a method on an object, Ruby first looks for that method in the object's class. Failing to find it there, it next checks the superclass of the object's class and keeps on going up through the class hierarchy until it hits the topmost class which as of Ruby 1.9 is BasicObject. The diagram below illustrates the way Ruby traverses the class hierarchy in search of a given method:
The method lookup path for classes works in a similar fashion. When you call a method on a class object, it will first look in its class, which as we have established is Class, and then continue on to its superclass, Module, all the way up to BasicObject:
Dog.class => Class Class.superclass => Module Module.superclass => Object Object.superclass => BasicObject
And again visually:
As we can see
Object lie at the root of the class hierarchy, so this means that all objects, whether they be regular objects or class objects, will eventually inherit the instance methods defined in those classes.
The primary function of classes is to define the behavior of their instances. For example, the behavior of dog instances in general is placed in the Dog class. However, in Ruby we can also confer unique behavior to individual objects. Meaning that we can create methods for a specific dog object that are not shared by other instances of the Dog class. These methods are often referred to as singleton methods as they belong to a single object only.
To re-iterate, singleton methods are defined on the object itself instead of in its class:
snoopy = Dog.new def snoopy.alter_ego "Red Baron" end snoopy.alter_ego # => "Red Baron" fido.alter_ego # => NoMethodError: undefined method `alter_ego' for #<Dog:0x0000000190cee0>
As noted above, when a method gets called on an object, Ruby first looks in the object's class for the method definition. In the case of singleton methods, the method is obviously not added to the object's class, since that method is not available to other instances of that class. So where the heck is it? And, how does Ruby manage to find both the object's singleton methods and also the general instance methods without disrupting the normal method lookup path?
As it turns out, Ruby accomplishes all this in typical neat fashion. First a new anonymous class is created to hold the object's singleton methods. Then this anonymous class assumes the role of the object's class and the original class is re-designated as the superclass of that anonymous class. As such, the normal method lookup pattern is unaltered - both the singleton methods (in the anonymous class) and the instance methods (in the superclass) will be found along the method lookup path when the object receives a method call (see diagram below).
There are many alternate names for this dynamically created anonymous class that Ruby inserts into the method lookup path: metaclasses, ghost classes, singleton classes and eigenclasses. The word "eigen" comes from the German, roughly connoting something like "one's very own". Eigenclass seems to have found favor in the Ruby community, so I'll stick to that.
Note: In addition to defining singleton methods using the object name (e.g.
def snoopy.alter_ego), you can also add methods to eigenclasses using this special syntax:
class << snoopy def alter_ego "Red Baron" end end
class << syntax opens the eigenclass of whatever object you pass to it and lets you define a method straight in there.
Being objects, classes too can have singleton methods. In fact, what we commonly think of as class methods are nothing more than singleton methods for classes - methods owned by a singular object that incidentally happens to be a class. As with all singleton methods, class methods are also housed in an eigenclass.
Class singleton methods can be created in a variety of ways:
class Dog def self.closest_relative "wolf" end end
class Dog class << self def closest_relative "wolf" end end end
def Dog.closest_relative "wolf" end
class << Dog def closest_relative "wolf" end end
All the examples above are identical in that they all open the eigenclass of the object Dog and define a (class) method directly in there.
Eigenclasses are not only anonymous; under normal circumstances they are hidden from view. This, despite the fact that they have been designated as the first stop in the method lookup path:
class << Dog def closest_relative "wolf" end end Dog.class # => Class
In the code above, the Dog class is still reporting its class as the class Class even though we have added a class method, which should have caused the class to be changed to be the eigenclass. Nonetheless, there is a way we can get objects to reveal their eigenclass:
class Object def eigenclass class << self self end end end
This code might appear a bit puzzling at first glance, so let's see if we can figure out what's going on here.
First off, we know that the Object class is one of the ultimate ancestors of all classes in Ruby, which is why it is a good place to define methods that you want to be universally available.
To understand this code we have to keep track of where and how the value of
self changes. Immediately inside the
eigenclass() instance method,
self is the object that the method was originally called on. We then open the eigenclass of this receiver object with the
class << self syntax. Now that we are in the scope of the eigenclass, the value of
self changes and now refers to the eigenclass object itself. By returning the value of
self from within this eigenclass, we trigger Ruby's built-in
to_s object identifier - allowing us get a glimpse of the elusive eigenclass.
Here's how we can make actual use of this
eigenclass() instance method to bring eigenclasses out into the open.
class Dog end snoopy = Dog.new => #<Dog:0x00000001c4a170> snoopy.eigenclass => #<Class:#<Dog:0x00000001c4a170>> snoopy.eigenclass.superclass => Dog
Ruby's to_s Method
to_s method is defined in the Object class and returns a string representing the object it was called on. This string is a composite of the object's class name followed by a unique object ID. For example,
Class objects (instances of the class Class) such as Class, Object or String override the
to_s method and simply return their name instead.
Dog.to_s => "Dog"
In actuality, the overriding of the
to_s method for classes is done in Module which is the superclass of Class. Here is how the Ruby documentation describes the Module
Returns a string representing the module or class. For basic classes and modules this is the name. For singletons, we show information on the thing we are attached to as well.
Note the bit about singletons including information about the attached object as well. This explains why when we call
to_s on the eigenclass of snoopy we get back the anonymously named "Class" followed by the identifier for its attached object:
to_s on the superclass of the eigenclass, however, references the original (non-singleton) class Dog and so simply returns the name "Dog".
Eigenclasses and Class Inheritance
Just as an object has access to instance methods defined in the superclasses of its class, so do classes have access to the class methods defined in their ancestor classes. Take a look at the code below:
class Mammal def self.warm_blooded? true end end class Dog < Mammal def self.closest_relative "wolf" end end Dog.closest_relative # => "wolf" Dog.warm_blooded? # => true
This is another puzzler. How is it that classes manage to inherit the class methods of their superclasses? After all, the superclass of a class object is in the method lookup path of its instances but not in its own method lookup path:
Method lookup path of a Dog instance: fido --> Dog --> Mammal --> Object ...
Method lookup path of a Dog Object: Dog --> Class --> Module --> Object...
Here again some fancy footwork is done to ensure that the method lookup path pattern remains unaltered and yet allow access to the class methods of the superclass.
First off, when a class (aka singleton) method is defined on Dog, an eigenclass is created. For all intents and purposes, this eigenclass becomes the class of the Dog object in lieu of Class. Class gets "pushed up" the lookup chain and becomes a superclass. Now, when a class method is defined on the superclass of Dog (e.g, Mammal), the resulting eigenclass is designated as the immediate superclass of Dog's own eigenclass and Class gets pushed up even higher. As this description is probably as clear as mud, hopefully the following diagram will shed some light on the matter:
So there you have it. The Ruby object method lookup path in all its glory. Well almost. We haven't looked at where module mixins fit into the picture. Hopefully I'll have the time to cover that in a later blogpost.