Principles of Object-Oriented Software Development
[] readme course preface 1 2 3 4 5 6 7 8 9 10 11 12 appendix lectures resources

Object-oriented programming languages

When developing an object-oriented system, at some time a choice has to be made for an actual programming language or environment. It goes without saying that the optimal environment will be one that is in accord with the method chosen for design. Naturally, other desiderata (involving efficiency, portability or client-imposed constraints) may play an equally significant role.

Object-oriented programming languages

5

• language design dimensions
• classless prototypes
• meta-level architectures

Additional keywords and phrases: programming languages, orthogonality, reliability, complexity, types, delegation, multiple paradigms, prototypes, reflection

slide: Object-oriented programming languages

This chapter will present an overview of the numerous languages that exist for object-oriented development. A comparison of Smalltalk, Eiffel, C++ and Java will be given, and we will look at the considerations underlying the design of the various object-oriented (extensions of) programming languages. Also, some possible modifications and alternatives to the traditional class-based object model will be discussed, including active objects, prototypes and meta-level architectures.

subsections:

The object paradigm is embodied in numerous programming languages.  [Sau89] presents a survey of 88 object-oriented languages, of which 69 are standalone and 19 incorporated into either multi-paradigm or database systems. (A multi-paradigm system, in this context, means a system embedding an environment for window programming or knowledge-based reasoning.) In this section, we will first look at the classification of object-oriented languages (as given in Saunders, 1989). Most of the languages mentioned are based on a distinction between classes and objects. However, alternative object models (without classes) are also being employed. Finally, we will review a number of object extensions of the languages Lisp, C and Prolog.

On the notion of object

Before our comparative study of object-oriented programming languages, we may well reflect on some issues of language design (specifically the motivations underlying the development of a programming language) and in particular on the notion of object underlying our conception of object oriented programming languages. Language design is an intricate issue. The motivation to develop a programming language may come from the desire for experimentation (as it has been for the author,  [Eliens92]), from governmental policy (in the case of Ada), corporate policy (as for PL-1), the wish to improve programming habits (which lies at the basis of Pascal and Modula,  [Wirth83]), the wish to provide more adequate programming constructs (as, for instance, C++ was originally meant to be a better C), the efficient implementation of a theoretically interesting model of computing (as has been the case for Prolog), or circumstantial forces (of which Java may be considered an example).

Whatever motivation lies behind the development of a programming language, every language is meant to serve some purpose. Whether implicitly or explicitly stated, a programming language is characterized by its design goals and the applications it is intended to support. A fortiori this holds for object-oriented programming languages. The impetus to research in object-oriented programming may be traced back to the development of Simula, which was originally intended for discrete event simulation. As observed in  [Tai], Simula has since served as a valuable source of ideas in several research areas in computer science. See slide 5-simula.

The notion of object -- Simula

• abstract data types -- software engineering
• frames -- artificial intelligence
• semantic data models -- database system development
• capability-based computing -- distributed systems

slide: The heritage of Simula

These areas include abstract data types (that play a prominent role in software engineering,  [Parnas72b];  [Liskov74]), frames in artificial intelligence (which have become an invaluable mechanism for knowledge representation,  [Fikes] and  [Minsky]), semantic data models (which are widely used to develop information systems,  [Hammer78]), and capability-based computing (that plays a prominent role in distributed computer systems,  [Levy]). The research efforts in these areas in their turn have had a strong impact on our conception of object-oriented computing. With regard to object-oriented programming we may differentiate between three (partially distinct) viewpoints from which to characterize the notion of an object.

object-oriented

structurally

• - capability of representing arbitrarily structured complex objects

operationally

• - the ability to operate on complex objects through generic operators

behaviorally

• - the specification of types and operations (data abstraction)

slide: Perspectives of object orientation

From a structural viewpoint, object-oriented means the capability of representing arbitrarily complex objects. This viewpoint is of importance for implementing object-oriented languages and the development of adequate runtime models of object-oriented computing. From this perspective, an object is (in the end) a structure in memory. From an operational viewpoint, object-oriented means the ability to operate on complex objects through generic operators. This viewpoint is closely related to the notion of semantic data models, and is of particular importance for conceptual modeling. From this perspective, an object represents (an element of) a conceptual model. From a behavioral viewpoint, object-oriented means the support to specify abstract polymorphic types with associated operations. This viewpoint is primarily of importance for software engineering and the development of formal methods of specification and verification. From this perspective an object is like a module, to be used for data abstraction. From the inception of Simula, there has been a close relation between object-orientation and modeling, that is a tendency to regard a program as a physical model simulating the behavior of either a real or imaginary part of the world, see  [Knudsen]. However, as observed in  [Tai], there seems to be a division between the European interpretation of object orientation (which remains close to the original notion of conceptual modeling) and the American interpretation (which is of a more pragmatic nature as it stresses the importance of data abstraction and the reusability of program components).

A classification of object-oriented languages

To be characterized as object-oriented, a language must minimally support an object creation facility and a message-passing facility (message-passing in the sense of method invocation). In addition, many languages provide a mechanism to define classes together with some form of inheritance. See slide 5-classification.

Objects -- language characteristics

• object creation facility
• message-passing capability
• class capability
• inheritance features

Classification

• hybrid -- C, Lisp, Pascal, Prolog
• frame-based -- knowledge-based reasoning
• distributed, concurrent, actor -- parallel computing
• alternative object models -- prototypes, delegation

slide: A classification of languages

Actually, as we will see in section dimensions, one may have a lively debate on the proper design dimensions of object-oriented programming languages. An important issue in this respect is what makes a language object-oriented as opposed to object-based. Other issues in this debate are whether an object-oriented language must support classes (in addition to a mechanism to create objects) and whether (static) inheritance should be preferred above (dynamic) delegation. This debate is reflected in a number of research efforts investigating alternative object models and object communication mechanisms. See section prototypes.

Of the 69 (standalone) object-oriented languages surveyed, 53 were research projects and only 16 were commercial products. Of these, 14 were extensions of either Lisp (10) or C (4). Among the remaining languages, quite a number were derived from languages such as Pascal, Ada or Prolog. There is a great diversity between the different object-oriented languages. However, following  [Sau89], they may be divided among subcategories reflecting their origin or the area of application for which they were developed (as shown above).

Hybrid languages

These, having originated out of (an object-oriented extension of) an already existing language, are likely to be applied in a similar area to their ancestor. In practice, this category of languages (which includes C++ and CLOS) seems to be quite important, partly because their implementation support is as good as the implementation support for their base languages and, more importantly, they allow potential software developers a smooth transition from a non object-oriented to an object-oriented approach.

Frame-based languages

in contrast to the previous category, these were explicitly developed to deal with one particular application area, knowledge-based reasoning. A frame is a structure consisting of slots that may either indicate a relation to other frames or contain the value of an attribute of the frame. In fact, the early frame-based languages such as FRL,  [FRL], and KRL,  [KRL], may be considered as object-oriented avant la lettre, that is before object orientation gained its popularity. Later frame-based systems, such as KEE,  [KEE], and LOOPS,  [LOOPS], incorporated explicitly object-oriented notions such as classes and (multiple) inheritance.

Concurrent, distributed and actor languages

To promote the use of parallel processing architectures a number of parallel object-oriented languages have been developed, among which are the language Hybrid (which supports active objects with their own thread of control,  [Nierstrasz]), Concurrent Smalltalk (a concurrent extension of Smalltalk), Orient-K (a language for parallel knowledge processing) and POOL-T (which may be characterized as a simplified version of Ada), see  [YT87]. More recently sC++, an extension of C++ with synchronous active objects has been proposed in  [Petitpierre98]. A realisation of the same concept in Java has also been proposed, albeit without compiler support, in  [Petitpierre99].

