November 93 - Prototype-based OOLs
Prototype-based OOLs
Mikel Evins
When we think of object-oriented languages we usually think of languages that
support data abstraction by providing data templates called classes. A class acts as a
description of a type of program data that we use and reuse by creating instances of it.
When we want to define a new type of data we can reuse previous design work by
creating subclasses, defining new representations in terms of what they add to old
ones.
Classes also help organize the dynamic behavior of our programs, as most or all of the
routines we write are associated with classes. Whether we use the Smalltalk model of
objects that respond to messages or the Common Lisp Object System (CLOS) model of
functions that specialize on classes and instances, the behavior of the program is
determined by the interaction of a routine called in the context of a class (or perhaps a
group of classes) that defines the behavior of the routine.
When we are accustomed to thinking of object-oriented languages in such terms it may
seem peculiar to talk about objects without classes, but that's the subject of this
article. It turns out that not all object-oriented programming languages use classes as
a primary organizing principle; indeed, several object-oriented languages support no
notion of classes at all. Specifically, we will discuss object-oriented programming
languages that use concepts called prototypes and delegation to organize data and
dynamic behavior; we will call such languages prototype-based languages. (There is at
least one other way to organize object-oriented programs that involves neither classes
nor prototypes; the programming language BETA uses a concept called a pattern).
Class-based languages, that is, object-oriented programming languages whose type
systems are based on classes, are much more common and more familiar to most
programmers than prototype-based languages. Nevertheless, there are several
interesting languages based on prototypes rather than on classes, and at least one,
NewtonScript, is important to working programmers interested in shipping
commercial applications. Others, such as Self, are exerting influence on the language
design community through their embodiment of innovative compilation techniques and
novel ways of organizing data and behavior. At least one recently-designed class-based
language, Dylan, shows the influence of prototype-based languages.
For the purposes of this article we will define a class-based language as one in which
data types are defined by classes , which are special constructs distinguished from
ordinary data objects by their role as abstract type descriptions, and in which the
behavior of a function or method is determined by the class with which it is associated.
Examples of class-based languages include C++, Smalltalk, CLOS, Objective-C, and
Dylan.
A prototype-based language is a language which lacks distinguished classes, in which
any runtime object can serve as a template for the creation of another object, and in
which the behavior of a function or method is determined by the search path among
objects used to find it. Examples of prototype-based languages include Self,
ObjectLisp, NewtonScript, T, and Cecil.
Advantages of prototypes
Classes are abstract descriptions of data types. Class-based languages create new
program objects by instantiation; that is, they create new memory objects whose
structure and behavior is determined by the description embodied in the class. Classes
may be objects themselves, that is they may exist at runtime and occupy memory, or
they may simply be compile-time abstractions that describe for the compiler how to
lay out memory when creating an object.
In a prototype-based language the role of classes is occupied by objects called
prototypes. A prototype is an object that is used as a model for the creation of another
object. The behavior of a function or method is determined by looking up the named
routine in a specified object or in other objects called its delegates. Although this
description may sound very much like the description of classes and inheritance there
are important differences.
First, as already mentioned, a class may or may not exist at runtime. Classes are
descriptions of abstract data types, and the creation of a new object of that type does
not require that the class actually exist as a data object itself. A prototype, on the
other hand, is definitely a data object that exists in the program's address space. A new
object is created by copying the prototype in whole or in part.
Second, classes, when they are objects at all, are a special kind of object whose
primary purpose is to serve as a type description. A prototype, on the other hand, is
just an object like any other object. A prototype of a window object is itself a window
object and it has all the data and behavioral features of any of the objects that it is used
to create.
Third, unlike methods in class-based languages, those in prototype-based languages
need not be associated with any particular set of objects. In a class-based language a
method in some sense belongs to the set of objects that are defined by the class. In a
prototype-based language a method simply resides in a context, usually in a slot or
field of an object. The particular behavior that results from a message-send or a
function-call depends upon the context in which the function or method is looked up.
Advocates of prototype-based languages claim several advantages for using prototypes
instead of classes. For one thing the use of prototypes eliminates the potentially
complicated meta-object problem of defining the class of a class. If a class is an object,
then of what class is it an instance?
Class-based languages use several different approaches to solve this problem. In C++
classes are simply compile-time conventions, and so classes have no semantic function
at all in a running program. The meta-object problem is solved by omitting it. In
Smalltalk it is solved by providing a set of meta-classes: each object is the instance of
a class and each class is an instance of a meta-class. The meta-classes are subclasses
of the class Class. Each meta-class defines the behavior of its instances, which are
classes, and the classes in turn define the behavior of their instances. You might be
wondering what class is used to instantiate the class Class; Class is usually defined
circularly, being (either literally or in effect) an instance of itself. The object model
of CLOS is somewhat more complicated still, involving a suite of class-oriented
functions and types called a Meta-object protocol. A reasonable description of the
CLOS meta-object protocol is beyond the scope of this article (but if you are interested
in learning more about it see The Art of the Metaobject Protocol by Gregor Kiczales,
Jim des Rivieres, and Daniel G. Bobrow, MIT Press, 1991).
In a prototype-based language the meta-object problem does not exist because classes
do not exist. Objects are created by copying other objects (this is not technically true
in all prototype-based languages, but the principles are essentially the same even
when the technical details are not). There is no need to concern yourself about the
semantics of classes because there aren't any.
Another advantage claimed by advocates of prototype-based languages is superior
flexibility. One might say that among object-oriented dynamic languages
prototype-based languages are the most dynamic. In a program written in a
class-based language an object is a member of a class; either it was created according
to a compile-time template or by executing code associated with a runtime class
object, but either way it is assumed to belong to a set of objects whose characteristics
are defined by a class. Prototype-based programs instead consist of objects whose
relationships are all defined by runtime pointers, and which may or may not be
organized into sets comparable to those defined by classes. An object's prototype can be
changed at runtime, as can its memory layout.
In all fairness, these differences are not so hard and fast as they may seem. For one
thing, most prototype-based programs are, in practice, organized around sets of
objects that might as well be classes. For another, some class-based languages, notably
CLOS, provide facilities with which instances of classes can be modified much in the
same way as objects in a prototype-based system. Nevertheless, there are differences
of degree between class-based and prototype-based languages, enough that we can think
of a continuum of flexibility: C++, which provides essentially no runtime support for
modifying an object's layout or class relationships might form the static end of the
spectrum, and a language like Self, in which each object's layout and prototype
relationships can be modified at any time, might form the dynamic end.
Prototypes and delegation
The basic organizing principles of a prototype-based language are prototypes and
delegation.. Prototypes form the basis of the data-description model in a
prototype-based language and delegation is the mechanism by which code is
dynamically selected for execution.
A prototype is any object that is used as the model for another, newly-created, object.
A window system implemented in a prototype-based language, for example, might
provide a prototypical window object for use as the model of all windows. New window
objects could be created by cloning the prototype. The new windows would be created
with copies of the prototype's instance variables, except for those variables that the
creating function specifically overrides. A new window would most likely store a
pointer to its prototype, enabling runtime code to dynamically determine a window's
prototype and enabling the system to use optimizations such as copy-on-write
discipline for the slot or field values of the new window.
Delegation is the process by which a function or method, referred to in the context of
one object, is found in another object. When a method is referred to by code defined for
an object but is not itself found to be defined in the object the language runtime