Understanding Smalltalk classes and metaclasses
TL;DR
This tutorial explains the design of the Smalltalk object model, in particular the relationship between objects, classes and metaclasses.
Introduction
This tutorial is loosely based on Part 1 of the Blue Book.
The design of each object-oriented language differs slightly in how objects, classes and metaclasses are related to each other. Smalltalk's design can be explained in terms of the following seven points:
1. Every object is an instance of a class.
2. Every class eventually inherits from Object
.
3. Every class is an instance of a metaclass.
4. The metaclass hierarchy parallels the class hierarchy.
5. Every metaclass inherits from Class
and Behavior
.
6. Every metaclass is an instance of Metaclass
.
7. The metaclass of Metaclass
is an instance of Metaclass
.
1. Every object is an instance of a class
In Smalltalk, everything is an object, and every object is an instance of a class.
true class == True
1 class == SmallInteger
nil class == UndefinedObject
The class of an object serves both as factory for instances and as a repository for common behavior. For instance, the methodDict
of a class is a dictionary mapping messages to compiled methods.
True methodDict
2. Every class eventually inherits from Object
Classes in Smalltalk form a single inheritance hierarchy with Object
at the root.
Every class eventually inherits from Object
.
true class inheritsFrom: Object
1 class inheritsFrom: Object
Aside:
Actually there is another class, ProtoObject
, which is the superclass of Object
. Mostly it can be ignored, but since Object
already provides a large number of pre-defined methods, ProtoObject
is sometimes useful to avoid having all this default behavior.
Object methodDict size
When an object receives a message, the method is looked up in the method dictionary of its class, and, if necessary, its superclasses, all the way up to Object (and PrototoObject
).
The class of true
has a method for the message #not
.
true class selectors includes: #not
Although true
understands #=
, it is defined in Object
, not in True
or Boolean
.
"true" true class canUnderstand: #=
"not true" true class selectors includes: #=
"not true" Boolean selectors includes: #=
"true" Object selectors includes: #=
If no method is found for a message, the default behavior is to send the message #doesNotUnderstand:
with the reified message back to the original receiver.
"Won't be found, so will generate a #doesNotUnderstand: message" true foo
3. Every class is an instance of a metaclass
Since everything is an object, it follows that classes are objects too. Since classes are objects, they too must be instances of classes. Such classes are known as metaclasses . Different object-oriented languages adopt different design decisions concerning whether or not metaclasses are supported, and, if they are, whether metaclasses can be explicitly defined or not.
In Smalltalk, there are no explicit metaclasses. Instead metaclasses are created implicitly when classes are created. The name of a Smalltalk metaclass of a class X
is simply X class
, and can be accessed by sending #class
to X
.
True class name = 'True class'
While a class serves as a repository of common behavior for its instances, a metaclass serves as the repository of behavior for its (unique) instance, namely the class.
Here we see the method dictionary of Boolean
, defining methods understood by instances of Boolean (and its subclasses):
Boolean methodDict
In contrast, here is the method dictionary of Boolean class
, holding methods understood by Boolean
itself:
Boolean class methodDict
The GT Coder shows all class and instance methods together in one browser, however in the bottom right corner we can see whether a method belongs to the class or instance side.
For example, Boolean>>#isLiteral
is an instance method.
We can send #isLiteral
to an instance of Boolean
or its subclasses.
true isLiteral
On the other hand, Boolean>>#new
is a class method.
We can send #new
to Boolean
itself or its subclasses (though this will generate an error, as intended).
True new
4. The metaclass hierarchy parallels the class hierarchy
This is a pragmatic design choice in the Smalltalk-80 system. By making the metaclass hierarchy parallel the class hierarchy, we ensure that every class also inherits its class-side methods from its metaclass.
NB: method lookup works exactly the same way for metaclasses as it does for classes.
5. Every metaclass inherits from Class and Behavior
Since rule 2 tells us that all classes eventually inherit from Object
, we should infer that the same holds for metaclasses, which are also classes. So the metaclass hierarchy does not stop with Object class
but rather with Object
. In between, however, we have the special system classes Class
, ClassDescription
, and Behavior
.
Behavior
provides the minimum state necessary for objects that have instances. It is also the basic interface to the compiler: creating a method dictionary, compiling a method, and accessing and modifying the class hierarchy.
ClassDescription
extends Behavior
with instance variables, method categories, and change sets, amongst others.
Class
provides the common behavior of all (regular) classes.
6. Every metaclass is an instance of Metaclass
Metaclass
serves as the shared repository of behavior for all metaclasses (just as Class
is the shared repository of behavior for all normal classes).
true class class class = Metaclass
1 class class class = Metaclass
Smalltalk class class class = Metaclass
7. The metaclass of Metaclass is an instance of Metaclass
At last we can close the loop. Since everything is an object, Metaclass
is also an object, and is an instance of Metaclass
. This, in turn, is itself a metaclass, and hence is an insatnce of Metaclass
.
Metaclass class class = Metaclass
Now we can see how the parallel hierarchies are actually part of the same single inheritance hierarchy with Object
at the root.