POOL-T also supports the notion of active objects. Active objects have a body which allows them to execute their own activity in parallel with other active objects. To answer a request to execute a method, an active object must explicitly interrupt its activity (by means of an answer or accept statement as in Ada). POOL-T is interesting, primarily, because it is complemented by extensive theoretical research into the semantical foundations of parallel object-oriented computing. See  [BRR90] and also section semantics. The idea of simultaneously active objects leads in a natural way to the notion of distributed object-oriented languages that support objects which may be located on geographically distinct processors and which communicate by means of (actual) message passing. Examples of such languages are Distributed Smalltalk (a distributed extension of Smalltalk that introduces so-called proxy objects to deal with communication between objects residing on different processors,  [Bennett]) and Emerald (that supports primitives to migrate objects across a processor network,  [Black]). All parallel/distributed object-oriented languages introduced thus far are based on a traditional object model insofar as an object retains its identity during its lifetime. In contrast, the so-called actor languages support a notion of object whereby the parallel activity of an object is enabled by self-replacement of the object in response to a message. Self-replacement proceeds as follows. Each actor object has a mail-queue. When a message arrives for the actor object, the object invokes the appropriate method and subsequently creates a successor object (which basically is a copy of itself with some modifications that may depend upon the contents of the message). Message handling occurs asynchronously. This scheme of asynchronous message passing enables an actor system to execute in parallel, since during the execution of a method the replacement object may proceed to handle other incoming messages. In actor systems, object identity is replaced by what may be called mail-queue or address identity. From a theoretical viewpoint this allows us to treat actor objects as functions (in a mathematical sense) that deliver an effect and another object in response to a message. However, pragmatically this leads to a complicated and quite low-level object model which is hard to implement in a truly parallel way.

Alternative object models

Since the introduction of Smalltalk, the predominant notion of objects has been based on the distinction between classes and objects. Classes serve to describe the functionality and behavior of objects, while objects are instance of classes. In other words, classes serve as templates to create objects. Inheritance, then, may be regarded as a means by which to share (descriptions of) object behavior. It is generally defined on classes in terms of a derivation mechanism, that allows one to declare a class to be a subclass of another (super) class. The distinction between classes and objects leads to a number of difficulties, both of a pragmatic and theoretical nature. (See also sections dimensions and meta for a discussion of the theoretical problems.) For example, the existence of one-of-a-kind classes, that is classes which have only one instance, is often considered unnatural. An example of a class-less language is the language Self. Self has a Smalltalk-like syntax, but in contrast to Smalltalk only supports objects (containing slots) and messages, and hence no classes. Slots may be designated to be parent-slots which means that messages that cannot be handled by the object itself are delegated to the parent object. In contrast to inheritance, which is static since the inherited functionality is computed at object creation time, delegation to parent objects as in Self is dynamic, since parent slots may be changed during the lifetime of an object. Objects in Self may themselves be used to create other objects (as copies of the original object) in a similar way as classes may be used to create instances. However, the changes made to an object are propagated when cloning object copies. Single objects, from which copies are taken, may in other words be regarded as prototypes, approximating in a dynamic way the functionality of their offspring, whereas classes provide a more static, so to speak universal, description of their object instances. Self employs runtime compilation, which is claimed to result in an efficiency comparable to C in  [US87]. In section prototypes we will discuss the use of prototypes and the distinction between inheritance and delegation.

Alternative object models may also be encountered in object-oriented database managements systems and in systems embedding objects such as hypertext or hypermedia systems.

Object extensions of Lisp, C and Prolog

The notion of object is to a certain extent orthogonal to, that is independent of, language constructs around which programming languages may be constructed, such as expressions, functions and procedures. Hence, it should come as no surprise that a number of (popular) object-oriented programming languages were originally developed as extensions of existing languages or language implementations. See slide 5-extensions.

Object extensions

• Lisp -- LOOPS, FLAVORS, CLOS, FOOPS
• C -- Objective C, C++
• Prolog -- SPOOL, VULCAN, DLP

Commercial products -- languages

• Smalltalk, Eiffel, C++, Objective C, Object Pascal, Java

slide: Object-oriented languages

The advantage of extending an existing language with object-oriented constructs, from the point of view of the user, is that the object-oriented approach can be gradually learned. However, at the same time this may be a disadvantage, since a hybrid approach to software development may give rise to sloppy design. Many proponents of an object-oriented approach, therefore, believe that learning to use object-oriented constructs is best done in an environment as offered by Smalltalk, where classes and objects are the sole means of developing an application. It is noteworthy that, with the exception of Smalltalk, Eiffel and Java, many commercially available languages are actually extensions of existing languages such as Lisp, C and (to some extent) Prolog.

Lisp-based extensions

In  [Sau89], ten Lisp-based object-oriented languages are mentioned, among which are LOOPS (introducing a variety of object-oriented constructs, see  [LOOPS]), Flavors (which extends Lisp by adding generic functions that operate on objects, see  [Moon]), and CLOS (which is actually a standardization effort of the ANSI X3J13 group to define the Common Lisp Object Standard). CLOS is a widely used system containing some non-trivial extensions to the object model and the way in which polymorphic methods may be defined.

C-based extensions

Another very important class of object extensions is those of C-based object-oriented languages, of which the most well-known are Objective-C and C++. The concepts underlying these two extensions are radically different. Objective-C introduces objects as an add-on to the constructs (including structs) available in C, whereas C++ realizes a close (and efficient) coupling between the struct (record) notion of C and the concept of a class. In other words, in Objective-C there is a clear distinction between conventional C values and data types such as int, float and struct on the one hand, and objects on the other hand. Objects have a special data type (id) which allows them to be treated as first class elements. To define an object class, both an interface and implementation description must be given. These descriptions are preceded by a special sign to designate Objective-C specific code. Also, method declarations (in the interface description) and method definitions (which are to be put in the implementation section) must be preceded by a special sign to designate them as methods available for clients of object instances of that class. The object model of Objective-C is similar to the object model of Smalltalk. In contrast, C++ quite radically departs from this object model in order to achieve an as efficient as possible implementation of objects. The key to an efficient implementation lies in the integration of the struct (record) construct originally provided by C with the class concept, by allowing functions to be members of a struct. As explained in  [St91], the equivalences depicted in slide 5-structure hold.

Object structure -- efficient mapping

C++


struct A { ... }  ==  class   A { public: ...  }
class   A { ... }  ==  struct A { private: ... }


slide: The equivalence between class and struct

This interpretation allows an efficient mapping of object structures to the memory of a computer, provided that the compiler is clever enough. Nevertheless, the efficiency of C++ comes at a price. C++ does support micro-efficiency but does not necessarily lead to the design of efficient code. In particular, hand-crafted memory management will not necessarily offer the most efficient solution when compared with built-in support, but is almost certainly detrimental to the quality of the code.

Prolog-based extensions

A quite different class of object-oriented extensions, used primarily in research laboratories, consists of attempts to incorporate object-oriented features in (high-level) logic-based languages, such as Prolog. Among these are languages such as SPOOL (developed in the context of the Japanese fifth-generation computing project, see  [Fukanaga]), Vulcan (that provides a preprocessor giving syntactic support for embedding objects in concurrent logic programming languages, see  [Kahn]) and DLP (a language combining logic programming with object-oriented features and parallelism developed by the author, see appendix DLP). The list of research articles covering the subject of combining logic programming and object-oriented programming is quite extensive. An overview and discussion of the various approaches is given in  [Davison93] and also in  [Eliens92].

Script languages -- integration with Java

Scripting has become a popular way to create applications, in particular GUI-based applications and Web applications. Tcl/Tk and Python are extensively used for GUI-based applications. For Web applications, scripting may be used at the client-side, for example to customize HTML pages using Javascript, or at the server-side, for writing CGI-scripts in (for example) Perl.

Script languages

Java embedding

• Javascript -- Dynamic HTML
• Perl -- CGI/Web library
• JPL

• Tcl/Tk -- tclets
• Jacl, Tcl Blend

• Python -- Grail
• JPython

slide: Script languages

