Prototype-Based Languages: From A New Taxonomy To Constructive Proposals and Their Validation
Prototype-Based Languages: From A New Taxonomy To Constructive Proposals and Their Validation
Prototype-Based Languages: From A New Taxonomy To Constructive Proposals and Their Validation
Abstract
Prototype-based languages are currently proposed as a substitute to class-based languages for a higher flexibility in manipulating objects. These languages are all based on a similar set of basic principles: object-centered representation, dynamic addition (deletion) of slots, cloning and message delegation. But they all differ in the precise interpretation of these principles and nobody has yet considered the semantic issues raised by their combination. In this paper, we propose a new taxonomy of prototypebased languages, enhancing the Treaty of Orlando by now discussing issues associated with the different semantics of the identified prototype-based languages. From this taxonomy, we extract a constructive proposal for the design of a new prototype-based language. This proposal is the chief result of this paper; it suggests one set of primitives which is regarded as the best to provide a clean, useful and coherent prototype-based computational model. We finally describe an implementation of most interesting language alternatives in the form of a Smalltalk-80 platform. This platform establishes an operational semantics for the basic primitives and more interesting validates our previous taxonomy by implementing it as a class hierarchy. Obviously, this platform has been used to relate in the same formalism the semantics of different languages with each others. For instance, the programming models of existing languages, such as Self, ObjectLisp and Actra's examplars, are faithfully derived as subclasses in this hierarchy.
1. Introduction
In object-oriented programming, a long road lead from class-based to prototype-based languages. Experimental work about knowledge representation in the AI community has shown the usefulness of frames for their capability to represent different types of knowledge (values, procedures, predicates, defaults) as a collection of slots. On the other hand, the quest for new computational models for distributed AI have led to the development of the actor model emphasizing the cloning and delegation mechanisms. In parallel with this evolution, many users of class-based languages developing applications in the fields of user-interfaces [MGDV90] and virtual reality systems [Born81, Smit86], have tried to escape the traditional abstract data type model to move towards a less constrained one. For such kind of applications, classes have also been considered as a source of complexity because they are playing too many roles [Born86]. The alternative solution is often based on the concept of prototypes, more amenable to a form of programming-by-example and providing an alternative to class instantiation and class inheritance [Born86, MACL89, UnSm87, MyGV92]. Prototypebased languages propose a new programming paradigm that is justified in two fundamental ways
The authors present addresses are the following: * LITP, 4 place Jussieu, 75252 Paris Cedex 05. Email: chd@rxf.ibp.fr ** Universit de Montral, Dpartement d'informatique et de recherche oprationnelle, C.P. 6128, Succursale A, Montral, Qubec, Canada, H3C 3J7. Email: malenfan@iro.umontreal.ca *** cole des Mines de Nantes, 3 rue Marcel Sembat, 44049 Nantes Cedex 04, France. Email: pc@rxf.ibp.fr.
compared to class-based languages. First, on the philosophical side, people's natural way to grasp new concepts is generally to begin by creating concrete examples rather than abstract descriptions; classbased languages force people to work in the opposite direction by creating abstractions (classes) prior concrete objects (instances). Second, on the more pragmatic side, class-based languages seem to unnecessarily constrain objects, by disallowing distinctive behavior for individual objects among their instances and by forbidding inheritance between objects to share values of instance variables. Recently, many languages using prototypes have been proposed. Borning derived a small prototypebased language to compare them to classes [Born86]. Lieberman [Lieb86] gave an informal description of the delegation system, from which he argued that prototypes are strictly more powerful than classes. Self [UnSm87] is a pure prototype-based language efficiently implemented [ChUL89, ChUn91]. Systems mixing prototypes and classes have also been proposed [LaTP86, Lalo89]. Nevertheless if we consider this recent evolution of prototypes, current prototype-based languages differ in the semantics of object representation, object creation, object encapsulation, object activation and object inheritance. So understanding the exact merits of each language is not always easy, both in terms of expressive power and applicability to specific kinds of problems. The Treaty of Orlando [StLU88] proposed a first comparison of class-based and prototype-based languages; we go further by addressing more extensively the problem of the alternative semantics associated to pure prototypebased languages. More precisely, this paper has three goals: 1) to build a new taxonomy for prototype-based languages, 2) to use this taxonomy to propose our model of a prototype-based language and 3) to validate this taxonomy by implementing it as a (Smalltalk-80) class hierarchy. To reach these goals, the methodology we used is essentially experimental. We have collected language proposals from several papers, identified
their primitives and classified their alternative semantics. To fully understand the semantics of primitive principles, we have implemented and experimented with them; this lead to the implementation of ProtoTalk. From this experimentation, we collected observations that helped in expanding and building our taxonomy. So the structure of our argumentation is the result of a process of clarification of ideas coming from these observations. We have organized mechanisms in increasing order of complexity; this order is reflected in the construction of our taxonomy. The paper is organized as follows. Section 2 recalls the basic principles of prototype-based languages and defines shortly their terminology. Section 3 studies the different semantics associated to prototype-based languages, introduces our taxonomy and proposes our model. Section 4 presents the platform Prototalk Prototypes in Smalltalk implementing the previous taxonomy as a Smalltalk class hierarchy. Then we conclude on future research work.
2.1 Prototypes
"If Clyde was the elephant most familiar to you, the prototypical elephant might be an image of Clyde himself. If I ask a question as "How many legs does an elephant have?", a way to answer the question is to assume that the answer is the same as how many legs Clyde has, unless there is a good reason to think otherwise." [Lieb86]
The most important idea of the prototype-based approach is that concrete objects are the only mean to model applications. There are no classes, as usual in object-oriented programming. Prototypes are not meant to be abstractions, as classes are, and they are not linked in any way to other objects that would describe them, as in the class-based approach. A prototype is a self-sufficient entity with its own state and behavior, and capable of answering messages. This means that the normal way to speak
about a concept in these languages is to provide a concrete example for this concept. This has tremendous importance as we shall see later; but note here that any reference to a concept must first be rephrased in terms of its concrete examples, as pointed out by Lieberman.
2.2 Delegation
Delegation was introduced [Lieb81] as a message forwarding mechanism. The basic idea of delegation is to forward messages that cannot be handled by an object to another object called its parent in Self or proxy in Act1 (we will use the term "parent" in the rest of the paper). Indeed, the key-point of delegation is that the pseudo-variable "self" still points to the original receiver of the message, even if the method used to answer the message is found in one of its parent [Lieb86]. Delegation is proposed as a mean for an object to retrieve and share knowledge provided by another object. Let's use Clyde and the elephant example again. Assume an elephant Fred; delegation means that you can construct an object representation of Fred that is related to Clyde in such a way that if I ask a question as "How many legs does Fred have?", then the way to answer this question is to assume that the answer is the same as how many legs Clyde has, unless we know Fred to have a different number of legs. Hence, Clyde acts as a prototypical instance, not only to answer questions about the concept of elephant but also to answer questions about specific elephants. This use of delegation is proposed as an alternative to class inheritance.
important point here is that sharing is done at the level of concrete objects and not at the level of concepts as with class inheritance; this means that structures, behavior and values are shared. This fact also has a tremendous importance in prototype-based programming models. With delegation, the notion of "self" or in other words, what means to be inside or outside an object, must be reconsidered in the light of this object extension view. What does it mean to be inside or outside of the extension object B? or to be inside or outside of the extended object A? How well do the different prototype models address these issues? These are crucial questions we will address in the next section.
3.1 Taxonomy
Most prototype-based languages provide a message passing mechanism to activate objects and a cloning primitive. But, they differ in the semantics of other basic mechanisms. We now classify these alternatives to get our taxonomy, which is shown in Figure 1. Each leaf of this taxonomic tree corresponds to one basic family of prototype-based languages built on a self-sufficient set of primitives and mechanisms.
More than message forwarding, delegation can also be interpreted as an extension mechanism. An object B that delegates to another object A, can be viewed as an extension of A. Hence, delegation defines objects having a shared part and a private part. This interpretation of delegation raises the important issue of the meaning of "self". The most
representation
slots
yes
no
yes
no
yes
no L3
yes
no L6
yes
no L9
yes
no L12
creation primitive
newEmpty L1 ...
newInitials L11
delegation
implicit
explicit L15
implicit
explicit L18
no L13
no L16
yes L17
L2 is reductible to L1
Figure 1. Our taxonomy of prototype-based programming languages (to ease the understanding of the figure, we hide part of the lowest levels).
of slots are advocated by Self [UnSm87]. But in the variables&methods approach, encapsulation of variables is enforced by standard (Smalltalk-like) visibility rules, whereas new mechanisms must be provided to restrict the visibility of slots in the other approach.
the third level in the taxonomy. If a primitive to create new objects ex nihilo is provided, two new alternatives show up. We can create empty objects (let us call this primitive newEmpty), or objects with an initial structure (let us call this one newInitials:). These alternatives appear at the fourth level of the taxonomy. But if we restrict ourselves to the creation of new empty objects alone, this raises the question of what to do with these new empty objects? Some means must be provided to modify their structure in order to build the concrete objects in applications. Indeed, this introduce two other design alternatives which are whether or not the structure of objects can be extended and/or shrunk dynamically, by adding or deleting slots (let's call these primitives addSlot: and
deleteSlot:) or respectively variables and methods (let's call them addVar:, deleteVar:, addMethod: and deleteMethod:). For a better classification, we made these alternatives as the second level of our taxonomy. Let's now consider cloning. The simple biological metaphor of cloning makes it very appealing, compared to the traditional way to create objects in class-based models, namely by instantiation. The basic idea is that, given an existing object, it is easy to get a new object similar to the first one by simply copying it. Cloning offers two alternatives: shallow cloning or deep cloning. However, deep cloning is usually ruled out as an uninteresting alternative because it is time consuming and provide little interesting properties on its own. Because most languages provide a primitive for shallow cloning that we call shallowClone, we introduced it in the root of the taxonomy.
split objects composed in this case of both the extended object and the extension object (we will defined this concept more precisely in Section 3.2.4). Therefore, we introduce a distinction between languages that explicitely deal with split objects and languages that don't. These two alternatives make the sixth level in our taxonomy. Handling split objects in a language means that we can create them, refer to them, clone them and otherwise deal with them as with other objects. In other words, handling split objects means to treat them as first-class entities in the language.
p1 x y move 10 20 method
p2 x y move
p1 x y move
15 10 20 method
p2 x y move
p1 x y move
a) cloning
a)
b) #Joe 50 ... an AssociationMember parent association status a Turtle at 10@30 parent y heading
a Pen x y move
10 20 a method
... ...
... ...
...
40 45
able to deal with explicit delegation orders. As far as we could figure out from [MyGV92], Garnet is a language in the family of (L8). However Garnet, like in [Born86], uses constraints propagation as primary computation mechanism rather than message passing and delegation, and this introduces some distortion when we classify it using our taxonomy.
respective slots move, x and y. Notice however that, even if p1 and p2 get the same structure and the same values, when p2 is modified, for example, the two objects become independent (Fig. 2b) and cease sharing updated values. Hence, shallow cloning enforce creation-time sharing, characterized by an independent evolution of the clone and the copied object which prevents objects to be unexpectedly modified by their clone. The independent evolution applies to slot individually; here p1 and p2 continue to share the method move, even after p2's y slot has been modified.
adding the slots salary and company to Joe also leaves the door open for other extensions, e.g. Joe as a member of an association (Fig. 3a). Any modifications to Joe are automatically seen by its extensions. Also, changes to the person Joe can be made through its extension objects. For example, if the employee changes its personal address, the change will be made at the person level and will be effective for all extensions of this person. This example is also a good one of what delegation can do that class inheritance cannot. In class-based systems, it is possible to define the concept of employee by extension of the concept of person, but it is not possible to extend Joe, the instance of person, with an object like Joe_as_employee; if Joe already exists in the system, an instance of employee sharing the information stored in Joe cannot be created. Furthermore, it would be impossible to create an instance anAssociationMember which would share the information about the person. Another interesting use of life-time sharing is advocated in the pen-turtle example (Fig 3b). Sharing of values via delegation allows here a row of turtles to be derived from the same pen object by sharing the same x-coordinate.
but p1 and p1s clone would continue to share the same pointer to the value. However two villains would remain: firstly a new and distinct assignment primitive would be needed to modify the value of y of p1s clone without modifying p1, and secondly, if modification of the structure of objects is allowed, a new method or variable created on p1 would not be known of p1 clone as it would with delegation. This last problem could be denoted as the incremental propagation of newly created slots to clones of an object requiring the memorization of every clone of each objects, as described by Borning [Born86]. Hence, simulating delegation with cloning is impossible without special mechanisms or if we want to keep both the addressing model and the clone operation simple.
#white
Now if Clyde has an accident and looses one leg, Fred also looses it! Fred should then be modified either to add it the slot legs assigned to its original value 4 or to change its parent. But there is no easy way for Clyde to tell Fred, and more generally for an object to tell all the objects that delegates to him, that he has changed, that he has moved from the status of prototypical object to the one of exceptional object. In such a case, cloning is more appropriate. This suggests first that prototypical instances should be immutable, but let us postpone the discussion of this issue to 3.2.5, and second, that in this case cloning
should have been used. Our conclusion is that both delegation and cloning are basic mechanisms, providing different capabilities that sometimes overlap but all of which being necessary.
among these elementary objects applying to the same entity, namely the row of turtles. Hence, we conclude that a useful concept of split object emerges from delegation and parent links, and this concept must be treated as such in the language.
Joe_as_employee and Joe that describes completely the employee Joe. A third constituent is the pair Joe_as_associationMember and J o e that describes the member of an association Joe. The last constituent is the split objet Joe_as_aWhole itself. Each of these constituents is an interesting entity that we would like to be able to refer to in order to manipulate them normally. The whole treatment of split objects as first-class entities in the language resides in this designation problem. For example, should we introduce in our example new inverse links from the elementary object Joe to all its children and use the pointer to Joe to designate the split object? Should we introduce new elementary objects to play the role of the split objects by having pointers to all its elementary objects? or to all its constituents? At present time, we have no satisfactory solution to this problem. It is an open research problem on which we will come back in the conclusion of the paper.
modification; if the prototypical instance looses its status of being prototypical and instead becomes an exceptional instance of the concept, the original concept is lost. In both cases, a solution may be to make prototypical instances immutable, but this solution is contrived in two ways. First, it introduces a distinct kind of objects in the prototype-based model, which then looses part of its elegance and simplicity. Second, by making prototypical instances immutable, we loose the capability of normally speaking about these objects. For example, I can't use Clyde, and say that it has lost a leg, because of its status of prototypical instance. We conclude that prototypical instances are not the right device to implement classlike reuse in prototype-based languages.
Prototypical Instances
Reuse from prototypical instances using both cloning and delegation cannot serve as a mechanism to provide class-like sharing because it causes semantics problems, especially when modifications are made to the prototypical instance. The problem with cloning is that when modifying the prototypical instance, there is no way to insure that the family of objects cloned from this prototypical instance will be homogeneous; objects cloned before the modification will not be affected, thus they will diverge from the new concept implemented by the updated prototypical instance. With delegation, the problem is seen the other way around. When the prototypical instance is modified, all objects delegating to it are affected by the
as S redefines the print method to specify the behavior of its eventual sub-objects, it masks any other print method applying to itself, therefore rending impossible the application of a message print to itself. Although ad hoc solutions to this problem exist, they do not change the fundamental problem. In fact, shared repositories are a form of abstract objects and creating them prior concrete objects seems to go against one of the raison dtre of prototypes. The problem of printing is just a sign that the abstract shared repository in fact declares methods for its sub-objects and that the link between an object and the shared repository is not simply an inheritance one. Hence, shared repositories are not a solution to achieve class-like reuse in prototypebased languages. Now, if such abstract objects must be banished from prototype-based applications (at least at the programming level), the programmer could be prevented from creating them by testing at method creation time that nowhere a method tries to access a variable or another method within the object which is not implemented by the object itself or by one of its parent. Indeed, this does not banish references to self and late binding still useful to create extension objects redefining variables or methods.
positive result is that maybe we do not have to bother about this kind of sharing at the programming model level. In the implementation of Self [ChUL89], maps provide exactly the kind of sharing we need, and maps are generated and managed automatically at the implementation level. Actually, maps are shared repositories but objects are linked to their map through a map-of link. This link is different from the parent link but this does not hurt the prototype-based model at all, simply because this new link is hidden within the implementation. Hence, a proper way to achieve sharing and reuse among a set of similar objects is to do it at the implementation level using devices such as maps.
extended with the different alternatives in cloning and delegation. (L1), (L3), (L5), (L7), (L9) and (L11) are usable and interesting. To our knowledge and except in our platfom (see section 4), these have not been implemented yet. This is certainly due to their impoverished capabilities, but they could be used as assembler languages to implement other more powerful prototype-based languages. To extend these six languages with delegation and eventually with a support for split objects yield a second set of higher-level languages. In this paper we will focus on those ((L13) to (L18)) derived from (L5) and (L11) because, as we will explain in the next section, they are, from our point of view, the most interesting. Languages (L13) to (L18) are workable; instead of assessing them one by one, we prefer to look at the best choice at each level in the taxonomic tree in order to derive the proposals for new prototype-based languages.
modification of structure, we must choose to have primitive to create objects ex nihilo. The other alternative leads to use cloning to create new objects and we have seen above that languages using cloning without dynamic modification of structure are impossible to use. For the very same reason, we choose to create objects ex nihilo using the primitive newInitials: instead of newEmpty because the latter would need dynamic modification of structure in order to create useful objects. Delegation gives us the choice between implicit and explicit delegation. Obviously, explicit delegation is more tedious to use than implicit delegation. But it is more flexible since it gives the possibility to choose the object where to delegate on a message per message basis. In implicit delegation, an object identifies its parents through parent links and messages are delegated to these objects only. The reason why we favour implicit delegation comes from the fact that delegation introduces split objects. The chief difference between implicit and explicit delegation is that when delegation is made implicit in the language, the link between an object and its parent must be explicit but when delegation is explicit, this link is blurred into the code of methods where the explicit delegation messages appear. Because the link between an object and its parent appears to be so important in the structure of objects, we also conclude that implicit delegation is highly preferable since this link is made explicit. Because of our previous discussion on the emergence of split objects, we prefer languages which make them first-class entities. Hence, (L14) and (L17) provide the kind a prototype-based programming models we advocate. To summarize, our proposal is the model characterized by the following statements: - Prototypes should be represented either with variables and methods or with slots, the most important point being to implement an encapsulation mechanism. The structure of prototypes should be immutable, to enforce encapsulation by preventing malicious users to dynamically add public accessors to private information. - The first primitive to create new objects should be
newInitials:, a primitive which create an object with an initial set of slots and slot values. This avoids the need of dynamic modification of the structure of objects. Delegation should be implicit rather than explicit, using a parent link to implement the delegation. Delegation should be used in the language to extend existing objects thus creating split objects. The chief advantage of implicit delegation is to make the relationship between objects explicit to ease the understanding of their behavior. The primitive newInitials: should prevent users from creating abstract objects by forcing each message send to self in all objects to have a binding, either by a slot in the object itself or by a slot in one of its parent; this avoids the semantic problems associated to abstract objects, but still preserves the ability to redefine slots. Split objects should be treated as first class entities and, in particular, it must be possible to create them, refer to them, shallow clone them, and otherwise deal with them as with any object in the language. Neither delegation nor cloning should be used to achieve class-like sharing among a family of similar objects. We have shown that this kind of sharing causes semantic incoherences in the prototype-based model. Since this kind of sharing is mandatory to get space-efficient implementations of prototype-based languages, implementation devices such as maps proposed by Self should be used to achieve it at the implementation level.
4. Prototalk
We now discuss some implementation issues through an overview of the Prototalk platform. 1 The snapshot in Appendix A illustrates the use of this platform to experiment with various prototype-based languages.
1 Note that we do not implement prototype-based languages using Smalltalk-80 constructs. Lieberman [Lieb86] has shown that delegation cannot be implemented in a class-inheritance oriented language using message passing unless the specific properties of Smalltalk class variables are used [Stei87]. We choose Smalltalk as the implementation language mainly for its programming environment and its reflective capabilities.
eval: context defined on the class ImplicitDelegationMessageNode "The argument context is a dictionary containing the current values of self and of the arguments and temporaries of the method in which the message sending is performed. The variables receiver, selector and arguments are instance variables of the receiver of the eval: message." | client server args newContext | 1 client := receiver eval: context. receiver is evaluated 2 server := rec serverFor: selector. I search the method in the receiver 3 if (server equals nil) 4 then the exception doesNotUnderstand is raised 5 else args := arguments evlis: context. the arguments are evaluated 6 newContext := Dictionary new. I create a new context 7 newContext at: #self put: client. In which I bind self to the new receiver 8 newContext at: #super put: (server parent) 9 I return: ((server methodNamed: selector) applyWith: args in: newContext)
Figure 4: Evaluation of a message sending instruction for a language with implicit delegation.
7 newContext at: #self put: (context at: #self)
An interesting property of the platform lies in its use of the class hierarchy to inherit evaluators of the different languages from one another. Each new evaluator inherits its implementation from a previous one, except for those constructions that are given a different semantics in the new language.
its implementation is given in the literature. Delegation requires that when applying a method, the pseudo-variable self be bound to the receiver of the message (the client) rather than to the object in which the method was found (the server).
L7AndImplicitDelegation. This amounts to evaluate the receiver (line 1), to search a server for the selector M in the receiver (line 2), if a server is found (line 5) to evaluate the arguments, to create a new context (line 6), to bind in this context the pseudo-variable self to the receiver (line 7) and to apply the method M to its arguments in the new context (line 9). The method serverFor:, defined on the class L7AndmplicitDelegation finds which object owns the method to be applied, if the method is not owned by the receiver, it performs a lookup through the parent hierarchy. The resend of messages via a pseudo-variable "super" (as in Smalltalk) is a fundamental mechanism in an implicit delegation language whereas it is not needed with explicit delegation. For example, considering the implementation of Lieberman's dashed turtle example [Lieb86] using implicit delegation shows that resend of messages is mandatory. The line 8 in Figure 4 partly implements the resend of messages: if "super" is used in the method to be applied, the search will start at the right place. But this is not enough: indeed, while interpreting super m, the original client will be lost; "super m" does not means send the message m to the object which is the value of the variable super but delegates the message m to the appropriate server while preserving the current client. We thus have modified our parser so that it creates a different king of message node (instance of the class MessageToSuperMessageNode) when encountering a message to "super" in a method. The eval: method defined on that new class is similar to the above one (Fig 4) except for the line 7 (Fig. 5). We will see that the same solution will be used for the implementation of explicit delegation.
redefine, for each message to be delegated, a method on the delegating object, the body of which simply delegating the message. Putting simplicity aside, this solution has several drawbacks [Lieb86]: expressing delegation becomes tedious, grouping delegation orders is impossible, clients have to redefine methods each time new ones are added in their server, etc. More sophisticated solutions to this problem exist (see e.g. the objects scripts of Act1). Currently, only the first solution is implemented: if an object A wants to delegates a message M to B, he has to define a method named M in which it delegates the message using the appropriate construct. (cf. fig 6). Here is an example of code in the language associated to the class L7AndExplicitDelegation (Fig. 6). The expression o1 delegates: #m1 withArgs: nil in Figure 6 stands for delegate the message m1 to o1 and apply the related method in my environment. The question now is: how to interpret the expression o1 delegates: #m1 withArgs: nil located in the method m1 of o2?
o1 := Root newEmpty. o1 addSlot: 'x 10'. o1 addSlot: 'm1 self x + self x'. o2 := Root newEmpty. o2 addSlot: 'x 20'. o2 addSlot: 'm1 o1 delegates: #m1 withArgs: nil'. o2 m1 --> 40.
Should that expression be a standard message sending, self would change [Lieb86] and the reference to the actual client would be lost. A solution could be, for the client, to explicitly pass itself as argument when delegating a message, for example by writing: "server delegates: aMessage withArgs: arguments forClient: self". Unfortunately this would also allow programmers to write things like "server delegates: aMessage withArgs: arguments for: any_object_out_there", leading to interesting semantic questions. In our solution, the expression o1 delegates: #m1 withArgs: nil, although having the syntax of a message sending, will not be interpreted as such by the system. The selector delegates:WithArgs: is tracked at parsing time to generate an instance of the
class : ExplicitDelegationNode. For example, parsing the expression o1 delegates: #m1 withArgs: nil creates an instance of ExplicitDelegationNode with receiver o1 and selector #m1. Then, the new method eval: defined on ExplicitDelegationNode is exactly the same that e v a l : for MessageToSuperMessageNode (Fig. 5).
to implement the delegation. Delegation should be used in the language to extend existing objects thus creating split objects. - The primitive newInitials: must prevent the users from creating abstract objects i.e objects having methods in which are referenced other slots not defined in the object or in one of its parents. - Split objects should be treated as first class entities. - Neither delegation nor cloning should be used to achieve the class-like sharing among a family of similar objects, which should instead be achieved at the implementation level. Many avenues are now awaiting a more complete exploration. First and most important, we have shed the light on the central concept of split objects. A deeper understanding of this concept is mandatory for the correct use of delegation but it raises several crucial issues. How can we treat split objects as full first-class entities in the language, i.e. create them, refer to them, clone them and otherwise deal with them as with any other object? Is the parent link enough to deal with them or will another link be necessary (e.g. the inverse of the parent link)? How can we refer to the constituents of a split object but still enforce the encapsulation of the split object? Can we define more precisely what is a split object? Are there many different kinds of split objects, as the person-employee-associationMember example compared to the pen-turtles example seems to suggest? Is is possible to design a programming methodology for prototypes which would insure that only such legitimate split objects are created? As a second avenue, new programming models are challenging our taxonomy. For example, some prototype-based languages use a constraint propagation computing model which subsumes message passing and achieve similar effects on objects as the sharing provided by delegation do in more traditional languages. The taxonomy should be adapted to take these new trends into account. Finally, as a third avenue, some interesting but less basic issues have still to be addressed: dynamic modification of parents links, multiple delegation (the prototype's interpretation of multiple inheritance), the questions of whether or not there should be an initial
5. Conclusion
Prototypes provide a serious alternative to the class-based approach but current languages have been designed using very different semantics. In this paper, we have proposed a new taxonomy of prototype-based languages validated by its implementation as a Smalltalk-80 class hierarchy. From this taxonomy, we have extracted a set of design choices providing, in our view, the most elegant and the most useful prototype-based programming model. This model is characterized by the following statements: - Prototypes should be represented either with methods and variables or with slots, the most important point being to implement an encapsulation mechanism. - The first primitive to create new objects should be newInitials:, a primitive which creates an object with an initial structure. - Delegation should be implicit, using a parent link
object in the system and what it should look like, and also the questions of how to access primitives in the language and how they could be redefined by prototypes. Our future work is to look more attentively at these issues and to experiment the design of representative applications using our language proposal.
Acknowledgements
The authors wish to thank Jean-Franois Perrot and Jean Vaucher for their useful comments on the preliminary drafts of this paper.
References
[Born81] A.H. Borning. The Programming Language Aspects of ThingLab, A Constraint-Oriented Simulation Laboratory. ACM Transaction on Programming Languages and Systems, 3(4):353-387, October 1981. [Born86] A.H. Borning. Classes versus Prototypes in Object-Oriented Languages. In Proceedings of the IEEE/ACM Fall Joint Conference, pages 36-40, 1986. [ChUL89] Craig Chambers, David Ungar, and Elgin Lee. An Efficient Implementation of Self, a Dynamically-typed Object-Oriented Language Based on Prototypes. Proceedings of OOPSLA'89, ACM Sigplan Notices, 24(10):49-70, October 1989. [CUCH91] C. Chambers, D. Ungar, B.-W. Chang, and U. Hlzle. Parents are Shared Parts of Objects: Inheritance and Encapsulation in Self. Lisp and Symbolic Computation, (4):207-222, 1991. [ChUn91] C. Chambers and D. Ungar. Making Pure Object-Oriented Languages Practical. Proceedings of OOPSLA'91, ACM Sigplan Notices, 26(11):1-15, November 1991. [LaLo89] W.R. LaLonde. Designing Families of Data Types Using Examplars. ACM Trans. on Prog. Languages and Systems, 11(2):212-248, April 1989.
[Lieb81] H.Lieberman. A preview of Act1.AI memo No 625, MIT, June 1981. [Lieb86] H. Lieberman. Using Prototypical Objects to Implement Shared Behavior in Object Oriented Systems. Proceedings of OOPSLA'86, ACM Sigplan Notices, 21(11):214-223, November 1986. [Lieb87] H.Lieberman. Reversible Object-Oriented Interpreters. in Proceedings of European Conference on Object-Oriented Programming (ECOOP'87), special issue if BIGRE No 54, pp 13-22, June 1987, Paris. [LaTP86] W.R. LaLonde, D. Thomas, and J.R. Pugh. An Examplar Based Smalltalk. Proceedings of OOPSLA'86, ACM Sigplan Notices, 21(11):322-330, November 1986. [MACL89] Macintosh Allegro Common-Lisp Reference Manual, version 1.3. [MGDV90] B.A. Myers, et al.. Garnet, Comprehensive Support for Graphical, Highly Interactive User Interfaces. IEEE Computer, 23(11):71-85, November 1990. [MyGV92] B.A.Myers, D.A.Giuse, B.Vander-Zanden. Declarative Programming in a Prototype-Instance System: Object-Oriented Programming Without Writing Methods. To appear in the Proceedings of OOPSLA'92. [Smit86] R.Smith. The Alternate Reality Kit: An Animated Environment for Creating Interactive Simulations. Proc. of the 1986 IEEE Workshop on Visual Languages, Dallas, Texas, pages 99-106, June 1986. [StLU88] L.A. Stein, H. Lieberman, and D. Ungar. A Shared View of Sharing: The Treaty of Orlando. In W. Kim and F. Lochovosky eds., Object-Oriented Concepts, Applications and Databases. AddisonWesley, 1988. [Stei87] L.A. Stein. Delegation is Inheritance. Proceedings of OOPSLA'87, ACM Sigplan Notices, 22(12):138-146, December 1987. [UCCH91] D. Ungar, C. Chambers, B.-W. Chang, and U. Hlzle. Organizing Programs without Classes. Lisp and Symbolic Computation, (4):223-242, 1991. [UnSm87] D. Ungar and R. Smith. Self: The Power of Simplicity. Proc. of OOPSLA'87, ACM Sigplan Notices, 22(12):227-242, December 1987
2 The platform has been programmed with Objectworks\Smalltalk, releases 2.5 and 4.0; Objectworks\Smalltalk is a trademark of ParcPlace System, Inc.