Chapter 13
Classes and metaclasses

As we saw in Chapter 5, in Smalltalk, everything is an object, and every object is an instance of a class. Classes are no exception: classes are objects, and class objects are instances of other classes. This object model captures the essence of object-oriented programming: it is lean, simple, elegant and uniform. However, the implications of this uniformity may confuse newcomers. The goal of this chapter is to show that there is nothing complex, “magic” or special here: just simple rules applied uniformly. By following these rules you can always understand why the situation is the way that it is.

13.1 Rules for classes and metaclasses

The Smalltalk object model is based on a limited number of concepts applied uniformly. Smalltalk’s designers applied Occam’s razor: any consideration leading to a model more complex than necessary was discarded.

To refresh your memory, here are the rules of the object model that we explored in Chapter 5.

Rule 1.
Everything is an object.
Rule 2.
Every object is an instance of a class.
Rule 3.
Every class has a superclass.
Rule 4.
Everything happens by sending messages.
Rule 5.
Method lookup follows the inheritance chain.

As we mentioned in the introduction to this chapter, a consequence of Rule 1 is that classes are objects too, so Rule 2 tells us that classes must also be instances of classes. The class of a class is called a metaclass. A metaclass is created automatically for you whenever you create a class. Most of the time you do not need to care or think about metaclasses. However, every time that you use the browser to browse the “class side” of a class, it is helpful to recall that you are actually browsing a different class. A class and its metaclass are two separate classes, even though the former is an instance of the latter.

To properly explain classes and metaclasses, we need to extend the rules from Chapter 5 with the following additional rules.

Rule 6.
Every class is an instance of a metaclass.
Rule 7.
The metaclass hierarchy parallels the class hierarchy.
Rule 8.
Every metaclass inherits from Class and Behavior.
Rule 9.
Every metaclass is an instance of Metaclass.
Rule 10.
The metaclass of Metaclass is an instance of Metaclass.

Together, these 10 rules complete Smalltalk’s object model.

We will first briefly revisit the 5 rules from Chapter 5 with a small example. Then we will take a closer look at the new rules, using the same example.

13.2 Revisiting the Smalltalk object model

Since everything is an object, the color blue in Smalltalk is also an object.

 
Color blue -→ Color blue  

Every object is an instance of a class. The class of the color blue is the class Color:

 
Color blue class -→ Color  

Interestingly, if we set the alpha value of a color, we get an instance of a different class, namely TranslucentColor:

 
(Color blue alpha: 0.4) class -→ TranslucentColor  

We can create a morph and set its color to this translucent color:

 
EllipseMorph new color: (Color blue alpha: 0.4); openInWorld  

You can see the effect in Figure 13.1.


PIC

Figure 13.1: A translucent ellipse


By Rule 3, every class has a superclass. The superclass of TranslucentColor is Color, and the superclass of Color is Object:

 
TranslucentColor superclass -→ Color 
Color superclass                   -→ Object  

Everything happens by sending messagess (Rule 4), so we can deduce that blue is a message to Color, class and alpha: are messages to the color blue, openInWorld is a message to an ellipse morph, and superclass is a message to TranslucentColor and Color. The receiver in each case is an object, since everything is an object, but some of these objects are also classes.

Method lookup follows the inheritance chain (Rule 5), so when we send the message class to the result of Color blue alpha: 0.4, the message is handled when the corresponding method is found in the class Object, as shown in Figure 13.2.


PIC

Figure 13.2: Sending a message to a translucent color


The figure captures the essence of the is-a relationship. Our translucent blue object is a TranslucentColor instance, but we can also say that it is a Color and that it is an Object, since it responds to the messages defined in all of these classes. In fact, there is a message, isKindOf:, that you can send to any object to find out if it is in an is a relationship with a given class:

 
translucentBlue := Color blue alpha: 0.4. 
translucentBlue isKindOf: TranslucentColor -→ true 
translucentBlue isKindOf: Color                    -→ true 
translucentBlue isKindOf: Object                  -→ true  

13.3 Every class is an instance of a metaclass

As we mentioned in Section 13.1, classes whose instances are themselves classes are called metaclasses. Metaclasses are implicit. Metaclasses are automatically created when you define a class. We say that they are implicit since as a programmer you never have to worry about them. An implicit metaclass is created for each class you create, so each metaclass has only a single instance. Whereas ordinary classes are named by global variables, metaclasses are anonymous. However, we can always refer to them through the class that is their instance. The class of Color, for instance, is Color class, and the class of Object is Object class:

 
Color class   -→ Color class 
Object class -→ Object class  
Figure 13.3 shows how each class is an instance of its (anonymous) metaclass.