Most of the scripting languages, including Tcl/Tk, Perl and Python, have an extensive library for creating (server-side) Web applications. For Tcl/Tk, there exists a Netscape plugin which allows for the inclusion of so-called tclets (pronounce ticklets), applets written in Tcl/Tk, in a HTML Web page.

Scripting has clear advantages for rapid prototyping. Disadvantages of scripting concern the lack of efficiency, and the absence of compile-time checks.

Script languages may be extended using C/C++, and more recently Java. The impact of Java becomes evident when considering that there exists a Java implementation for almost each scripting language, including Tcl/Tk, Perl and Python. JPython, which is the realization of Python in Java, even offers the possibility to integrate Python classes with Java classes, and is announced as a candidate scripting platform for Java in  [JPython].

Java has also in other respects stimulated programming language research, since it appears to be an ideal platform for realising higher level programming languages.

Objects in Javascript

Originally, objects were not part of the languages Tcl/Tk and Perl. For these languages, objects have been added in an ad hoc fashion. In contrast, Python has been developed as an object-oriented language from its inception.

Javascript is a somewhat special case, since it allows for the use of built-in objects, in particular the objects defined by the Document Object Model (DOM), and its precursors. Nevertheless, due to its dynamic nature, Javascript also allows for creating user-defined objects, as indicated in the example below.



<script language=Javascript>    javascript
function object_display(msg) { // object method
return msg + ' (' + this.variable++ + ')';
}

function object() { // object constructor
this.variable=0;
this.display = object_display;
return this;
}

var a = new object(); // create object

document.write(a.display("a message"));
document.write(a.display("another message"));
</script>

The trick is to define a function that allocates the storage for instance variables, which may include references to functions. Using the keyword new, a new structure is created that may be used as an object.

As an aside, Javascript has become surprisingly popular for writing dynamic HTML pages, as well as for writing server-side scripts. It is also supported by many VRML (Virtual Reality Modeling Language) browsers to define script nodes. See section DIVA. A reference implementation of Javascript is available, for embedding Javascript in C/C++ applications.

Comparing Smalltalk, Eiffel, C++ and Java

subsections:

The languages Smalltalk, Eiffel, C++ and Java may be regarded as the four most important (and popular) representatives of classical object-oriented languages, classical in the sense of being based on a class/object distinction.

Criteria for comparison

• class libraries
• programming environment
• language characteristics

slide: Criteria for comparison

In this section we will compare these languages with respect to what may be called their intrinsic language characteristics. Before that, however, we will indicate some other (more external) criteria for comparison such as the availability of class libraries and the existence of a programming environment. See slide 5-comparison. Our discussion is based on (but in some respects disagrees with and extends)  [BPS89].

Criteria for comparison

When choosing a particular programming language as a vehicle for program development a number of factors play a role, among which are the availability of a class library, the existence of a good programming environment, and, naturally, the characteristics of the language itself.

Class libraries

An important criterion when selecting a language may be the availability of sufficient class library support. A general class library, and preferably libraries suitable for the application domain one is working in, may drastically reduce development time. Another important benefit of using (well-tested) class libraries is an improvement of the reliability of the application.

Smalltalk (that is Smalltalk-80 of ParcPlace Systems) comes with a large collection of general purpose and graphics programming classes, that are identical for both MS-DOS and Unix platforms. Also Eiffel comes with a standard collection of well-documented libraries containing common data structures, container classes and classes for graphics programming. For both Smalltalk and Eiffel, the accompanying classes may almost be considered to be part of the language definition, in the sense that they provide a standard means to solve particular problems.

In contrast, for C++ there is almost no standard library support (except for IO stream classes). Even worse, the various C++ compiler vendors disagree considerably in what functionality the standard class libraries of C++ must offer. Fortunately, however, there is an increasingly large number of third party libraries (commercially and non-commercially) available. The burden of choosing the appropriate libraries is, however, placed on the shoulders of a user or a company, which has the advantage that a more optimal solution may be obtained than possible within the confines of standard libraries.

Java, on the other hand, offers an overwhelming amount of APIs, including a Reflection API, for meta programming, APIs for networking, communication, and APIs for multimedia and 3D. Perhaps the greatest benefit of Java is the effort put into the standardization of these APIs.

Programming environments

Another selection criterion in choosing a language is the existence of a good programming environment. What constitutes a good programming environment is not as simple as it may seem, since that depends to a large extent upon the experience and preferences of the user. For example, with respect to operating systems, many novice users favor a graphical interface as originally offered by the Apple Macintosh computers, while experienced users often feel constrained by the limitations imposed by such systems. In contrast, experienced users may delight in the terseness and flexibility of the command-based Unix operating system, which leads to outright bewilderment with many novice users.

Of the object-oriented programming languages we consider, Smalltalk definitely offers the most comprehensive programming environment (including editors, browsers and debuggers). Eiffel comes with a number of additional tools (such as a graphical browser, and a program documentation tool) to support program development (and maintenance).

In contrast, C++ usually comes with nothing at all. However, increasingly many tools (including browsers and debuggers) have become available.

For Java there are a number of IDEs (Integrated Development Environments) available, most of which run only on the PC platform.

Language characteristics

Despite the commonality between Smalltalk, Eiffel, C++ and Java (which may be characterized by saying that they all support data abstraction, inheritance, polymorphism and dynamic binding), these languages widely disagree on a number of other properties, such as those listed in slide
5-characteristics.

Language characteristics

• uniformity of data structures
• documentation value
• reliability
• inheritance mechanisms
• efficiency
• memory management
• language complexity

slide: Language characteristics

These characteristics were used in  [BPS89] to compare Smalltalk, Eiffel and C++ with the language Oberon, which offers what may be called a minimal (typed) object-oriented language. We will, however, limit our discussion here to Smalltalk, Eiffel, C++, and, additionally, Java.

Language characteristics

Smalltalk, Eiffel, C++ and Java differ with respect to a number of language characteristics. An indication of the differences between these languages is given slide
5-language-characteristics.

SmalltalkEiffelC++Java
uniformityhigh medium low medium
documentation value medium high medium high
reliability medium medium low* high*
protected operations no no yes yes
multiple inheritance no yes yes no*
efficiency low medium high low
garbage collection yes yes no* yes
language complexity low* medium high medium

slide: Comparing Smalltalk, Eiffel, C++ and Java

This characterization conforms to the one given in  [BPS89], with which I think the majority of the object-oriented community will agree. It is further motivated below. However, the places indicated by an asterisk deserve some discussion. In particular, I wish to stress that I disagree with characterizing the reliability of C++ as low. (See below.)

Uniformity

In Smalltalk, each data type is described by a class. This includes booleans, integers, real numbers and control constructs. In Eiffel there is a distinction between elementary data types (such as boolean, integer and real) and (user-defined) classes. However (in the later versions of Eiffel) the built-in elementary types behave as if declared by pre-declared classes. For C++, the elementary data types and simple data structures (as may be defined in C) do not behave as objects. To a certain extent, however, programmers may deal with this non-uniformity by some work-around, for example by overloading functions and operators or by embedding built-in types in a (wrapper) class. Java may be regarded as a simplified version of C++. Due to its restrictions, such as the absence of operator overloading and type casts, the language appears to be more uniform for the programmers.

Documentation value

Smalltalk promotes a consistent style in writing programs, due to the assumption that everything is an object. One of perhaps the most important features of Eiffel is the use of special keywords for constructs to specify the correctness of programs and the behavioral properties that determine the external interface of objects. Moreover, Eiffel provides a tool to extract a description of the interface of the method classes (including pre- and post-conditions associated with a method) which may be used to document (a library of) classes. To my taste, however, the Eiffel syntax leads to somewhat verbose programs, at least in comparison with programs written in C++.

