Advantages of prototype-based systems:
Part of the source of complexity in Smalltalk is that classes play several different roles:
Simplest version of a prototype-based system:
Suppose that objects are completely self-contained, so that an object consists of state and behavior. One can send messages to an object asking it for information, asking it to change its state, or asking it to change its behavior. The only way to make a new object is to make a complete copy of an existing object, copying both state and behavior. Once the copy is made, there is no further relation between the original and the copy. (Creating new objects by copying eliminates the need for metaclasses, since creation and modification messages are sent to prototypes or other individuals rather than to classes.)
This is a clean model, and would be easy to teach about. It handles object creation, modification, and representation. What is missing? First, there is no notion of classification of kinds of objects, either by message protocol or by representation. Second, there is no way to update a whole group of objects in a similar manner at one time (the equivalent of such actions as adding new methods to a class in Smalltalk). These are both important, and so the model needs to be augmented to support classification and updating.
Two ways of handling the missing capabilities (classification and updating):
self
in the method
being executed go back to the original object X. (Notice that as a result
this is different from having X just send the same message to Y. In this
latter case, references to self
would refer to Y.)
In Lieberman's original proposal, we might have a prototypical point
object, with x
and y
fields, and also code for
methods such as +
. To make a new point p
, we
make a child of point
. p
would
typically override the slots for x
and y
inherited from point
.
Suppose we send the message +
with the argument q
to p
. p
would look in its method dictionary, and
not find +
. So it would delegate the message to its parent
point
. It would execute this code. When the code sends
self
the message x
, however, lookup starts back
in p
and the correct x
is found.
An odd consequence of Lieberman's model is that one can, for example,
override the x
field from the prototype point
and
continue to inherit the y
field. If the x
and
y
fields of point
are changed, the child will see
a change in its y
but not in its x
.
However, a benefit of this model is that any object can have its own
special behavior (for example, p
could have its own version of
some method).
SELF also adopts the delegation model. As an organizing principle, though,
an object such as point
is divided into two objects: a
prototype point
with x
and y
slots,
with a parent point traits
. To make a new point, we
clone the point
object (i.e. make a copy of it - not
a child). point traits
serves a class-like role.
SELF tries to blur the distinction between instance variables and methods.
Both state (such as x
) and code (such as the method for
+
) are stored in slots. All access is via
message sends. When an object receives a message, it looks for a slot of
that name. If it finds one, it gets the contents of the slot, evaluates
it, and returns the result. Otherwise it delegates the message to its
parent(s). Most objects evaluate to themselves; however, code evalutes by
running. Note that this makes it tricky to manipulate methods -- this is
done in SELF using mirrors. Given a slot named foo
,
SELF automatically defines access and setting methods foo
and
foo:
.
Cecil is another prototype-based system. To avoid the curiousity regarding
overriding the x
field of point
but not
y
, slots in Cecil can be annoted either as shared or
copydown.
Other recent prototype-based languages include NewtonScript, Glyphic Script, and Kevo. (See the panel statements in the OOPSLA 94 panel on prototype-based languages.)