PIC

Figure 13.3: The metaclasses of Translucent and its superclasses


The fact that classes are also objects makes it easy for us to query them by sending messages. Let’s have a look:

 
Color subclasses                           -→ {TranslucentColor} 
TranslucentColor subclasses         -→ #() 
TranslucentColor allSuperclasses  -→ an OrderedCollection(Color Object ProtoObject) 
TranslucentColor instVarNames     -→ #(alpha) 
TranslucentColor allInstVarNames -→ #(rgb cachedDepth cachedBitPattern alpha) 
TranslucentColor selectors             -→  an IdentitySet(#pixelWord32 #asNontranslucentColor #privateAlpha #pixelValueForDepth: #isOpaque #isTranslucentColor #storeOn: #pixelWordForDepth: #scaledPixelValue32 #alpha #bitPatternForDepth: #hash #isTransparent #isTranslucent #balancedPatternForDepth: #setRgb:alpha: #alpha: #storeArrayValuesOn:)  

13.4 The metaclass hierarchy parallels the class hierarchy

Rule 7 says that the superclass of a metaclass cannot be an arbitrary class: it is constrained to be the metaclass of the superclass of the metaclass’s unique instance.

 
TranslucentColor class superclass -→ Color class 
TranslucentColor superclass class -→ Color class  

This is what we mean by the metaclass hierarchy being parallel to the class hierarchy; Figure 13.4 shows how this works in the TranslucentColor hierarchy.


PIC

Figure 13.4: The metaclass hierarchy parallels the class hierarchy.


 
TranslucentColor class                                     -→ TranslucentColor class 
TranslucentColor class superclass                   -→ Color class 
TranslucentColor class superclass superclass -→ Object class  
Uniformity between Classes and Objects. It is interesting to step back a moment and realize that there is no difference between sending a message to an object and to a class. In both cases the search for the corresponding method starts in the class of the receiver, and proceeds up the inheritance chain. Thus, messages sent to classes must follow the metaclass inheritance chain. Consider, for example, the method blue, which is implemented on the class side of Color. If we send the message blue to TranslucentColor, then it will be looked-up the same way as any other message. The lookup starts in TranslucentColor class, and proceeds up the metaclass hierarchy until it is found in Color class (see Figure 13.5).
 
TranslucentColor blue -→ Color blue  
Note that we get as a result an ordinary Color blue, and not a translucent one — there is no magic!


PIC

Figure 13.5: Message lookup for classes is the same as for ordinary objects.


Thus we see that there is one uniform kind of method lookup in Smalltalk. Classes are just objects, and behave like any other objects. Classes have the power to create new instances only because classes happen to respond to the message new, and because the method for new knows how to create new instances. Normally, non-class objects do not understand this message, but if you have a good reason to do so, there is nothing stopping you from adding a new method to a non-metaclass.

Since classes are objects, we can also inspect them.

PIC Inspect Color blue and Color.

Notice that in one case you are inspecting an instance of Color and in the other case the Color class itself. This can be a bit confusing, because the title bar of the inspector names the class of the object being inspected.

The inspector on Color allows you to see the superclass, instance variables, method dictionary, and so on, of the Color class, as shown in Figure 13.6.


PIC

Figure 13.6: Classes are objects too.


13.5 Every metaclass Inherits from Class and Behavior

Every metaclass is-a class, hence inherits from Class. Class in turn inherits from its superclasses, ClassDescription and Behavior. Since everything in Smalltalk is-an object, these classes all inherit eventually from Object. We can see the complete picture in Figure 13.7.


PIC

Figure 13.7: Metaclasses inherit from Class and Behavior


Where is new defined? To understand the importance of the fact that metaclasses inherit from Class and Behavior, it helps to ask where new is defined and how it is found. When the message new is sent to a class it is looked up in its metaclass chain and ultimately in its superclasses Class, ClassDescription and Behavior as shown in Figure 13.8. The question “Where is new defined?” is crucial. new is first defined in the class Behavior, and it can be redefined in its subclasses, including any of the metaclass of the classes we define, when this is necessary. Now when a message new is sent to a class it is looked up, as usual, in the metaclass of this class, continuing up the superclass chain right up to the class Behavior, if it has not been redefined along the way. Note that the result of sending TranslucentColor new is an instance of TranslucentColor and not of Behavior, even though the method is looked-up in the class Behavior! new always returns an instance of self, the class that receives the message, even if it is implemented in another class.
 
TranslucentColor new class -→ TranslucentColor    "not Behavior
"
 


PIC

Figure 13.8: new is an ordinary message looked up in the metaclass chain.


A common mistake is to look for new in the superclass of the receiving class. The same holds for new:, the standard message to create an object of a given size. For example, Array new: 4 creates an array of 4 elements. You will not find this method defined in Array or any of its superclasses. Instead you should look in Array class and its superclasses, since that is where the lookup will start. Responsibilities of Behavior, ClassDescription and Class. Behavior provides the minimum state necessary for objects that have instances: this includes a superclass link, a method dictionary, and a description of the instances (i.e., representation and number). Behavior inherits from Object, so it, and all of its subclasses, can behave like objects. Behavior is also the basic interface to the compiler. It provides methods for creating a method dictionary, compiling methods, creating instances (i.e., new, basicNew, new:, and basicNew:), manipulating the class hierarchy (i.e., superclass:, addSubclass:), accessing methods (i.e., selectors, allSelectors, compiledMethodAt:), accessing instances and variables (i.e., allInstances, instVarNames …), accessing the class hierarchy (i.e., superclass, subclasses) and querying (i.e., hasMethods, includesSelector, canUnderstand:, inheritsFrom:, isVariable). ClassDescription is an abstract class that provides facilities needed by its two direct subclasses, Class and Metaclass. ClassDescription adds a number of facilities to the basis provided by Behavior: named instance variables, the categorization of methods into protocols, the notion of a name (abstract), the maintenance of change sets and the logging of changes, and most of the mechanisms needed for filing-out changes. Class represents the common behaviour of all classes. It provides a class name, compilation methods, method storage, and instance variables. It provides a concrete representation for class variable names and shared pool variables (addClassVarName:, addSharedPool:, initialize). Class knows how to create instances, so all metaclasses should inherit ultimately from Class.

13.6 Every metaclass is an instance of Metaclass

Metaclasses are objects too; they are instances of the class Metaclass as shown in Figure 13.9. The instances of class Metaclass are the anonymous metaclasses, each of which has exactly one instance, which is a class.


PIC

Figure 13.9: Every metaclass is a Metaclass.


Metaclass represents common metaclass behaviour. It provides methods for instance creation (subclassOf:) creating initialized instances of the metaclass’s sole instance, initialization of class variables, metaclass instance, method compilation, and class information (inheritance links, instance variables, etc.).

13.7 The metaclass of Metaclass is an Instance of Metaclass

The final question to be answered is: what is the class of Metaclass class?

The answer is simple: it is a metaclass, so it must be an instance of Metaclass, just like all the other metaclasses in the system (see Figure 13.10).


PIC

Figure 13.10: All metaclasses are instances of the class Metaclass, even the metaclass of Metaclass.


The figure shows how all metaclasses are instances of Metaclass, including the metaclass of Metaclass itself. If you compare Figures 13.9 and 13.10 you will see how the metaclass hierarchy perfectly mirrors the class hierarchy, all the way up to Object class.

The following examples show us how we can query the class hierarchy to demonstrate that Figure 13.10 is correct. (Actually, you will see that we told a white lie — Object class superclass -→ProtoObject class, not Class. In Pharo, we must go one superclass higher to reach Class.)

Example 13.1: The class hierarchy
 
TranslucentColor superclass -→ Color 
Color superclass                   -→ Object  

Example 13.2: The parallel metaclass hierarchy
 
TranslucentColor class superclass   -→ Color class 
Color class superclass                     -→ Object class 
Object class superclass superclass -→ Class    "NB: skip ProtoObject class" 
Class superclass                              -→ ClassDescription 
ClassDescription superclass            -→ Behavior 
Behavior superclass                         -→ Object  

Example 13.3: Instances of Metaclass
 
TranslucentColor class class -→ Metaclass 
Color class class                   -→ Metaclass 
Object class class                 -→ Metaclass 
Behavior class class              -→ Metaclass  

Example 13.4: Metaclass class is a Metaclass
 
Metaclass class class -→ Metaclass 
Metaclass superclass -→ ClassDescription  

13.8 Chapter summary

Now you should understand better how classes are organized and the impact of a uniform object model. If you get lost or confused, you should always remember that message passing is the key: you look for the method in the class of the receiver. This works on any receiver. If the method is not found in the class of the receiver, it is looked up in its superclasses.