The issue of producing documentation from C++ is still open. A number of tools exist (including a WEB-like system for C++ and a tool to produce manual pages from C++ header files) but no standard has yet emerged. Moreover, some people truly dislike the terseness of C/C++. Personally, I prefer the C/C++ syntax above the syntactical conventions of both Eiffel and Smalltalk, provided that it is used in a disciplined fashion.

Java programs may be documented using javadoc. The javadoc program may be regarded as the standard C++ has been waiting for, in vain.

Reliability

Smalltalk is a dynamically typed language. In other words, type checking, other than detecting runtime errors, is completely absent. Eiffel is generally regarded as a language possessing all characteristics needed for writing reliable programs, such as static type checking and constructs for stating correctness assertions (which may be checked at runtime). Due to its heritage from C, the language C++ is still considered by many as unreliable. In contrast to C, however, C++ does provide full static type checking, including the signature of functions and external object interfaces as arise in independent compilation of module files. Nevertheless, C++ only weakly supports type checking across module boundaries. Contrary to common belief, Eiffel's type system is demonstrably inconsistent, due to a feature that enables a user to dynamically define the type of a newly created object in a virtual way (see section self-reference). This does not necessarily lead to type-insecure programs though, since the Eiffel compiler employs a special algorithm to detect such cases. The type system of C++, on the other hand, is consistent and conforms to the notion of subtype as introduced informally in the previous part. Nevertheless, C++ allows the programmer to escape the rigor of the type system by employing casts.

An important feature of Eiffel is that it supports assertions that may be validated at runtime. In combinations with exceptions, this provides a powerful feature for the development of reliable programs.

At the price of some additional coding (for example, to save the current state to enable the use of the old value), such assertions may be expressed by using the assert macros provided for C++.

In defense of C++, it is important to acknowledge that C++ offers adequate protection mechanisms to shield classes derived by inheritance from the implementation details of their ancestor classes. Neither Smalltalk nor Eiffel offer such protection.

Java was introduced as a more reliable variant of C++. Java's reliability comes partly from the shielded environment offered by the Java virtual machine, and partly from the absence of pointers and the availability of built-in garbage collection. Practical experience shows that for the average student/programmer Java is indeed substantially less error-prone than C++.

Inheritance

Smalltalk offers only single inheritance. In contrast, both Eiffel and C++ offer multiple inheritance. For statically typed languages, compile-time optimizations may be applied that result in only a low overhead. In principle, multiple inheritance allows one to model particular aspects of the application domain in a flexible and natural way.

As far as the assertion mechanism offered by Eiffel is concerned,  [Meyer88] gives clear guidelines prescribing how to use assertions in derived classes. However, the Eiffel compiler offers no assistance in verifying whether these rules are followed. The same guidelines apply to the use of assertions in C++, naturally lacking compiler support as well.

The Java language offers only single (code) inheritance, but allows for multiple interface inheritance. The realization of (multiple) interfaces seems to be a fairly good substitute for multiple (implementation) inheritance.

Efficiency

Smalltalk, being an interpreted language, is typically slower than conventionally compiled languages. Nevertheless, as discussed in section self-implementation, interpreted object-based languages allow for significant optimizations, for example by employing runtime compilation techniques.

The compilation of Eiffel programs can result in programs having adequate execution speed. However, in Eiffel dynamic binding takes place in principle for all methods. Yet a clever compiler can significantly reduce the number of indirections needed to execute a method.

In contrast to C++, in Eiffel all objects are created on the heap. The garbage collection needed to remove these objects may affect the execution speed of programs.

C++ has been designed with efficiency in mind. For instance, the availability of inline functions, and the possibility to allocate objects on the runtime stack (instead of on the heap), and the possibility to declare friend functions and classes that have direct access to the private instance variables of a class allow the programmer to squeeze out the last drop of efficiency. However, as a drawback, when higher level functionality is needed (as in automatic garbage collection) it must be explicitly programmed, and a similar price as when the functionality would have been provided by the system has to be paid. The only difference is that the programmer has a choice.

At the time of writing there does not exist a truly efficient implementation of the Java language. Significant improvements may be expected from the JIT (Just In Time) compilers that produce native code dynamically, employing techniques as originally developed for the Self language, discussed in section Self.

Language complexity

Smalltalk may be regarded as having a low language complexity. Control is primarily effected by message passing, yet, many of the familiar conditional and iterative control constructs reappear in Smalltalk programs emulated by sending messages. This certainly has some elegance, but does not necessarily lead to easily comprehensible programs.

Eiffel contains few language elements that extend beyond object-oriented programming. In particular, Eiffel does not allow for overloading method names (according to signature) within a class. This may lead to unnecessarily elaborate method names. (The new version of Eiffel (Eiffel-3) does allow for overloading method names.)

Without doubt, C++ is generally regarded as a highly complex language. In particular, the rules governing the overloading of operators and functions are quite complicated. The confusion even extends to the various compiler suppliers, which is one of the reasons why C++ is still barely portable. Somewhat unfortunately, the rules for overloading and type conversion for C++ have to a large extent been determined by the need to remain compatible with C. Even experienced programmers need occasionally to experiment to find out what will happen.

According to  [BPS89], C++ is too large and contains too much of the syntax and semantics inherited from C. However, the validity of their motto small is beautiful is not as obvious as it seems. The motivations underlying the introduction of the various features incorporated in C++ are quite well explained in  [Stroustrup97]. The main problem, to my mind, in using C++ (or any of the object-oriented languages for that matter) lies in the area of design. We still have insufficient experience in using abstract data types to define a complete method and operator interface including its relation to other data types (that is its behavior under the various operators and type conversions that apply to a particular type). The problem is hence not only one of language design but of the design of abstract data types.

Java is certainly less complex than C++. For example, it offers no templates, no operator overloading and no type coercion operators. However, although Java is apparently easier to use, it is far less elegant than C++ when it comes to creating user-defined types. Class interfaces in Java are usually much more verbose than similar interfaces in C++. And, due to the absence of templates, type casts are necessary in many places. On the other hand, casts in Java are type safe.

Design dimensions of object-oriented languages

subsections:

Despites the widespread adoption of object-oriented terminology in the various areas of computer science and software development practice, there is considerable confusion about the precise meaning of the terms employed and the (true) nature of object-oriented computing. In an attempt to resolve this confusion,  [Wegner87] (in the landmark paper Dimensions of object-based language design) introduces the distinction between object-based and object-oriented. See slide 5-object-based. This distinction comes down to, roughly, the distinction between languages providing only encapsulation (object-based) or encapsulation plus inheritance (object-oriented). See section object-based. Another issue in the debate about object-orientation is the relation between classes and types.  [Wegner87] concludes that the notions of objects, classes and inheritance (that constitute the classical object model) are highly interrelated, and instead proposes an orthogonal approach by outlining the various dimensions along which to design an object-oriented language. These dimensions may be characterized by the phrases: objects, types, delegation and abstraction.

Object-oriented language design

• object: state + operations
• class: template for object creation
• inheritance: super/base and subclasses


object-oriented =
objects + classes + inheritance


strong typing -- compile time checking

slide: Object-based versus object-oriented

In this section we will look at the arguments presented in  [Wegner87] in somewhat more detail. Also, we will look at the viability of combining seemingly disparate paradigms (such as the logic programming paradigm) with the object-oriented language paradigm. In the sections that follow, we will discuss some alternatives and extensions to the object model.

Object-based versus object-oriented

Classes versus types

