CSE 341 -- Metaclasses

All objects in Smalltalk are instances of some class. For example, we might have an instance p of the class Point. Or 3.14 is an instance of the class Float.

Classes in Smalltalk are objects themselves, and understand certain messages, just like any other object. One often-used message is new, to make a new instance. A variant is new: (used for e.g. the class Array). Another common kind of message is one that gets a constant, for example ColorValue blue or Float pi.

In the Smalltalk browser we can see methods that define class messages by clicking the "class" button instead of the "instance" button.

Many classes classes contain a category "examples" or "testing".

Suppose we want to redefine the new message to Stacks so that it automatically initializes the stack. We can do this as follows:

new
   | s |
   s := super new.  "note we have to use super rather than self!"
   s setsize: 10.
   ^s
or more concisely:
new
   ^super new setsize: 10
What happens if we say:
new
   ^self new setsize: 10
We could also define:
new: n
   ^super new setsize: n
allowing us to redefine new as:
new
   ^self new: 10

How It Works

What sort of an object is a class? In Simula, and in C++ (more or less), classes are not runtime objects at all. For example, you can't pass a class as a parameter to a procedure, or assign it to a variable. Java has limited facilities of this sort, which we'll talk about later.

In Smalltalk-72, classes were runtime objects, but they were rather special. They weren't instances of some class themselves. If you just mentioned the class name, it would create an instance:

  p := Point.
In Smalltalk-76, classes are again runtime objects, but they are made part of the normal class-instance scheme. To make an uninitialized instance of the class Point:
  p := Point new.
p now understands messages like "+" and "x" and "printOn:" The definitions of these are in the class Point, which is itself an object (or in a superclass of points).

Rule: to look up a message name, look in your class. If it's there, use it; otherwise search up the superclass chain. In other words, go up exactly one instance link, and then up 0 or more superclass links.

What is the class Point? In Smalltalk-76 it is an instance of the class Class. Class is a subclass of Object, and an instance of itself. Class understands messages like "new", "instvars", and "compile:" Consequence: all classes understand the same messages!

Thus in Smalltalk-76 initialization must be done like this:

  p := Point new.
  p x: 10 y: 20.
or
  p := Point new x: 10 y: 20.

Metaclasses

In Smalltalk-80, the designers wanted to allow class-specific initialization messages, e.g.

  p := Point x: 10 y: 20.
To accomplish this, every class is an instance of a unique metaclass. "Point" is an instance of "Point class" (the metaclass for point). Point class defines messages such as x:y:. Another benefit of this is that it gives the programmer a place to hang constants and such, e.g. Float pi.

In the Smalltalk environment, the class/instance switch in the browser lets one switch between methods the instances understand and methods the class understand (i.e. methods defined in the class and methods defined in the metaclass).

The cost in confusion is huge, though. A particular problem is that new programmers are hit over the head with this right away (unless the teacher glosses over it, which is the usual response).

Alternatives to Smalltalk-Style Metaclasses

Java

In Java, every object is an instance of one class (as in Smalltalk). Let's start with the ArrayList class. It is a subclass of AbstractList, which is a subclass of AbstractCollection, which is a subclass of Object. Object is a subclass of itself. Any class can be interrogated as to its class, via the getClass() message, for instance:
ArrayList list = new ArrayList();
Class c = list.getClass();
This returns the runtime class (ArrayList), which can then be interrogated as to its properties, methods, constructors, etc. This provides some degree of run-time manipulation of classes. (I could for instance, take an arbitrary object, ask its class, and then construct a new object of that class.)

What about class specific initialization? Java, like C++, handles that with the concept of a constructor, which can be viewed as a message that's sent to a class and asks it to initialize a new instance of that class. What about a place to hang constants? Java (like C++) uses the notion of static methods or variables, which can be though of as belonging to the class (rather than instances of that class)

The Java solution is not as general as the Smalltalk-80 solution, but manages to provide many of the essential properties without as much confusion (although it's still confusing to beginning programmers!). In Java, classes are almost full-fledged objects, but not quite. In Smalltalk, classes are full-fledged objects, but the meta-mumble confusion is huge.

Prototype-based system -- no classes

Prototype-based languages do away with the notion of classes entirely. Everything is an object again. Programmers design prototypical objects, and then reproduce them via cloning and mutation. Imagine making a point object, with an X and Y field, and methods for printing and adding. Other point objects can be created by copying the original, and mutating the x and y fields to new values.

So far so good, but what about inheritance? Inheritance can be achieved by delegation. A 3-D point can be created by making a new object that is like the prototypical point object, but has an additional field, Z. The 3-D point might override the add method, but keep other methods of the 2D point. When those methods are invoked, they are still the original method, but they are run on behalf of the 3D point in question.

Comparing the strategies

Smalltalk-style problem solving: the programmer sits down, contemplates the world and its concepts, and expresses these concepts in terms of classes. Classes are essentially like sets in mathematics: we use them to describe groups of objects which all share certain characteristics. Smalltalk (and C++, CLOS, and any class based OO language) problem solving proceeds from the general/abstract to the specific/concrete. We can think of Smalltalk as a knowledge representation tool that favors/supports an analysis-driven (come up with the categories/classes/hierarchy first) approach.

Observation: Smalltalk's mode of problem solving is not necessarily bad, but it is difficult for beginning and even seasoned programmers. Many programmers spend too little time coming up with the correct generalizations and abstractions and launch too quickly into implementing and testing their simulation. When they do this, they often need to backtrack and make changes to their classes. Furthermore, and more importantly, in some situations, we don't actually know enough about how to describe the necessary concepts a priori in order to adequately implement them as classes.

In other words, we may be the most diligent programmers in the world, but it just may be the case that we don't have what it takes to build the abstractions correctly the first time. In this case, the usual solution is to go forward, assuming that we'll learn more about the "right way" as our implementation becomes more concrete. The point is, that class-based object oriented programming languages arguably are flawed as design tools, because they encourage a style of design that is not necessarily natural for most humans. Dynamic languages such as Smalltalk address it by making it pretty easy to make changes to the class hierarchy (and provide tools to aid the programmer in doing so).

Prototype-based languages address this apparent mismatch. They argue that the natural order of knowledge representation is not from the general to the specific, but the other way around. Their view of problem solving begins with prototypical objects. These objects are concrete in every way. As above, prototype-based object-oriented systems gain all of the advantages of inheritance without the need to create a substantial layer of abstraction first.

The "debate" between class-based and prototype-based approaches dates back at least to the time of Plato and Aristotle.