Another confusion that frequently arises is due to the ill-defined relationship between the notion of a class and the notion of a type. The notion of types is already familiar from procedural programming languages such as Pascal and (in an ill-famed way) from C. The type of variables and functions may be profitably used to check for (syntactical) errors. Strong static type checking may prevent errors ranging from simple typos to using undefined (or wrongly defined) functions. The notion of a class originally has a more operational meaning. Operationally, a class is a template for object creation. In other words, a class is a description of the collection of its instances, that is the objects that are created using the class description as a recipe. Related to this notion of a class, inheritance was originally defined as a means to share (parts of) a description. Sharing by (inheritance) derivation is, pragmatically, very convenient. It provides a more controlled way of code sharing than, for example, the use of macros and file inclusion (as were popular in the C community). Since  [Wegner87] published his original analysis of the dimensions of object-oriented language design, the phrase object-oriented has been commonly understood as involving objects, classes and inheritance. This is the traditional object model as embodied by Smalltalk and, to a large extent, by Eiffel, C++ and Java. However, unlike Smalltalk, both Eiffel and C++ have also been strongly influenced by the abstract data type approach to programming. Consequently, in Eiffel and C++ classes have been identified with types and derivation by inheritance with subtyping. Unfortunately, derivation by inheritance need not necessarily result in the creation of proper subtypes, that is classes whose instances conform to the behavior specified by their base class. In effect, derived classes may be only distantly related to their base classes when inheritance is only used as a code sharing device. For example, a window manager class may inherit from a list container class (an idiom used in Meyer, 1988).

Towards an orthogonal approach -- type extensions

According to  [Wegner87], much of the confusion around the various features of object-oriented programming languages arises from the fact that these features are largely interdependent, as for instance the notion of object and class on the one hand, and the notion of class and inheritance on the other. To resolve this confusion,  [Wegner87] proposes a more orthogonal approach to characterize the various features of object-oriented languages, according to dimensions that are to a large extent independent. See slide 5-orthogonal.

Orthogonal approach

• objects -- modular computing agents
• types -- expression classification
• delegation -- resource sharing
• abstraction -- interface specification

slide: Orthogonal dimensions

The features that constitute an object-oriented programming language in an orthogonal way are, according to  [Wegner87]: objects, types, delegation and abstraction.

Objects

are in essence modular computing agents. They correspond to the need for encapsulation in design, that is the construction of modular units to which a principle of locality applies (due to combining data and operations). Object-oriented languages may, however, differ in the degree to which they support encapsulation. For example, in a distributed environment a high degree of encapsulation must be offered, prohibiting attempts to alter global variables (from within an object) or local instance variables (from without). Moreover, the runtime object support system must allow for what may best be called remote method invocation. As far as parallel activity is concerned, only a few languages provide constructs to define concurrently active objects. See section Active for a more detailed discussion. Whether objects support reactiveness, that is sufficient flexibility to respond safely to a message, depends largely upon (program) design.  [Meyer88], for instance, advocates a {\em shopping list approach} to designing the interface of an object, to allow for a high degree of (temporal) independence between method calls.

Types

may be understood as a mechanism for expression classification. From this perspective, Smalltalk may be regarded as having a dynamic typing system: dynamic, in the sense that the inability to evaluate an expression will lead to a runtime error. The existence of types obviates the need to have classes, since a type may be considered as a more abstract description of the behavior of an object. Furthermore, subclasses (as may be derived through inheritance) are more safely defined as subtypes in a polymorphic type system. See section flavors. At the opposite side of the type dimension we find the statically typed languages, which allow us to determine the type of the designation of a variable at compile-time. In that case, the runtime support system need not carry any type information, except a dispatch table to locate virtual functions.

Delegation

(in its most generic sense) is a mechanism for resource sharing. As has been shown in  [Lieber], delegation subsumes inheritance, since the resource sharing effected by inheritance may easily be mimicked by delegating messages to the object's ancestors by means of an appropriate dispatching mechanism. In a narrower sense, delegation is usually understood as a more dynamic mechanism that allows the redirection of control dynamically. In addition, languages supporting dynamic delegation (such as Self) do not sacrifice dynamic self-reference. This means that when the object executing a method refers to itself, the actual context will be the delegating object. See section prototypes for a more detailed discussion. In contrast, inheritance (as usually understood in the context of classes) is a far more static mechanism. Inheritance may be understood as (statically) copying the code from an ancestor class to the inheriting class (with perhaps some modifications), whereas delegation is truly dynamic in that messages are dispatched to objects that have a life-span independent of the dispatching object.

Abstraction

(although to some extent related to types) is a mechanism that may be independently applied to provide an interface specification for an object. For example, in the presence of active objects (that may execute in parallel) we may need to be able to restrict dynamically the interface of an object as specified by its type in order to maintain the object in a consistent state. Also for purely sequential objects we may impose a particular protocol of interaction (as may, for example, be expressed by a contract) to be able to guarantee correct behavior. Another important aspect of abstraction is protection. Object-oriented languages may provide (at least) two kinds of protection. First, a language may have facilities to protect the object from illegal access by a client (from without). This is effected by annotations such as private and protected. And secondly, a language may have facilities to protect the object (as it were from within) from illegal access through delegation (that is by instances of derived object classes). Most languages support the first kind of protection. Only few languages, among which are C++ and Java, support the second kind too. The independence of abstraction and typing may further be argued by pointing out that languages supporting strong typing need not enforce the use of abstract data types having a well-defined behavior.

Object-oriented programming has evolved as a new and strong paradigm of programming. Has it? Of the languages mentioned, only Smalltalk has what may be considered a radically new language design (and to some extent also the language Self, that we will discuss in the next section). Most of the other languages, including Eiffel, C++ (and for that matter also CLOS and Oberon), may be considered as object-oriented extensions of already existing languages or, to put it more broadly, language paradigms. Most popular are, evidently, object-oriented extensions based on procedural language paradigms, closely followed by the (Lisp-based) extensions of the functional language paradigm. Less well-known are extensions based on the logic programming paradigm, of which DLP is my favorite example. In  [Wegner92], it is argued that the logic programming paradigm does not fit in with an object-oriented approach. I strongly disagree with this position. However, the arguments given in  [Wegner92] to defend it are worthwhile, in that they make explicit what desiderata we may impose on object-oriented languages. Remaining within the confines of a classical object model, the basic ingredients for an object-oriented extension of any language (paradigm) are: objects, classes and inheritance. Although the exact meaning of these notions is open for discussion, language designers seem to have no difficulty in applying these concepts to extend (or design) a programming language.

Open systems

• reactive -- flexible (dynamic) choice of actions
• modular -- (static) scalability

Dimensions of modularity

• encapsulation boundary -- interface to client
• distribution boundary -- visibility from within objects
• concurrency boundary -- threads per object, synchronization

slide: Dimensions of modularity

According to  [Wegner92], the principal argument against combining logic programming and object-oriented programming is that such a combination does not support the development of open systems without compromising the logical nature of logic programming. Openness may be considered as one of the prime goals of object orientation. See slide 5-open. A software system is said to be open if its behavior can be easily modified and extended.  [Wegner92] distinguishes between two mechanisms to achieve openness; dynamically through reactiveness, and statically through modularity. Reactiveness allows a program to choose dynamically between potential actions. For sequential object-oriented languages, late binding (that is, the dispatching mechanism underlying virtual function calls) is one of the mechanisms used to effect the dynamic selection of alternatives. Concurrent object-oriented languages usually offer an additional construct, in the form of a guard or accept statement, to determine dynamically which method call to answer. In both cases, the answer depends upon the nature of the object and (especially in the latter case) the state of the object (and its willingness to answer). Openness through modularity means that a system can safely be extended by adding (statically) new components. The issue of openness in the latter sense is immediately related to the notion of scalability, that is the degree to which a particular component can be safely embedded in a larger environment and extended to include new functionality. At first sight, classes and inheritance strongly contribute to achieving such (static) openness. However, there is more to modularity than the encapsulation provided by classes only. From a modeling perspective, encapsulation (as provided by objects and classes) is the basic mechanism to define the elements or entities of a model. The declarative nature of an object-oriented approach resides exactly in the opportunity to define such entities and their relations through inheritance. However, encapsulation (as typically understood in the context of a classical object model) only provides protection from illegal access from without. As such, it is a one-sided boundary. The other side, the extent to which the outside world is visible for the object (from within), may be called the distribution boundary. Many languages, including Smalltalk and C++, violate the distribution boundary by allowing the use of (class-wide) global variables. (See also section meta.) Evidently, this may lead to problems when objects reside on distinct processors, as may be the case in distributed systems. Typically, the message passing metaphor (commonly used to characterize the interaction between objects) contains the suggestion that objects may be physically distributed (across a network of processors). Also (because of the notion of encapsulation), objects are often regarded as autonomous entities, that in principle may have independent activity. However, most of the languages mentioned do not (immediately) fulfill the additional requirements needed for actual physical distribution or parallel (multi-threaded) activity.

Object-oriented logic programming

Logic programming is often characterized as relational programming, since it allows the exhaustive exploration of a search space defined by logical relations (for instance, by backtracking as in Prolog). The advantage of logic programming, from a modeling point of view, is that it allows us to specify in a logical manner (that is by logical clauses) the relations between the entities of a particular domain. A number of efforts to combine logic programming with object-oriented features have been undertaken, among which is the development of the language Vulcan. Vulcan is based on the Concurrent Prolog language and relies on a way of implementing objects as perpetual processes. Without going into detail, the idea (originally proposed in  [ST83]) is that an object may be implemented as a process defined by one or more (recursive) clauses. An object may accept messages in the form of a predicate call. The state of an object is maintained by parameters of the predicate, which are (possibly modified by the method call) passed to the recursive invocation of one of the clauses defining the object. To communicate, an object (defined as a process) waits until a client asks for the execution of a method. The clauses defining the object are then evaluated to check which one is appropriate for that particular method call. If there are multiple candidate clauses, one is selected and evaluated. The other candidate clauses are discarded. Since the clauses defining an object are recursive, after the evaluation of a method the object is ready to accept another message. The model of (object) interaction supported by Concurrent Prolog requires fine-grained concurrency, which is possible due to the side-effect free nature of logical clauses. However, to restrict the number of processes created during the evaluation of a goal, Concurrent Prolog enforces a committed choice between candidate clauses, thus throwing away alternative solutions.  [Wegner92] observes, rightly I think, that the notion of committed choice is in conflict with the relational nature of logic programming. Indeed, Concurrent Prolog absolves logical completeness in the form of backtracking, to remain within the confines of the process model adopted.  [Wegner92], however, goes a step further and states that reactiveness and backtracking are irreconcilable features. That these features may fruitfully be incorporated in a single language framework is demonstrated by the language DLP. However, to support backtracking and objects, a more elaborate process model is needed than the process model supported by Concurrent Prolog (which in a way identifies an object with a process). With such a model (sketched in appendix E), there seems to be no reason to be against the marriage of logic programming and object orientation.

Active objects -- synchronous Java/C++

When it comes to combining objects (the building blocks in an object-oriented approach) with processes (the building blocks in parallel computing), there are three approaches conceivable. See slide 6-o-conc.

Object-based concurrency

• multiple active objects -- rendezvous
• asynchronous communication -- message buffers

slide: Objects and concurrency

One can simply add processes as an additional data type. Alternatively, one can introduce active objects, having activity of their own, or, one can employ asynchronous communication, allowing the client and server object to proceed independently.

Processes

The first, most straightforward approach, is to simply add processes as a primitive data type, allowing the creation of independent threads of processing. An example is Distributed Smalltalk (see Bennett, 1987). Another example is Java, which provides support for threads, synchronized methods and statements like wait and notify to protect re-entrant concurrent methods. The disadvantage of this approach, however, is that the programmer has full responsibility for the most difficult part of parallel programming, namely the synchronization between processes and the avoidance of common errors such as simultaneously assigning a value to a shared variable. Despite the fact that the literature, see  [Andrews], abounds with primitives supporting synchronization (such as semaphores, conditional sections and monitors), such an approach is error-prone and means a heavy burden on the shoulders of the application developer.

Active objects

A second, and in my view preferable, approach is to introduce explicitly a notion of active objects. Within this approach, parallelism is introduced by having multiple, simultaneously active objects. An example of a language supporting active objects is POOL, described in  [Am87]. Communication between active objects occurs by means of a (synchronous) rendezvous. To engage in a rendezvous, however, an active object must interrupt its own activity by means of an (Ada-like) accept statement (or answer statement as it is called in POOL), indicating that the object is willing to answer a message. The advantage of this approach is, clearly, that the encapsulation boundary of the object (its message interface) can conveniently be employed as a monitor-like mechanism to enforce mutual exclusion between method invocations. Despite the elegance of this solution, however, unifying objects and processes in active objects is not without problems. First, one has to decide whether to make all objects active or allow both passive and active objects. Logically, passive objects may be regarded as active objects that are eternally willing to answer every message listed in the interface description of the object. However, this generalization is not without penalty in terms of runtime efficiency. Secondly, a much more serious problem is that the message-answering semantics of active objects is distinctly different from the message-answering semantics of passive objects with respect to self-invocation. Namely, to answer a message, an active object must interrupt its own activity. Yet, if an active object (in the middle of answering a message) sends a message to itself, we have a situation of deadlock. Direct self-invocation, of course, can be easily detected, but indirect self-invocations require an analysis of the complete method invocation graph, which is generally not feasible.

Asynchronous communication

Deadlock may come about by synchronous (indirect) self-invocation. An immediate solution to this problem is provided by languages supporting asynchronous communication, which provide message buffers allowing the caller to proceed without waiting for an answer. Asynchronous message passing, however, radically deviates from the (synchronous) message passing supported by the traditional (passive) object model. This has the following consequences. First, for the programmer, it becomes impossible to know when a message will be dealt with and, consequently, when to expect an answer. Secondly, for the language implementor, allocating resources for storing incoming messages and deciding when to deal with messages waiting in a message buffer becomes a responsibility for which it is hard to find a general, yet efficient, solution. Active objects with asynchronous message passing constitute the so-called actor model, which has influenced several language designs. See  [Agha].

Synchronous C++/Java

In  [Petitpierre98], an extension of C++ is proposed that supports active objects, method calls by rendez vous and dynamic checks of synchronization conditions. The concurrency model supported by this language, which is called sC++, closely resembles the models supported by CCS, CSP and Ada.

An example of the declaration of an active object in sC++ is given in slide ex-active.

sC++


active class S {
public:
m () { ... }
private:
@S () {  // pseudo-constructor
select {
01 -> m(); // external call
instructions ...
||
accept m;  // accept internal method
instructions ...
||
waituntil (date); // time-out
instructions ...
||
default           // default
instructions ...
}
}
};


slide: Synchronization conditions in sC++

The synchronization conditions for instances of the class are specified in a select statement contained in a constructor-like method, which defines the active body of the object. Synchronization may take place in either (external) calls to another active object, internal methods that are specified as acceptable, or time-out conditions. When none of the synchronization conditions are met, a default action may take place. In addition to the synchronization conditions mentioned, a when guard-statement may occur in any of the clauses of select, to specify conditions on the state of the object or real-time constraints.

The sC++ language is implemented as an extension to the GNU C++ compiler. The sC++ runtime environment offers the possibility to validate a program by executing random walks, which is a powerful way to check the various synchronization conditions. The model of active objects supported by sC++ has also been realized as a Java library, see  [Petitpierre99]. There is currently, however, no preprocessor or compiler for Java supporting synchronous active objects.

As argumented in  [Petitpierre98], one of the advantages of synchronous active objects is that they allow us to do away with event-loops and callbacks. Another, perhaps more important, advantage is that the model bears a close relationship with formal models of concurrency as embodied by CCS and CSP, which opens opportunities for the verification and validation of concurrent object-oriented programs. In conclusion, in my opinion, the active object model discussed deserves to become a standard for both C++ and Java, not because it unifies the concurrency model for these languages, which is for example also done by JThreads++ described in  [JThreads], but because it offers a high level of abstraction suitable for concurrent object-oriented software engineering.

Prototypes -- delegation versus inheritance

subsections:

The classical object model (which is constituted by classes, objects and inheritance) not only has its theoretical weaknesses (as outlined in the previous section) but has also been criticized from a more pragmatic perspective because of its inflexibility when it comes to developing systems. Code sharing has been mentioned as one of the advantages of inheritance (as it allows incremental development). However, alternative (read more flexible) forms of sharing have been proposed, employing prototypes and delegation instead of inheritance.

Alternative forms of sharing

A class provides a generic description of one or more objects, its instances. From a formal point of view, classes are related to types, and hence a class may be said to correspond to the set of instances that may be generated from it. This viewpoint leads to some anomalies, as in the case of abstract classes that at best correspond to partially defined sets. As another problem, in the context of inheritance, behavioral compatibility may be hard to arrive at, and hence the notion of subtype (which roughly corresponds with the subset relation) may be too restrictive. In practice, we may further encounter one-of-a-kind objects, for which it is simply cumbersome to construct an independent class. In a by now classical paper,  [
Lieber] proposes the use of prototypes instead of classes. The notion of prototypes (or exemplars) has been used in cognitive psychology to explain the incremental nature of concept learning. As  [Lieber] notes, the philosophical distinction between prototypes (which provide a representative example of an object) and classes (which characterize a set of similar objects) may have important pragmatical consequences as it concerns the incremental definition of (hierarchies of) related objects. First, it is (claims Lieberman, 1986) more natural to start from a concrete example than to start from an abstract characterization as given by a class. And secondly, sharing information between prototypes and clones (that is, modified copies) thereof is far more flexible than the rather static means of sharing code as supported by the class inheritance mechanism. Code sharing by inheritance may be characterized as creation time sharing, which in this respect is similar to creating a copy of the object by cloning. In addition, prototypes may also support lifetime resource sharing by means of delegation. In principle, delegation is nothing but the forwarding of a message. However, in contrast to the forwarding mechanism as described in sections delegation-in-Java and hush-idioms, delegation in the context of prototypes does not change implicit self-reference to the forwarding object. In other words, when delegating a message to a parent object, the context of answering the message remains the same, as if the forwarding object answers the request directly. See slide 5-prototypes.

Prototypes -- exemplars

• cloning -- creation time sharing

slide: Prototypes

An almost classical example used to illustrate prototypical programming is the example of a turtle object that delegates its request to move itself to a pen object (which has x and y coordinate attributes and a move method). The flexibility of delegation becomes apparent when we define a number of turtle objects by cloning the pen object and adding an y coordinate private to each turtle. In contrast to derivation by inheritance, the x coordinate of the pen object is shared dynamically. When changing the value of x in one of the turtle objects, all the turtle objects will be affected. Evidently, this allows considerable (and sometimes unwished for) flexibility. However, for applications (such as multimedia systems) such flexibility may be desirable.

Design issues

Strictly speaking, prototype-based delegation is not stronger than forwarding in languages supporting classes and inheritance. In  [Dony], a taxonomy of prototype-based languages is given. (This taxonomy has been partly implemented in Smalltalk. The implementation, however, employs so-called class-variables, which are not unproblematic themselves. See section meta.) One of the principal advantages of prototype-based languages is that they offer a consistent yet simple model of programming, consisting of objects, cloning and delegation. Yet, when designing a prototype-based language, a number of design decisions must be made (as reflected in the taxonomy given in Dony {\it et al.}, 1992). These issues concern the representation of the state of an object, how objects are created and the way in which delegation is handled. See slide 5-proto.

State

• slots -- parents
• variables and methods

Creation

• shallow cloning
• deep cloning

Delegation

• implicit delegation
• explicit delegation

slide: Prototypes -- state, creation, delegation

The basic prototype model only features slots which may store either a value or a piece of code that may be executed as a method. Alternatively, a distinction may be made between variables and methods. In both cases, late binding must be employed to access a value. In contrast, instance variable bindings in class-based languages are usually resolved statically. When creating a new object by cloning an existing object, we have the choice between deep copying and shallow copying. Only shallow copying, however, allows lifetime sharing (since deep copying results in a replica at creation time). Shallow copying is thus the obvious choice. Finally, delegation is usually handled implicitly, for instance by means of a special parent slot, indicating the ancestor of the object (which may be changed dynamically). Alternatively, it may be required to indicate delegation explicitly for each method. This gives a programmer more flexibility since it allows an object to have multiple ancestors, but at the price of an increase in notational complexity. Explicit delegation, by the way, most closely resembles the use of forwarding in class-based systems. One of the, as yet, unresolved problems of delegation-based computing is how to deal with what  [Dony] call split objects. An object may (internally) consist of a large number of (smaller) objects that are linked to each other by the delegation relation. It is not clear how to address such a complex object as a single entity. Also, the existence of a large number of small objects that communicate by message passing may impose severe performance penalties.

Implementation techniques -- Self

A major concern of software developers is (often) the runtime efficiency of the system developed. An order of magnitude difference in execution speed may, indeed, mean the difference between acceptance and rejection.

Improving performance

• special-purpose hardware
• hybrid languages
• static typing
• dynamic compilation

slide: Improving performance

There are a number of ways in which to improve on the runtime efficiency of programs, including object-oriented programs. For example,  [Ungar92] mention the reliance on special-purpose hardware (which thus far has been rapidly overtaken by new general-purpose processor technology), the use of hybrid languages (which are considered error-prone), static typing (which for object-oriented programming provides only a partial solution) and dynamic compilation (which has been successfully applied for Self). See slide 5-opt. As for the use of hybrid languages, of which C++ is an example, the apparent impurity of such an approach may (to my mind) even be beneficial in some cases. However, the programmer is required to deal more explicitly with the implementation features of the language than may be desirable. In general, both with respect to reliability and efficiency, statically typed languages have a distinct advantage over dynamically typed (interpreted) languages. Yet, for the purpose of fast prototyping, interpreted languages (like Smalltalk) offer an advantage in terms of development time and flexibility. Moreover, the use of (polymorphic) virtual functions and dynamic binding necessitate additional dynamic runtime support (that is not needed in strictly procedural languages). Clever compilation reduces the overhead (even in the case of multiple inheritance) to one or two additional indirections.

Dynamic compilation

The language Self is quite pure and simple in design. It supports objects with slots (that may contain both values and code, representing methods), shallow cloning, and implicit delegation (via a designated parent slot). Moreover, the developers of Self have introduced a number of techniques to improve the efficiency of prototype-based computing.

Self -- prototypes

• - objects, cloning, delegation

Dynamic compilation -- type information

• customized compilation
• message inlining
• lazy compilation
• message splitting

slide: Dynamic compilation -- Self

The optimization techniques are based on dynamic compilation, a technique that resembles the partial evaluation techniques employed in functional and logic programming. Dynamic compilation employs the type information gathered during the computation to improve the efficiency of message passing.

Whenever a method is repeatedly invoked, the address of the recipient object may be backpatched in the caller. In some cases, even the result may be inlined to replace the request. Both techniques make it appear that message passing takes place, but at a much lower price. More complicated techniques, involving lazy compilation (by delaying the compilation of infrequently visited code) and message splitting (involving a dataflow analysis and the reduction of redundancies) may be applied to achieve more optimal results.

Benchmark tests have indicated a significant improvement in execution speed (up to 60% of optimized C code) for cases where type information could be dynamically obtained. The reader is referred to  [Ungar92] for further details.

Meta-level architectures

Another weakness of the classical object model (or perhaps one of its strengths) is that the concept of a class easily lends itself to being overloaded with additional meanings and features such as class variables and metaclasses. These notions lead to extensions to the original class/instance scheme that are hard to unify in a single elegant framework. In this section we will study a proposal based on a reflexive relation between classes and objects. Depending on one's perspective, a class may either be regarded as a kind of abstract data type (specifying the operational interface of its object instances) or, more pragmatically, as a template for object creation (that is, a means to generate new instances).

The class concept

• abstract data type -- interface description
• object generator -- template for creation
• repository -- for sharing resources
• object -- instance of a metaclass

slide: The concept of class

In addition, however, in a number of systems A class may be used as a repository for sharing class-wide resources. For example, the Smalltalk language allows the definition of class variables that are accessible to all instances of the class. See slide 5-class.

Class variables

Clearly, the use of class variables violates what we have called the distribution boundary in section multi-paradigm, since it allows objects to reach out of their encapsulation borders. Class variables may also be employed in C++ and Java by defining data members as static. Apart from class variables, Smalltalk also supports the notion of class methods, which may be regarded as routines having the class and its instances as their scope. Class methods in Smalltalk are typically used for the creation and initialization of new instances of the class for which they are defined. In C++ and Java, creation and initialization is taken care of by the constructor(s) of a class, together with the (system supplied) new operator. Class methods, in C++ and Java, take the form of static member functions that are like ordinary functions (apart from their restricted scope and their calling syntax, which is of the form $class::member\left(...\right)$ in C++ and $class.member\left(...\right)$ in Java). Contrary to classes in C++ and Java, classes in Smalltalk have a functionality similar to that of objects. Classes in Smalltalk provide encapsulation (encompassing class variables and class methods) and message passing (for example for the creation and initialization of new instances). To account for this object-like behavior, the designers of Smalltalk have introduced the notion of metaclass of which a class is an instance.

Metaclasses

In the classical object model, two relations play a role when describing the architectural properties of a system. The first relation is the instance relation to indicate that an object O is an instance of a class C. The second (equally important) relation is the inheritance relation, which indicates that a class C is a subclass (or derived from) a given (ancestor) class P. When adopting the philosophy everything is an object together with the idea that each object is an instance of a class (as the developers of Smalltalk did), we evidently get into problems when we try to explain the nature (and existence) of a class.

To be an object, a class itself must be an instance of a class (which for convenience we will call a metaclass). Take, for example, the class Point. This class must be an instance of a (meta)class (say Class) which in its turn must be an instance of a (meta) class (say MetaClass), and so on. Clearly, following the instance relation leads to an infinite regress. Hence, we must postulate some system-defined MetaClass (at a certain level) from which to instantiate the (metaclasses of) actual classes such as Point.

slide: Meta architectures

The figure in slide meta-architecture(a) is a (more or less) accurate rendering of the solution provided by Smalltalk. We may add additional flexibility by allowing user-defined metaclasses that may refine the behavior of the system-defined metaclass Class. This is the solution chosen for Loops, see  [LOOPS]. Thus far we have traced the instance relation which leads (following the reversed arrows) from top to bottom, from metaclasses to actual object instances. As pictured in the diagram (a) above, the inheritance relation (followed in the same manner) goes in exactly the opposite direction, having the class Object at the root of the inheritance hierarchy. For example, the class Point (while being an instance of the metaclass Class) is derived by inheritance from the class Object. Similarly, the (meta)class Class itself inherits from the class Object, and in its turn the system-defined metaclass MetaClass inherits from Class. As for the user-defined metaclasses, these may be thought of as inheriting from the system-defined metaclass Class. Apart from being slightly confusing, Smalltalk's meta-architecture is rather inelegant due to the magic (that is system-defined) number of meta levels. In the following, we will study a means to overcome this inelegancy.

Reflection

[Cointe87] proposes an architecture that unifies the notions of object, class and metaclass, while allowing metaclasses to be defined at an arbitrary level. The key to this solution lies in the postulates characterizing the behavior of an object-oriented system given in slide 5-postulates.

Postulates -- class-based languages

• everything is an object
• every object belongs to a class
• every class inherits from the class Object
• class variables of an object are instance variables of its class

slide: Class-based languages -- postulates

The first three postulates are quite straightforward. They agree with the assumptions underlying Smalltalk. The last postulate, however, stating that a class variable of an object must be an instance variable of the objects class (taken as an object), imposes a constraint of a self-recurrent or reflexive nature. This recurrence is pictured in slide meta-architecture(b), which displays the object Class as an instance of itself (that is the class Class). In other respects, the diagram is similar to the diagram depicting the (meta) architecture of Smalltalk and Loops. To indicate how such a reflective relation may be implemented,  [Cointe87] introduces a representation of objects involving the attributes name (to indicate the class of the object), supers (to indicate its ancestor(s)), iv (to list the instance variables of the object) and methods (to store the methods belonging to the object). In this scheme of representation, the system-defined metaclass Class is precisely the object reflecting its own structure in the values of its attributes, as depicted above. Every instance of Class may assign values to its instance variables (contained in iv) that are appropriate to the instances that will be created from it. In general, a metaclass is an object having at least the attributes of Class (and possibly more). See slide 5-reflex-class.

Reflective definition of Class


name         Class
supers      (Object)
iv     (name supers iv methods)
methods      (new ...)


slide: A reflective definition of Class

Using this scheme, an arbitrary towering of metaclasses may be placed on top of concrete classes, thus allowing the software developer to squeeze out the last bit of differential programming. Elegant indeed, although it is doubtful whether many programmers will endeavor upon such a route. A nice example of employing (customized) metaclasses, however, is given in  [Malenfant89], where metaclasses are used to define the functionality of distribution and communication primitives employed by concrete classes.

Summary

This chapter presented an overview of object-oriented programming languages. We discussed the heritage of Simula and the various areas of research and development the ideas introduced by Simula has generated.

1

• notion of object -- viewpoints
• classification -- object extensions

slide: Section 5.1: The object paradigm

An overview of existing object-oriented languages was given in section 1 and we noted the prominence of hybrid languages derived from Lisp and C.

Comparing Smalltalk, Eiffel, C++ and Java

2

• criteria -- libraries, environments, language characteristics
• comparison -- language characteristics

slide: Section 5.2: Comparing Smalltalk, Eiffel, C++ and Java

In section 2, we looked at a comparison of Smalltalk, Eiffel, C++ and Java, including criteria such as the availability of libraries, programming environments and language characteristics.

Design dimensions of object-oriented languages

3

• object-oriented -- object-based + inheritance
• orthogonal dimensions -- objects, types, delegation, abstraction
• open systems -- dimensions of modularity

slide: Section 5.3: Design dimensions of object-oriented languages

In section 3, we discussed the design dimensions of object-oriented languages and characterized an orthogonal set of dimensions consisting of objects, types, delegation and abstraction. We also discussed the notion of open systems and multi-paradigm languages combining logic programming with object-oriented features.

Prototypes -- delegation versus inheritance

4

• prototypes -- cloning and delegation
• performance -- dynamic compilation

slide: Section 5.4: Prototypes -- delegation versus inheritance

In section 4, we dealt with classless prototype-based languages, supporting dynamic delegation instead of inheritance. We also discussed performance issues and observed that dynamic compilation based on runtime type information may achieve good results.

Meta-level architectures

5

• class -- the concept of class
• meta architecture -- subclass and instance hierarchy
• reflection -- postulates

slide: Section 5.5: Meta-level architectures

Finally, in section 5, we reflected on the concept of class and discussed a reflective architecture unifying the interpretation of a class as an object, capable of answering messages, and as a description of the properties of its instances.

Questions

1. What are the basic characteristics of object-oriented languages?
2. How would you classify object-oriented languages? Name a few representatives of each category.
3. What do you consider to be the major characteristic of the object model supported by C++? Explain.
4. Why would you need friends?
5. How would you characterize the difference between object-based and object-oriented?
6. Along what orthogonal dimensions would you design an object-oriented language? Explain.
7. Give a characterisation of active objects. In what situations may active objects be advantageous?
8. How would you characterize prototype-based languages?
9. What are the differences between inheritance and delegation? Does C++ support delegation? Explain. And Java?
10. How would you characterize the concept of a class?
11. Can you sketch the meta architecture of Smalltalk?
12. How would you phrase the postulates underlying class-based languages? Can you give a reflective version of these postulates?