Sie sind auf Seite 1von 223

Dynamic Object-Based Inheritance with Subtyping

Dissertation zur Erlangung des Doktorgrades der Mathematisch-Naturwissenschaftlichen Fakultt a der Rheinischen Friedrich-Wilhelms-Universitt Bonn a vorgelegt von G nter Kniesel u Universitt Bonn a Institut f r Informatik III u Rmerstr. 164, D-53117 Bonn o gk@cs.uni-bonn.de

Bonn 2000

Angefertigt mit Genehmigung der Mathematisch-Naturwissenschaftlichen Fakultt der Rheinischen Friedrich-Wilhelms-Universitt Bonn a a 1. Referent: Univ.-Prof. Dr. Armin B. Cremers 2. Referent: Univ.-Prof. Dr. Rainer Manthey Tag der Promotion: 7.7.2000

To Ulla, my wife and best friend. For your love, support and understanding.

Preface
Abstract
A survey of current object-oriented languages and systems would reveal an apparent dichotomy between class-based systems using inheritance and instantiation and prototype-based systems using delegation and cloning. These mechanisms dier signicantly in their range of applicability, expressive power, exibility, robustness to change, and in the safety guarantees they provide to the programmer. Their strengths are widely complementary. Each of them is required in dierent application domains and at dierent stages of application development so that their integration into one system appears worthwhile. This thesis develops a unied model for object-oriented languages, Darwin1 , which combines the two classic models referred to above. A common key aspect of both models is dynamic binding, the process that determines the code to be executed in response to a message. Darwin denes a unied dynamic binding process that guarantees that dynamic delegation can be safely used in a system based on static typing and subtyping. The unied scheme is equally applicable to prototype- and class-based systems. Applied to a prototype-based system it yields a model of statically typed, dynamically delegating prototypes. Applied to a class-based system it yields a model that integrates static typing, classes, instantiation, inheritance and dynamic delegation. Both models are novel and unique. However, the extended class-based model is expected to have most practical impact, since it combines two essential, complementary strengths. It oers the exibility of object-specic behaviour and of dynamic behaviour changes typical of delegation, plus the guarantees of uniform structure and compatible behaviour for groups of objects, typical of instantiation and strong typing. The interaction of delegation with typing is discussed thoroughly and its implications for dynamic binding are worked out. We bring our model down to earth by introducing Lava, an extension of Java with type-safe static and dynamic delegation and by presenting a portable and ecient implementation of Lava. Finally, Darwin and Lava are compared to related work. To our knowledge Darwin is the rst model for a typed language with dynamic delegation. Moreover, it is the rst model that explains the semantics of dynamic delegation
Charles Darwin (1809-1882) formulated the theory of the evolution of species by mutation and natural selection. Hopefully, the Darwin model will contribute to the evolution of two disparate species of object-oriented languages to one that will be better adapted to the struggle for survival in the computer industry. Time will show whether Darwins rule about the survival of the ttest will work against or in favour of our model.
1 Sir

6 directly, rather than using simulations via method update or object extension. Thus its operational semantics is closer to the dynamic binding semantics of existing delegation-based languages and can contribute better to their understanding. Last but not least, Darwin appears to be the rst statically typed object model that can express non-monotonic object evolution, i.e. scenarios in which objects successively acquire new methods and variables and abandon (some of) them again later on. For instance, Darwin can model such typical behaviour evolution scenarios as the state and the strategy pattern. In contrast, even the most powerful competing proposals are limited to object specialisation, i.e. to cases where every new state or strategy taken on by an object is a specialisation of the previous one. This is almost never the case in real life. The ability of Darwin to cope easily with many critical problems of objectoriented modelling makes it a good candidate for a universal model, which bridges the gap between inheritance-based and delegation-based languages and oers a basis for the semantic study and implementation of more abstract concepts.

Acknowledgements
The Darwin model has evolved over years and has beneted from discussions with many people who helped to shape the ideas reported here. First, I want to thank my advisor, Armin Cremers, for continuous support, encouragement and for providing an environment in which this research could evolve. Matthias Schickel and Pascal Costanza implemented the rst version of Lava (Lava 0.5) and contributed substantially to its design. Matthias ability to pinpoint inconsistencies and omissions in my early work and in many intermediate versions, which he continued to proof-read even after the end of his studies, has been an invaluable help along the way. Pascals repeated questioning of my ideas and suggestion of alternative proposals has been a permanent challenge that turned out to be an excellent quality assurance program. Andre Sielski performed the rst evaluation of Lava 0.5 by implementing a model of objects with roles in Lava and in pure Java. The results of his work have been an important input for the pure Java implementation of Lava developed in this thesis. Wolfgang Reddig has shared with me his detailed knowledge of C++ and Eiel. Discussing with him our dierent approaches to objects that can be viewed from multiple perspectives has always been a challenge and a pleasure. Eric Ernst volunteered to read essential parts of a draft of my thesis and provided very expert, constructive and honest criticism. I am also indebted to Oliver Stiemerling, Klaus Ostermann and Michael Austermann for helpful feedback to various drafts of this work. The discussion of my ideas with Luigi Liquori and Kathleen Fisher during ECOOP 98 helped to clarify the relation of our dierent approaches. This thesis would never have started without the inspiration from the seminal work of Henry Lieberman, David Ungar, Randall Smith and the whole Self team. They changed my view of programing languages in a fundamental way.

Contents
1 Introduction 3

State of the Art


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7
9 9 14 14 14 14 15 17 23 23 25 26 27 27 27 28 28 30 30 31 31 34 37 39 40 40 41 48 49

2 Basic Concepts 2.1 Common Basis Communicating Objects . . . . 2.2 Prototype-based Systems . . . . . . . . . . . . . 2.2.1 Ex-nihilo creation . . . . . . . . . . . . 2.2.2 Property Addition and Deletion . . . . . 2.2.3 Cloning . . . . . . . . . . . . . . . . . . . 2.2.4 Delegation, Consultation, and Resending 2.2.5 Typing . . . . . . . . . . . . . . . . . . . 2.3 Class-Based Systems . . . . . . . . . . . . . . . . 2.3.1 Instantiation . . . . . . . . . . . . . . . . 2.3.2 Inheritance . . . . . . . . . . . . . . . . . 2.4 Summary . . . . . . . . . . . . . . . . . . . . . .

3 Behaviour Evolution 3.1 Behaviour Evolution, Sharing and Reuse . . . . . . . . . . 3.1.1 Sharing . . . . . . . . . . . . . . . . . . . . . . . . 3.1.2 Unanticipated Reuse . . . . . . . . . . . . . . . . . 3.1.3 Behaviour Evolution . . . . . . . . . . . . . . . . . 3.2 Scenarios for Behaviour Evolution . . . . . . . . . . . . . 3.3 Requirements for Behaviour Evolution . . . . . . . . . . . 3.4 Behaviour Change in Prototype-Based Languages . . . . . 3.4.1 Object Structure Modication . . . . . . . . . . . 3.4.2 Delegation . . . . . . . . . . . . . . . . . . . . . . . 3.4.3 Summary Prototype-based Behaviour Evolution 3.5 Behaviour Evolution in Class-Based Languages . . . . . . 3.6 Simulation of Delegation . . . . . . . . . . . . . . . . . . . 3.6.1 Requirements for a Simulation . . . . . . . . . . . 3.6.2 Passing self as Message Argument . . . . . . . . . 3.6.3 Summary . . . . . . . . . . . . . . . . . . . . . . . 3.7 Chapter Summary . . . . . . . . . . . . . . . . . . . . . . i

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

ii

CONTENTS

II

Darwin
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

53
55 55 55 56 56 57 58 58 60 60 61 62 63 63 64 65 66 67 68 69 70 70 71 73 75 76 79 79 80 81 81 84 85 86 89 90 92 93 93 94 94 95 99 100 100 102 105

4 Static Delegation with Subtyping 4.1 Part and Chapter Overview . . . . . . . . . . . . 4.2 Classes and Prototypes in Darwin . . . . . . . . 4.3 Darwin as a Class-Based Object Model . . . . . 4.4 Delegation in Darwin . . . . . . . . . . . . . . . 4.4.1 Split Objects . . . . . . . . . . . . . . . . 4.4.2 Mandatory Attributes . . . . . . . . . . . 4.5 Graphical Notation . . . . . . . . . . . . . . . . . 4.6 Type System . . . . . . . . . . . . . . . . . . . . 4.6.1 Interface Types . . . . . . . . . . . . . . . 4.6.2 Typing of Delegation . . . . . . . . . . . . 4.6.3 Subtyping . . . . . . . . . . . . . . . . . . 4.6.4 Declared Subtyping . . . . . . . . . . . . 4.6.5 Atomic instances . . . . . . . . . . . . . . 4.6.6 Type-Safety . . . . . . . . . . . . . . . . . 4.7 Safe Forwarding . . . . . . . . . . . . . . . . . . . 4.8 Incomplete Objects . . . . . . . . . . . . . . . . . 4.8.1 Forwarding Cycles due to Subtyping . . . 4.8.2 Forwarding Cycles in the Program Graph 4.8.3 Cycle Summary . . . . . . . . . . . . . . . 4.9 Delegation Children Must be Subtypes . . . . . . 4.10 Complete Objects in Unsafe Contexts . . . . . . 4.11 Static Delegation with Subtyping . . . . . . . . . 4.11.1 Extended Dynamic Binding . . . . . . . . 4.11.2 Static delegation is type-safe . . . . . . . 4.12 Independent Extensibility . . . . . . . . . . . . . 4.12.1 Dynamic Binding . . . . . . . . . . . . . . 4.13 Delegation Children Must be Subtypes . . . . . . 4.14 Summary . . . . . . . . . . . . . . . . . . . . . . 5 Dynamic Delegation with Subtyping 5.1 Unsafety of Dynamic Delegation with Subtyping 5.2 Pure Specialisation . . . . . . . . . . . . . . . . . 5.3 Accessing Frozen Object State . . . . . . . . . . 5.3.1 Partially Frozen Object State . . . . . . . 5.3.2 Type-Safety is not enough . . . . . . . . . 5.3.3 Fully Frozen Object State . . . . . . . . . 5.3.4 Freezing Summary . . . . . . . . . . . . . 5.4 Waiting for Future Safe State . . . . . . . . . . . 5.5 Invariant split self . . . . . . . . . . . . . . . . . 5.5.1 Invariant self . . . . . . . . . . . . . . . . 5.5.2 Split self . . . . . . . . . . . . . . . . . . 5.5.3 Anticipated parent types . . . . . . . . . 5.5.4 Typing of receiver in child classes . . . . . 5.5.5 Transitive Dynamic Delegation . . . . . . 5.5.6 Observations . . . . . . . . . . . . . . . . 5.5.7 Dynamic type checking . . . . . . . . . . 5.5.8 Summary . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

CONTENTS 5.6

iii

Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . 107

III

Lava
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

109
111 111 113 113 114 115 116 117 121 122 124 125 125 127 130 130 131 131 132 132 133 134 135 135 136 136 137 137 139 141 141 150 153 153 153 156 157 157 158 159 159 160 160 161

6 Lava The Language 6.1 Lava Overview . . . . . . . . 6.2 Terminology . . . . . . . . . . 6.3 Syntax . . . . . . . . . . . . . 6.4 Mandatory elds . . . . . . . 6.4.1 Discussion . . . . . . . 6.5 Parent elds . . . . . . . . . . 6.5.1 Discussion . . . . . . . 6.6 Explicit delegation . . . . . . 6.7 Anticipated parent types . . . 6.7.1 Discussion . . . . . . . 6.8 Atomic types . . . . . . . . . 6.9 Subtyping . . . . . . . . . . . 6.9.1 Discussion . . . . . . . 6.10 Parent Cast . . . . . . . . . . 6.11 Overriding . . . . . . . . . . . 6.11.1 Discussion . . . . . . . 6.12 Final classes and methods . . 6.12.1 Discussion . . . . . . . 6.13 Simple Assignment Operator 6.14 Exceptions . . . . . . . . . . 6.15 Summary . . . . . . . . . . .

7 Implementation 7.1 Terminology and Notation . . . . . . . . . . . . 7.2 Basic Delegation Scheme . . . . . . . . . . . . . 7.2.1 Extended Method Format . . . . . . . . 7.2.2 Bridge Methods . . . . . . . . . . . . . . 7.2.3 Accessor Methods . . . . . . . . . . . . 7.2.4 Mandatory elds . . . . . . . . . . . . . 7.2.5 Atomic Instances . . . . . . . . . . . . . 7.2.6 Delegatee Fields . . . . . . . . . . . . . 7.2.7 Summary of basic scheme . . . . . . . . 7.3 Typing of receiver . . . . . . . . . . . . . . . 7.3.1 Anticipated delegation . . . . . . . . . . 7.3.2 Unanticipated delegation . . . . . . . . 7.3.3 Parent cast . . . . . . . . . . . . . . . . 7.4 Evaluation . . . . . . . . . . . . . . . . . . . . . 7.4.1 Anticipated delegation is ecient . . . . 7.4.2 Unanticipated delegation is dicult . . 7.5 Optimization of unanticipated delegation . . . 7.5.1 Reduced use of adapters . . . . . . . . . 7.5.2 Lazy, non-redundant adapter creation . 7.5.3 Lazy, non-redundant parent casts . . . . 7.5.4 Non-redundant parent casts in adapters

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

CONTENTS

7.6

7.5.5 Customization . . . . . . . . . . . . . . . . . . . . . . . . 162 Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . 162 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 165 167 169 170 172 174 175 176 178

8 Applications 8.1 Language Support for Good Design . . . . . . 8.2 The Decorator Pattern . . . . . . . . . . . . . 8.3 DCA: Dynamic Component Adaptation . . . 8.3.1 State of the Art . . . . . . . . . . . . 8.3.2 Wrapper-Based Euro Transition. . . . 8.3.3 Additive and disjunctive composition . 8.3.4 Dynamic Component Re-Wiring . . . 8.4 The Strategy Pattern . . . . . . . . . . . . . . 8.5 Chapter Summary . . . . . . . . . . . . . . .

IV

Related Work and Conclusions


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

179
181 181 182 183 183 185 186 186 189

9 Related Work 9.1 Design Patterns . . . . . . . . . . . . . . . . . . . . 9.2 Dynamically Typed Languages . . . . . . . . . . . 9.3 Statically Typed Languages . . . . . . . . . . . . . 9.3.1 Statically Typed Prototype-based Systems . 9.3.2 Statically Typed Mixins . . . . . . . . . . . 9.4 Extra-language Approaches . . . . . . . . . . . . . 9.5 Chapter Summary . . . . . . . . . . . . . . . . . . 10 Conclusions

ii

CONTENTS

List of Figures
2.1 2.2 2.3 2.4 2.5 3.1 3.2 3.3 3.4 3.5 3.6 3.7 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 7.1 Weak state notion . . . . . . . . . . . . . . . . . . . . . . . . . . Strong state notion . . . . . . . . . . . . . . . . . . . . . . . . . . Cloning in prototype-based languages . . . . . . . . . . . . . . . Dierent eects of delegation and consultation on self . . . . . . Dierent result of the same message for delegation and consultation Class-based modelling of anticipated property addition, deletion, and update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Class-based modelling of unanticipated property addition, deletion, and update . . . . . . . . . . . . . . . . . . . . . . . . . . . Simulation of delegation by passing self as message argument . . Typing of the self parameter (rst approximation). . . . . . . . . Method overriding in child classes . . . . . . . . . . . . . . . . . . Overriding in subclasses of parent classes . . . . . . . . . . . . . Program extension that invalidates existing classes . . . . . . . . Extended UML class diagram notation for delegation and consultation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Notation for object collaboration diagrams . . . . . . . . . . . . . Split objects and their dening program graph . . . . . . . . . . Incomplete cyclic object due to subtyping . . . . . . . . . . . . . An illegal cyclic program . . . . . . . . . . . . . . . . . . . . . . . Unsafety of delegation to subtype instances . . . . . . . . . . . . Propagation of unsafe values throughout the program . . . . . . Safe completion of the example from Figure 4.6 on page 72 . . . Semantic applicability and overriding . . . . . . . . . . . . . . . . 11 11 15 16 16

39 40 42 43 44 45 46

58 59 59 67 69 72 73 73 78

Unsafety of dynamic delegation with subtyping: subclasses . . . 82 Unsafety of dynamic delegation with subtyping: children . . . . . 83 The strategy pattern [GHJV95] with dynamic delegation . . . . . 84 A version of the strategy pattern in which semantic subtyping between sibling classes fails . . . . . . . . . . . . . . . . . . . . . 85 Frozen path approach . . . . . . . . . . . . . . . . . . . . . . . . 87 Inconsistency due to freezing . . . . . . . . . . . . . . . . . . . . 91 Safe dynamic delegation with invariant split self . . . . . . . . . 98 Transitive dynamic delegation with invariant split self . . . . . . 101 A scenario where the parent cast is useful . . . . . . . . . . . . . 104 Adapters for type-safe passing of child objects to external code . 150 iii

LIST OF FIGURES 8.1 8.2 8.3 8.4 8.5 Collaboration diagrams for generic object evolution patterns . . . Delegation supports good design practices . . . . . . . . . . . . . Simultaneous use of the original DM component and of its adapted DMtoEuro version by dierent clients . . . . . . . . . . . . . . . . Additive and disjunctive composition . . . . . . . . . . . . . . . . Component wiring before extension (a) and after extension (b) .

i 166 167 174 174 175

ii

LIST OF FIGURES

List of Tables
2.1 2.2 3.1 3.2 5.1 5.2 6.1 7.1 7.2 7.3 7.4 7.5 7.6 7.7 Variants of forwarding . . . . . . . . . . . . . . . . . . . . . . . . Basic concepts of class- and prototype-based systems . . . . . . . Sharing mechanisms of prototype- and class-based languages . . Object structure modication versus delegation . . . . . . . . . . 17 26 29 38

Dierence of casting to normal types versus atomic instances . 105 Comparison of approaches to type-safe delegation with subtyping 107 Darwin to Lava terminology translation . . . . . . . . . . . . . 113 Implementation of delegation . . . . . . . . Local and external eld access . . . . . . . . Local accessor methods . . . . . . . . . . . Check of assignment to mandatory eld var Visibility of translated methods and elds . Forwarded eld access . . . . . . . . . . . . Forwarding accessor methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 138 139 139 146 147 148

ii

LIST OF TABLES

List of Listings
2.1 3.1 3.2 3.3 3.4 3.5 5.1 5.2 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 8.1 8.2 8.3 8.4 8.5 8.6 The code from (2.1) and (2.2) in Java-style pseudo-syntax. . . . . 20 Property addition and deletion via object structure modication. 32 Property update via object structure modication. . . . . . . . . 33 Locally anticipated property addition and deletion via delegation. 34 Fully unanticipated property addition and deletion via delegation. 36 Locally anticipated property update via delegation. . . . . . . . 37 Safe dynamic delegation with invariant split self and atomic instances Lava code for example from Figure 5.7 . . . . . . . . . 97 Use of parent cast . . . . . . . . . . . . . . . . . . . . . . . . . 104 Checking for atomic instances source code . . . . . . . . . . . . 142 Checking for atomic instances translated code . . . . . . . . . . 142 Example source code . . . . . . . . . . . . . . . . . . . . . . . . . 143 Implementation of subtyping . . . . . . . . . . . . . . . . . . . . 149 Bridge methods that return a result . . . . . . . . . . . . . . . . 150 Compiled code example: child class . . . . . . . . . . . . . . . . . 151 Compiled code example: declared parent class . . . . . . . . . . . 152 Example: lazy, non-redundant parent casts . . . . . . . . . . . . 160 The decorator pattern with consultation . . . . . . . . . . . . . . 168 The decorator pattern with delegation . . . . . . . . . . . . . . . 169 The Euro scenario: The component to be adapted . . . . . . . . 172 The Euro scenario: Traditional wrapper-based adaptation fails . 173 The Euro scenario: Delegation-based adaptation succeeds . . . . 173 The strategy pattern in Lava . . . . . . . . . . . . . . . . . . . . 177

LIST OF LISTINGS

Chapter 1

Introduction
Behaviour evolution. Change and diversity are intrinsic to the real world, which continually evolves in ways that cannot be anticipated. For instance, the ability to look at the same thing from dierent perspectives 1 or to interact with an evolving entity independently of roles 2 taken on throughout a lifecycle is evident in the behaviour of objects or persons. In general, behaviour is not xed but may be selected from a range of potential responses to external events: a chess player can play according to dierent strategies, and a brake can react dierently depending on the road surface, the state of the tires, etc. Strategies as well as the parameters that inuence the behaviour of a brake can evolve. Unanticipated reuse. Often the requirements that a system must meet can change in fully unanticipated ways due to a variety of possible factors ranging from company-internal decisions to new legislation. Unanticipated requirement changes lead to unanticipated behaviour evolution. For instance, the transition from national currencies to the Euro required signicant unanticipated changes of the software deployed by West-European companies. Such changes must be equally well supported by programming languages and tools as changes that can possibly be anticipated. Nevertheless, current object oriented languages and methods still fall short of allowing unanticipated reuse. Modeling of behaviour evolution is mostly inhibited by the inability to reuse and evolve existing software in unanticipated ways. Class-based languages. This holds in particular for statically typed, classbased languages. Being able to statically enforce invariants (e.g. immutable structure of instances, type conformance) and hence rule out many common errors during compile-time, they are well-suited and widely-deployed for production programming. However, their focus on static concepts (static typing, static inheritance) restricts their use to well-structured application domains, where patterns of
instance, dierent engineers might be interested in dierent aspects of the same steel bar: aspects that inuence its load-bearing-capacity, aspects that inuence its tting with other parts of a construction, nancial aspects, or logistic aspects. 2 For instance, being a child, student, employee, husband, father or patient are various roles of a person.
1 For

CHAPTER 1. INTRODUCTION

sharing and reuse can be anticipated and do not change at run time. They cannot easily express changes in the structure or behaviour of an object.

Design patterns. In spite of the inherent limitations of the class based model there are ways to express the variety and dynamics of the real world with class based programs. Various authors have condensed best practice class-based solutions to recurring problems in object-oriented design into language specic idioms [Cop92, Mey92b, Mey96] and language independent design patterns [GHJV95, CS95, VCK95, Sch98]. Whereas application-specic patterns are a very eective approach to reuse of design knowledge, simulations of missing language concepts via patterns cut both ways: they burden the application programmer, create additional opportunities for programming errors, clutter the application code by tightly intermixing it with simulation code, and most importantly, contradict reuse. Whereas patterns themselves are reusable, the programs resulting from the use of design patterns often are not. Because the functionality missing from the language is simulated by a set of cooperating classes, these classes tend to be tightly dependent on each other in ways that are not grounded in the application but just in the technical details of the pattern. Such additional dependencies and assumptions built into the design often require consistent changes in a complete hierarchy of classes even for small changes in the application logic. Thus the application of design patterns can impede reuse (of existing code and designs) and complicate program maintenance, achieving the opposite of what is widely regarded as a main benet of object oriented programming. Therefore, design patterns are a possible workaround for the limitations of the class-based model but not a satisfactory solution.

Model extensions. This insight has led to a variety of proposals for extensions of the class-based model. Specic extensions for objects that can be regarded from dierent perspectives at the same time [VC93, WdJ91, Pap91, KLRSR95, GSR96, WJS94], objects that change their roles dynamically [Per89, Per90b, Per90a, RS91, ABGO93, Kni96b], or objects whose evolution history can be traced across multiple versions [Zdo86, HZ90, Kat90, MS92, Mal91, BM93, TOC93, SG91] are just some of the most hotly debated examples. However, each of these extensions is helpful only in a restricted class of applications and it is still unclear whether and how they could be combined. Since the classbased model has already been criticised as being too complex [US87], it is to be feared that its extension by a variety of special purpose concepts would render it completely incomprehensible and unmanageable. Designers and programmers would have to learn a multitude of concepts just to be able to decide which might really be needed in a certain application domain.

Minimality. Therefore, it appears desirable to develop a model that sets up a minimum kernel on which the mentioned extensions can easily be mapped. Easily means that the mapping must not create class interdependencies that are not grounded in the application semantics. This requirement is one of the lessons learned from class-based design patterns.

5 Prototype-based languages. An exemplary minimal kernel for object oriented programming that oers maximum expressiveness is already available in so-called prototype-based languages [LaL86, LTP86, Lie86, US87, SU95, Smi95, Cha93a, Tai96]. Prototype-based languages focus on working with self-contained, concrete objects instead of abstract classes. They give up the notion of class and replace static, class-based inheritance with dynamic, object-based inheritance, also known as delegation. Prototype- and delegation-based languages can directly express changes of structure and behaviour: objects can dynamically change their structure, their methods and the objects to which they delegate. However, the advantages of easy modelling of change are outweighed by the necessity of simulating explicitly more abstract concepts. Types, classes, instantiation, and inheritance are simulated either by special development environments [SLS+ 94] or as part of application programs, using special design patterns [CUCH91a, CUCH91b]. Unfortunately, the conceptual gap is so big, that easy mappings cannot be found. Whereas the complexity of the language decreases, the number and complexity of design patterns that are to be mastered by a programmer increases. Further reasons that have prevented the wide-spread use of prototype-based languages are their dependency on special development environments, special implementation techniques, and their missing static type concept. Aim. The aim of this work is to nd an appropriate balance between the simplicity and exibility of prototype-based systems and the high abstraction level and rigidity of class-based systems. The motto is evolution instead of revolution. Rather than looking for an absolutely minimal basis for object oriented programming this thesis seeks a minimal extension to the class-based model that allows the dynamic nature of the world to be expressed without compromising reuse. Approach. Our approach is the integration of dynamic delegation known from prototype-based languages into the statically typed class-based model. Due to dynamic delegation, Darwin allows for easy modelling of dynamic changes of structure and behaviour. Nevertheless, it extends the well-known class-based model by a single concept and can therefore be easily learned and employed. Challenges. Although it may look simple at rst glance, the integration strived for presents several conceptual challenges: 1. First of all it has to be shown that dynamic delegation is compatible with a static type system with subtyping. The opposite is claimed in literature on this subject [FM94, FM95, AC96]. 2. Secondly, the gain in expressiveness and reuse provided by Darwin has to be demonstrated on some application domains that have been dicult to model with traditional class-based languages. 3. Thirdly, ecient implementation techniques for dynamic delegation are needed, to make the model practical for large-scale production programming. At present, even Self, the system that oers the most advanced implementation and optimisation techniques for prototype-based languages,

CHAPTER 1. INTRODUCTION implements dynamic delegation by sequential search of method dictionaries at run-time.

Overview. The work is divided into four parts. The rst part summarises basic notions of object-oriented programming (Chapter 2) discusses the intimate relationship between behaviour modication, reuse and sharing mechanisms and reviews previous work on behaviour modication in class- and prototype-based languages (Chapter 3). The second part, presents the Darwin model. It starts with an introduction of the basic aspects of Darwin and a discussion of type-safe static delegation in Chapter 4. Chapter 5 proposes and compares dierent approaches to safe dynamic delegation with subtyping. The third part introduces an extension of Java that conforms to Darwin. It presents the design (Chapter 6) and implementation (Chapter 7) of the language Lava. The fourth part compares Darwin and Lava to related work (Chapter 9), and concludes (Chapter 10).

Part I

State of the Art

Chapter 2

Basic Concepts
The ill and unt choice of words wonderfully obstructs the understanding. Francis Bacon (1561 - 1626) In this chapter we give an overview of basic object-oriented concepts. Its purpose is to provide the terminological basis for the rest of this thesis. It is neither meant to replace a thorough introduction to object-oriented analysis, design and programming as given e.g. in [Mey88, JCJO92, JBR99] nor to give an overview of all the complex and often conicting terminology that has emerged during 30 years of object-oriented programming. Readers interested in such an overview may consult [FE95]. We only summarise the common concepts underlying class-based systems and discuss in more detail notions typical of prototype-based systems, which are less well-known and tend to be overloaded with dierent meanings in dierent communities. Readers familiar with class and prototype-based systems may skip this chapter and consult it as a reference, when they feel that the use of some terminology in the sequel does not match their understanding. Alternatively, they might want to read the section on typing of prototype-based systems before proceeding to the next chapter.

2.1

Common Basis Communicating Objects

In this section we review the basic concepts which are common to both, classbased and prototype-based systems: objects, encapsulation, identity, state, message passing, implicit sharing via self and types. Whereas objects with identity and state that communicate via messages are common to all object-oriented languages, the support for encapsulation and types may vary greatly from language to language and certain languages do not support them at all1. Nevertheless, encapsulation and typing may well be considered part of the common basis of object-oriented programming in that both are present in many class-based and prototype-based languages.
1 E.g. the rst object-oriented language, Simula, supports static typing but no encapsulation; on the other hand Smalltalk invented strong object-based encapsulation but discarded typing. The untyped prototype-based language Self has seen dierent versions, with and without encapsulation [ABC+ 93, CUCH91b, SU95].

10

CHAPTER 2. BASIC CONCEPTS

Objects. An object is a set of variables and a set of operations (usually called methods) that can be used to manipulate these variables. Both, variables and methods, are called properties. Encapsulation. Most object-oriented systems support some notion of encapsulation, which hides some of the properties of an object from other objects by dening a client interface which species all the properties that are visible to other objects. The client interface of an object is also known as its public interface in C++ and Java. Identity. An object identity or object identier [WdJ95, KC86] is a unique, system-managed, time-, location- and state-independent value that identies exactly one object during its whole life-time and beyond. In garbage-collected, non-distributed and non-persistent object systems a trivial implementation of object identiers as main memory addresses suces. In other cases quite sophisticated implementation mechanisms are required [WdJ95]. Values. The value of a method is the method implementation. The value stored in a variable may be either an atom, i.e. any entity that is not an object (e.g. 1, 3.4, true, null in Java), an object (e.g. embedded objects in Eiel) or an object identier. Values that are object identiers are also called reference values. Atoms and embedded objects are also called non-reference values. Many authors consider non-reference values to be non-object-oriented. However, no practical language can fully dispense with atomic values. Languages that strive for a pure object-oriented model try hard to treat atoms like references (e.g. Smalltalk and Self). Nevertheless, the distinction between objects and atoms still remains visible to the user, e.g. in the dierent eect of message sending after cloning (see 2.2.3). A common approach to make atomic values appear to be objects is to construct wrapper objects, which encapsulate the atomic values. E.g. the standard Java class library contains one wrapper class for every atomic object type. State. At any time an object is in a certain state, which determines its behaviour. There are two dierent, inconsistent notions of state that can be found in literature: 1. The weak state notion denes the state of an object to be the tuple of its property values ([FE95, page 416]). 2. The strong state notion denes the state of an object to be the tuple of its property values together with the state of all objects referenced by its variables [HLW+ 92, FE95, IWA99].

2.1. COMMON BASIS COMMUNICATING OBJECTS


= state of an object address

11

name municipalAuthorities street string

Figure 2.1: Weak state notion: The state of an object comprises only own property values. Object states are disjoint.

In the rst interpretation, object states are disjoint, and a change in one objects state does not change the state of other objects (Figure 2.1). This is counterintuitive, since changing the name of a street implicitly changes all addresses that refer to that street. In the second interpretation object states overlap (Figure 2.2) and sharing of objects has a natural interpretation as sharing of state. We use the strong state notion. The term local state is used to denote the part of the state tuple that corresponds to the own variable values of an object and transitive state to denote the part of the state tuple that corresponds to the states of referenced objects. Implicit sharing. Almost all object-oriented systems2 support some form of implicit sharing by which one object may use properties that are part of another entity as if they where its own: implicit sharing makes properties in the transitive state of an object appear to be part of its local state. This eect is achieved by delegation in prototype-based systems (2.2.4) and by instantiation in class-based systems (2.3.1): Delegation lets all the properties of a parent object appear to be part of its children; instantiation lets the instance methods and the class variables dened in a class appear to be part of its instances. Whereas the two techniques signicantly dier in many important aspects, they share a common basis: the notion of self . Self. Implicit sharing requires in particular that methods in shared entities be executed on behalf of dierent objects, which were not yet known at the
2 The

only exception seems to be Kevo [Tai95, Tai96].

= state of an object address

name municipalAuthorities street string

Figure 2.2: Strong state notion: The state of an object includes the states of all referenced objects: object states are shared.

12

CHAPTER 2. BASIC CONCEPTS

time when the methods were written. Therefore, the pseudovariable self 3 is used to refer to the object on whose behalf a method is being executed. In most systems4 self is a system-managed, implicit parameter of every method. Binding of self can happen either when a message is sent to an object (then self is bound to the receiving object) or when objects are created (then self is bound to the created object). The rst scheme is known as the self-application semantics of objects [Kam88, KR94, AC96], and underlies the implementation of all single-dispatch objectoriented languages. The second scheme is known as the recursive record semantics [AC96, Car88, KR94, Red88]. The recursive record semantics is more restricted, because it depends on the assumption that a particular instance will never act on behalf of another one. This assumption is incompatible with delegation (e.g. [SdM95]. Therefore, we consider that late binding of self happens as part of message sending, not as part of object creation (i.e. we adopt the self -application semantics). Messages. Messages are the unit of communication between objects. A message consists of a receiver expression, a name and argument values. Its eect is the invocation of a method in the object resulting from the evaluation of the receiver expression5 . As a notational convenience, many languages consider self to be the implicit message receiver if no receiver expression is given. In the following, messages whose implicit or explicit receiver is self are called implicit messages. Messages to other receivers are called explicit messages. The invoked method of the receiver object is the most specic one that is applicable to the message. In order to be applicable, a method must have (at least) the same name and number of arguments as the message to which it is bound. Typed systems additionally require that the types of actual arguments be subtypes of the methods formal argument types. In typed systems that support overloading and in systems based on multiple dispatching there may be dierent applicable methods. Then the most specic one is the one whose formal argument types are subtypes of the respective formal argument types of every other applicable method. Dynamic binding. Unlike a procedure call a message may possibly be bound at run-time, i.e. the code to be executed is determined dynamically, depending on the object which received the message. If the receiver object does not contain an applicable method a run-time error (message not understood) occurs. To ensure absence of such errors is the main purpose of object-oriented type systems.
called this (in Simula, Java and C++) and current (in Eiel). are exceptions: Component Pascal, for instance, makes self an explicit parameter for whose correct setting the programmer is responsible. However, it is an open debate, whether languages that require explicit management of self by the programmer may be considered object-oriented. 5 In systems based on multiple dispatching or multi-methods (e.g. CLOS, Cecil) messages do not have a unique receiver object. We do not include multiple dispatching systems in our discussion.
4 There 3 Also

2.1. COMMON BASIS COMMUNICATING OBJECTS

13

Types. Types are invariants that constrain the range of values that can be stored in variables, passed as parameters and returned as method results in any state of an object [PS94]. A type system is a set of rules that allow to infer types for every expression within a program and to check whether expressions are used in a type-consistent way, i.e. in a way that does not violate the constraints imposed by their types. The purpose of static type-checking in object-oriented languages is to ensure at compile-time that message not understood errors will not occur at run-time. The invariants employed as types by dierent type systems vary widely. In this work we will concentrate on interface types and class types, which are supported by many practical languages and theoretical type systems6 . A class type constrains values to be references to objects that are instances of a certain class or of its subclasses [PS94]. An interface type constrains values to be references to objects that have a certain interface (regardless of their class). An interface is a set of signatures. The signature of a variable is its name and type. The signature of a method is its name, its argument types and its result type. Subtypes. The particular power of object-oriented type systems is their notion of subtyping. An expression of a subtype may safely be used in any place where an expression of a supertype is expected. For instance, a variable of a certain type may contain references to objects of dierent subtypes, which provide dierent implementations for the same methods. In conjunction with dynamic binding subtyping lets the same message have dierent eects at different stages of program execution, depending on the dierent receiver objects. Thus subtyping preserves the benets of dynamic binding even in statically typed systems. There is a rich body of literature on type systems for class-based languages [CW85, DT88, GO91, PS92, PS94, PT94, Nie95, BC96, BK96, Bru96, BCC+ 96, GM96, Pun96, Sha95]. Type systems for prototype-based languages without delegation have also been proposed, e.g. by [AC96, Bla91, Bla94, FM94, FM95, BL95, Liq96, Liq97, RS98]. The only typed system with delegation is Cecil [Cha92, Cha93a, CL94]. These systems will be reviewed in section 2.2.5 (page 17 22). Whats missing. The concepts discussed so far do not constitute a fully operational object-oriented language. We have not yet mentioned how objects are created, and how object communities are structured. Prototype-based languages and class-based languages have dierent solutions for these two aspects, which account for their very dierent overall character. In the Treaty of Orlando [SLU89, LSU88] it was recognised early that, in spite of their contradicting philosophy, both language families provide solutions to the same basic forms of sharing that are essential to every object-oriented system: sharing by creating new objects from existing templates, and
types are supported by Simula, Beta, C++, Eiel and Java, just to name a few. Interface types are present in Java, Sather, and most theoretical models.
6 Class

14

CHAPTER 2. BASIC CONCEPTS sharing by letting existing objects use the abilities of other objects as if they where their own (empathy).

In the remainder of this chapter we shall discuss the basic mechanisms that implement template and empathy functionality. Their relation to sharing will be discussed in the next chapter.

2.2
2.2.1

Prototype-based Systems
Ex-nihilo creation

In the prototype-based languages Delegation [Lie86], Moostrap [MC93], and Kevo [Tai95, Tai96], no template mechanism is provided at all. A new object with a given structure is obtained by creating an empty object (ex nihilo creation [DMC92]) which must be manually lled with the desired attributes.

2.2.2

Property Addition and Deletion

Ex-nihilo creation is always accompanied by the ability to add and remove object properties dynamically. Taken together ex nihilo creation and dynamic property addition and deletion form a minimal core on which more convenient object manipulation for prototype-based languages can be built [MC93]. For example, cloning and property update can both be encoded.

2.2.3

Cloning

The simplest form of templates is implemented in prototype-based languages like Self [US87, SU95], NewtonScript [Smi95], and Cecil [Cha93a]. In Self an object, called a clone, can be created as a copy of an existing object, called a prototype. This is achieved by sending the prototype object the clone message. newObject = oldObject clone. // Creation of a clone in Self.

Cloning is dened as shallow-copying, i.e. the contents of the prototype are copied to the clone but the process is not repeated recursively on the objects referenced by the prototype. Immediately after cloning, the clone and the prototype reference the same objects7 . The clone and the prototype are independent in the sense that adding or deleting an attribute or changing the value of an attribute in one of them does not modify the other (Figure 2.3a). They are mutually dependent in the sense that sending a message that modies a subobject implicitly modies all objects that reference that subobject (Figure 2.3b).

Obviously, prototypes may be used as templates for new objects. They are non-strict templates because the created objects can be modied by adding or removing attributes. For instance, addition of an element to the left array in
7 This

situation is also known as aliasing of instance variables [Hog91].

2.2. PROTOTYPE-BASED SYSTEMS


array at: 1 at: 2 at: 3 '' 'ab' 'bc' 'cd' array clone at: 1 at: 2
at: 1 at: 2 array '' 'bc'

15
array clone at: 1 at: 2

a) Independent modications of prototype and clone

b) Modication of shared subobject

Figure 2.3: Cloning in prototype-based languages Figure 2.3b) is possible after cloning. Non-strict templates oer considerable exibility, since they enable building of unique objects, groups of strictly similar objects, as well as series of objects which are related by small incremental modications. The price of this exibility is that prototypes cannot guarantee common structure. It is up to the programmer to maintain structural invariance, where desired. All objects (including clones) can be used as prototypes for the creation of new objects. Whether an object is called a prototype or a clone depends on its use there are no distinguished prototype objects. The distinction between traits objects and prototype objects recommended in Self [CUCH91a] is a matter of programming style, not a built-in language concept. To summarise, cloning oers dynamic, temporary, full sharing of structure and values between individual objects. The sharing partners are independent with respect to changes of their own structure and values, but mutually dependent with respect to changes of their shared subobjects.

2.2.4

Delegation, Consultation, and Resending

Whereas nowadays the notion of inheritance is familiar even to beginners in object-oriented programming, the notion of delegation is overloaded in the literature with at least three dierent meanings8 . This work focuses on the meaning of the term delegation as it was originally introduced by Lieberman [Lie86] in the context of object-oriented programming and on a variant that is called consultation [KRC91]. The similarities and dierences are explained in the following and illustrated in Figure 2.4 and Figure 2.5 on the next page. In both cases an object, called the child, may have modiable references to other objects, called its parents. Messages for which the message receiver has no applicable method are automatically forwarded to its parents. When an applicable method is found in a parent object (the method holder ) it is executed after binding its implicit self parameter (2.1), which refers to the object on whose behalf the method is executed. Denition 2.1 (Delegation and consultation) Automatic forwarding with binding of self to the message receiver is called delegation. Automatic forwarding with binding of self to the method holder is called consultation (Figure 2.4 on the following page). Intuitively, delegating a message means asking another object to do something on behalf of the message receiver, i.e. as the message receiver would do it. Consultation means asking another object to do something as it knows how.
Dictionary of Object Technology [FE95] even presents ve dierent meanings of delegation.
8 The

16

CHAPTER 2. BASIC CONCEPTS


self self

...
message receiver delegation method holder message receiver

...
consultation (message sending) method holder

Figure 2.4: Dierent eects of delegation and consultation on self Technically, delegation is a variant of inheritance whereas consultation is just an automatic form of message sending. The technical essence of delegation is the specic binding of self . An example. Let us consider the example illustrated in Figure 2.5, where the object empJohn references its parent object persJohn in the attribute person. The message empJohn.currentPhone# cannot be answered by empJohn, since it contains no method for currentPhone#. The message is forwarded to persJohn. When the method for currentPhone# is found in persJohn, its self is bound to the message receiver, empJohn, if delegation is used, and to the method holder, persJohn, if consultation is used. Thus, the subsequent message self.phone# returns Johns oce phone number in the rst case, and his private phone number in the second case. Overriding and hiding. Because the dynamic binding process proceeds from the message receiver to its parents, methods in child objects take precedence over methods in parent objects. This eect is subtly dierent for delegation and consultation, due to their characteristic treatment of self . For delegation, it is called overriding and applies to an initially received message, as well as to subsequent messages to self . For consultation it is called hiding and only has eect for initially received messages. Overriding enables customisation of the behaviour of methods inherited from parents by supplying local methods for messages sent to self . Hiding does not. Resending. Delegation as a language concept should be well distinguished from an implementation technique that is also called delegation by many authors, although it does not achieve the same functionality [RBP+ 91, p. 244]:

Object "empJohn" person = phone# = 555555 parent reference

Object "persJohn" name = "John" phone# = 666666 currentPhone# = self.phone# 555555 666666

Delegation: empJohn.currentPhone# Consultation: empJohn.currentPhone#

Figure 2.5: Dierent result of the same message for delegation and consultation

2.2. PROTOTYPE-BASED SYSTEMS Forwarding explicit automatic with binding of self to method holder message receiver resending explicit delegation consultation delegation

17

Table 2.1: Variants of forwarding Delegation consists of catching an operation on one object and sending it to another object [...]. Every instance of Stack contains a private instance of List. The Stack::push operation delegates to the list by calling its last and add operations. To avoid confusion, we call this redirection of a message by an explicit message sending operation within a manually written method explicit resending. Technically, consultation can be described as automatic (or implicit) resending. In spite of the common technical basis, consultation has a practical advantage over explicit resending, in that it frees the programmer from manual coding of forwarding methods. The dierent notions of forwarding are summarized in Table 2.1.

2.2.5

Typing

Prototype-based systems have often been criticised for their unharnessed exibility. Technically, this is a critique of the lack of a static type system, which characterised all rst-generation prototype-based languages. In this section we review recent work on type systems for the two characteristic mechanisms of prototype-based systems, object structure modication and delegation. 2.2.5.1 Static Delegation without Subtyping

Apparently, the rst typed prototype-based languages have been Omega [Bla91, Bla94] and Cecil [Cha92, Cha93a, CL94]. Cecil restricted delegation to be static and delegation parents to be statically known. Omega prohibits both delegation and individual changes of objects [Bla94, p. 104]. It denes a type as a clone family (a group of objects cloned from the same prototype) and automatically propagates every structure modication of the prototype to its descendants. Thus, as a price for static type safety, Cecil and Omega eliminated any form of individual behaviour change. As noted in [AC96, p. 44] Cecil and Omega strongly restrict the dynamic features, to the point that there is not much dierence [... to] class-based languages. 2.2.5.2 Property Update with a Statically Known Value

The rst type systems for a prototype-based language that included method update were developed by Abadi and Cardelli in their Theory of Objects [AC96]. They require the new property value to be statically known [AC96, p.

18

CHAPTER 2. BASIC CONCEPTS

44], thus strongly limiting the dynamic aspects9 . The authors mention that this restriction is equivalent to delegation to statically known objects (as in Cecil) and justify it as follows (p. 44): This restriction, adopted by statically-typed delegation-based languages such as Cecil, is important for checking the compatibility of the child with the parent. Knowing the type of the parent is not sucient for soundness. For example, if the contents eld of cell is rst forgotten by subsumption, and a child is dened with a contents eld having type Boolean, say, then the inherited get method returns a Boolean when invoked via the child because of the interpretation of self as the original receiver. This result is wrong because the declared result type of the get method is Integer. More generally, a parent may be an unknown object as long as its physical type is statically known (as in Cecil). Alternatively, it must be statically known that the parent does not have certain properties in order to be able to add them. 2.2.5.3 Mutually Exclusive Subtyping and Method Addition

A similar observation to the one of Abadi and Cardelli appears in Fisher and Mitchells Notes on typed object-oriented programming [FM94, p. 876]): ... no subtyping is possible [if ] method addition is a legal operation on objects [because] objects with extra methods cannot be used in some contexts where an object with fewer methods may. As an example, a colored point object cannot be used in a context that will add color, but a point object can. Based on this assumption Fisher and Mitchell conclude that method addition and update, on one hand, and subtyping, on the other hand, are mutually exclusive. In [FM95] they present a system that gives dierent uses of objects dierent types. Their object types allow either extension and update without subtyping or subtyping without extension and update. 2.2.5.4 Restricted Subtyping or Restricted Method Addition

An approach that allows a limited coexistence of both concepts was proposed by Bono and Liquori. In [BL95] it was suggested to jointly enable subtyping and extension but to restrict subtyping so that it forgets only methods that are not referred to by the bodies of the methods which are visible in the supertype. Thus new methods can be added without fear that they would ever be used in contexts expecting forgotten methods. A complementary limited coexistence approach was suggested by Liquori in [Liq96, Liq97]. Instead of restricting subtyping, it remembers the types of forgotten methods and restricts method addition to add only methods whose type is consistent with the forgotten ones. A very similar idea is presented in [Rm98]. It is worked out at the level of e elds instead of objects, yielding a more ne grained and uniform system.
9 In

[AC96] method update with a statically known value was called embedding.

2.2. PROTOTYPE-BASED SYSTEMS 2.2.5.5

19

Subtyping and Semantically Inconsistent Method Addition

A more general approach was proposed recently by Riecke and Stone [RS98]. Their system combines unrestricted width subtyping and unrestricted method addition. This has been achieved by modifying the operational semantics in a way that makes it incompatible with the intuitive semantics of a program. Because this inconsistency has not yet been described in literature, we shall discuss the approach of Riecke and Stone in more detail, and point out its problems. Summary of the approach. The approach of [RS98] makes method dictionaries explicit part of object types. Method dictionaries map external method names to internal names. The internal name of a method can be thought of as the index of the methods code in a dispatch table [Str94, Str87]. The operational semantics is modied so that method selection, addition and update manipulate the method dictionary associated to the receiver object. In particular, when a method is added to an object, 1. its external name is mapped to a new internal name, 2. the objects method dictionary is extended by this new mapping and 3. the body of the new method is manipulated so that all messages sent therein to self use the extended dictionary but none of its future extensions (static binding). With static binding of messages to self , new methods can be added without changing the semantics of existing ones, even if the new and existing methods have the same name / signature. Addition of a new method automatically hides a previously existing method with the same name from the type of the object. However, the old method continues to be part of the object and maintains the same dispatch table position (internal name) as before. Except for the case of messages to self , method selection and method update use the current dictionary of the receiver object, i.e. the method stored at the dispatch table position currently associated to the methods external name is selected or replaced (dynamic binding). Thus the two central aspects of the approach are static binding of messages to self and dynamic binding of all other messages. Semantic inconsistency. The inconsistent treatment of messages to self and of other messages, leads to semantic inconsistency: the same message, sent to the same object may produce dierent results depending on whether it has been sent via the self pseudovariable or via another expression. Let us consider the following program in the notation of [RS98]: o1 := + + obj s. {}[] i(s) = 0 : int idouble(s) = s.i + s.i (2.1) (2.2)

o2 := (o1 i(s) = 2 : int) +

20

CHAPTER 2. BASIC CONCEPTS


o1 = newobject { int i=0; int idouble(){i+i} } o2 = o1 extendedby { int i=2; }

Listing 2.1: The code from (2.1) and (2.2) in Java-style pseudo-syntax. The intended semantics of the method idouble is to return the double of the current value of i. With the operational semantics of Riecke and Stone, however, this semantics is not preserved after addition of a new i method in (2.2). When o2 receives the message i o2 .i it returns 2 but when it receives the message idouble o2 .idouble it does not return 4 but 0. The reason is that the explicit i message accesses the new i property of o2 , whereas the body of idouble still accesses o2 s old i property, due to static binding of messages to self . The same holds for updates of i: whereas external update operations change the new i property of o2 , calls to idouble still use the old i property. What is to be learned from this example is that binding of external to internal names (i.e. messages to dispatch table positions) at the time when a method is added to an object is too early because it ignores methods with the same name added latter at a dierent dispatch table position. So static binding avoids one kind of semantic inconsistency that can arise between multiple methods with the same name (accidental method overriding) at the price of introducing another kind of semantic inconsistency (no method overriding, even if it is intended). No generalisation to imperative semantics. With the functional update semantics adopted in the original paper, one might argue that use of method addition ( in the above example is not appropriate and that method update +) () would better match the programmers intention because, after the update o2 := (o1 i(s) = 2 : int) the message o2 .idouble returns the expected result, 4. Requiring that object extension must be used carefully10 thus appears to solve the problem, at least in the trivial example given above. However, even in a functional setting, the operational semantics of any program with several hundreds of lines of code (which is still toy-size in professional software development) might be impossible to understand or to get right. Even if programmers would manage somehow not to lose track of things, the main problem with this solution is that it is not applicable in an imperative setting.
10 [RS98] write: [...] object extension must be used carefully. One may always use object extension in place of method override but the consequences are dierent. [...] The programmer must be careful to determine which of these behaviors is correct and use the appropriate operation.

// o2 .i = 2;

(2.3)

2.2. PROTOTYPE-BASED SYSTEMS

21

When the same object is referenced from dierent contexts (e.g. dierent variables of possibly dierent types) it might not be possible to update the value of a property. The simplest reason might be typing: if one of the variables referencing an object has a type that does not include a certain property then this property cannot be updated via that variable. Thus, there would be no choice but to add a second property with the same name and the desired value. The resulting inconsistency in the observable behaviour of the referenced object, may now arise also in a completely dierent context, as illustrated in the following example. Let T1 = {i : int, idouble : int} and T2 = {}. Clearly, T1 is a subtype of T2 (in the width subtyping system of Riecke and Stone) thus the following program is legal: o1 : T1 ; o2 : T2 ; ... o1 := o2 := o1 o2 i(s) = 2 : int + o1 .idouble // legal object extension // returns 0 (using value of old i) + + obj s. {}[] i(s) = 0 : int idouble(s) = s.i + s.i // o1 is of type T1 // o2 is of type T2

The inconsistent behaviour of i and idouble revealed in the last two lines is a serious problem. Now the inconsistency arising from the manipulation of an object in one context (i.e. via the variable o2 ) manifests itself in a completely dierent context (i.e. when accessing the same object via the variable o1 ). There is no way programmers can protect themselves against this eect, since the bug in one part of a program can be the logical consequence of a program part written by someone else. It follows that the approach of [RS98] cannot be applied to an imperative system because it would prohibitively lead to unpredictable behaviour of programs. Dependency on order of method addition. Besides the above main critique there is another aspect that is worth mentioning. With static binding, the semantics of an object depends on the order in which its methods have been added. For instance, even in the functional model, the objects o3 and o4 , dened as o3 := ((o1 + i(s) = 3 : int) + idouble(s) = s.i + s.i) o4 := ((o1 + idouble(s) = s.i + s.i) + i(s) = 3 : int) produce dierent results for idouble: o3 .idouble returns 6, because the body of the called idouble method accesses the new i property of o3 , whereas o4 .idouble (2.4) (2.5)

22

CHAPTER 2. BASIC CONCEPTS

returns 0, because the body of the called idouble method accesses the old i property of o4 (the one that has already been contained in o1 ). A semantics that depends on method ordering is not just an inconvenience for the programmer, who has to pay attention to add methods in the right order. Above all, it interacts with the method-level granularity of object structure modication operations in a way that makes it impossible to express the intended semantics of programs that contain groups of mutually dependent methods. As an example, consider the following program in Java-like pseudo-syntax:
o1 = new object { m1() { ... } m2() { ... } } o2 = o1 extended by { m1() { if ... then ... else m2() } m2() { if ... then ... else m1() } }

// m1() depends on m2() // m2() depends on m1()

Because it is not possible to add m1() and m2() as a group, the body of the added m1() method will refer to the old m2() method. One could imagine an extension of Riecke and Stones calculus by a means to express grouping of methods, trying to mimic the larger granularity of delegation. Nevertheless, such a correction would still not eliminate the general dependency on method ordering, the basic static binding problem, and the missing possibility to express implicit sharing (one parent object shared by multiple child objects that can be selectively addressed). In the nal analysis, the generality of the approach comes at the cost of semantic inconsistency and limited expressiveness. 2.2.5.6 Typing Summary

We may thus conclude that, in conjunction with subtyping, there still is no published system that allows semantically sound, unrestricted use of object extension, on one hand, or delegation, on the other. The reviewed approaches fall into three categories: Category A prohibits either subtyping or behaviour modication or both, Category B allows joint use of subtyping and behaviour modication (in the form of method addition and update) but signicantly restricts one of them, Category C allows unrestricted joint use of subtyping and behaviour modication (in the form of method addition and update) at the price of an operational semantics that is inconsistent with the intended semantics of a program. It is worth noting that almost all of the above proposals deal with method addition and update. In particular, there is no proposal that combines subtyping and delegation. This concludes our introduction of prototype-based systems. A detailed discussion of their practical use for modeling behaviour modication and a comparison to class-based systems is contained in chapter 3.

2.3. CLASS-BASED SYSTEMS

23

2.3

Class-Based Systems

Class-based systems dier from prototype-based systems by allowing groups of objects with uniform structure to be created via instantiation and the structure and behaviour of instantiated objects to be incrementally specied via inheritance.

2.3.1

Instantiation

Instantiation is the only approach to object creation that guarantees common structure of a group of objects. It distinguishes between two kinds of objects: classes and instances. Classes represent abstractions (e.g. the concept of a chair) while instances represent concrete objects (e.g. the chair I am sitting on). Classes. From a technical point of view, classes are entities that explicitly contain a template that species how objects are to be created. In the context of class-based languages a template is a specication of property names (and possibly types) together with an implementation of methods (and possibly a default initialisation of variables). The properties specied in the template are called instance properties. Additionally, classes may contain class properties (variables and methods). Class properties are shared part of all the instances of the class and are thus accessible within instance methods. In some languages, e.g. Smalltalk and LOOPS, classes are themselves objects and the template contained in a class is represented by one or more variables of the class object11 , which still exist and are accessible at runtime. In other languages, e.g. Eiel, C++, and Beta, templates are only present during compilation. Instances. Instances are objects created from a class template. Instantiation is the process of creating instances. This is usually done either by calling a new method of the class (e.g. in Smalltalk) or by using the new operator in conjunction with a constructor method of the class (e.g. in C++ and Java). All instances of the same class have exactly the same instance properties and dier only in the values of their variables. Unlike clones in prototype-based languages, instances can neither change their structure by adding or removing properties nor change the implementation of methods. Also, in almost all classbased languages and models all objects must be instances of some class and instances cannot change their unique most specic class12 . The properties accessible to an object include its own instance properties and the class properties of its class.
11 E.g. each Smalltalk class contains two variables, mehodDict and instanceVariables, which contain the names of the instance variables and methods dened in the class for its instances. 12 The most specic class of an instance is the one from which it was instantiated. When considering inheritance, an instance belongs to multiple classes in that it implicitly belongs also to the superclasses of its most specic class.

24 2.3.1.1 Variations

CHAPTER 2. BASIC CONCEPTS

Common, invariant structure of a group of objects enforced by instantiation may be either a blessing or a curse. The fact that instances cannot lose attributes or change their class protects against certain kinds of errors that could otherwise occur, e.g. when an objects structure is inadvertently modied (see. corrupted prototype problem in [SLS+ 94]. It is also essential for type systems and most class-based implementation techniques. On the other hand, classes and instantiation are too rigid to express structural individuality, diversity, and change, which are intrinsic to many applications. This gave rise to dierent variations of the basic (strict) instantiation concept. Extensible instances and promotion. Instances that can add (but not remove) properties are called extended instances and their templates are called minimal templates [Ste91]. Extended instantiation eases the work of programmers, who are not forced to create specic one-instance-subclasses for objects with unique structure. In principle, an extended instance can be simulated by a subclass with exactly one (strict) instance [Ste87, Ste91]. The simulation depends on the ability to manipulate object identiers: the subclass instance must appear to be the same object as the superclass instance whose extension it represents. Another prerequisite is the ability to create a subclass at run-time. Currently, Smalltalk appears to be the only widely-used class-based language that meets both requirements. In Hybrid [Mer88] programmers can promote the extended instance when they later discover that there are more objects of the same structure as the extended instance. Promotion makes the hidden subclass that implements the additional properties visible, such that programmers can use it to instantiate further objects of the same kind. One-of-a-kind objects. Beta allows the creation of one-of-a-kind objects that are not instances of any class13 . This makes the denition of unique objects like nil, true, and false more natural, since it does not require the introduction of articial abstractions. At a rst glance, one-of-a-kind objects look like extended instances of the empty class. The essential dierence between Betas one-of-a-kind objects and Hybrids extended instances is that the former are statically declared, whereas the latter can be created and extended at run-time. Multiple instantiation. Another variation is the concept of multiple instantiation, which allows an object to be instance of multiple most specic classes [FAC+89, FBC+ 87, Ste91, BG95]. Multiple instantiation is often coupled with the ability of objects to acquire and lose class membership while retaining their identity14 . With multiple instantiation methods in most specialised types over13 In Beta the notion of patterns is used, which is a more general abstraction mechanism than classes in that it unies methods and classes. 14 In a database context, dynamic change of class membership by an instance is often called object migration. However, in the context of distributed systems migration means that objects are moved from one network location to another one. To avoid confusion we shall not use this term here.

2.3. CLASS-BASED SYSTEMS

25

ride methods in more general types, but there is currently no satisfactory general criterion for deciding which method to select if no unique most specialised type that contains a selectable method exists. In [BG95] Bertino and Guerrini compare dierent dynamic binding for multiple instantiation. Each of the discussed alternatives has its particular strength and deciencies, so the issue is still open. A simulation of multiple instantiation by single instantiation with multiple inheritance is proposed in [Ste91]. However, the simulation does not solve the intricate semantic problems of dynamic binding for multiple instances addressed in [BG95] and depends on the same assumptions as the simulation of extended instances discussed above. Metaclasses. In some systems [GR89],[BKSK85], [Pae93] classes are themselves instances of other classes, called metaclasses. Notably in the ObjVLisp model [Coi87] and its adaptation to Smalltalk [Bri89], anything valid for classes and instances uniformly applies to metaclasses and classes. This uniformity allows to create systems, that are internally built using the same techniques that they oer to their users, i.e. objects, classes, inheritance, etc. If the internal representation of the system is consistent with its external behaviour in the sense that changes of the internal representation are reected in the systems behaviour (causal connection property) we have a reective system [Mae87]. Reective systems can be customized and extended without stepping out of their own object model. Reection has been recognized as a key concept in contemporary software engineering and applications in a variety of areas are reported in the literature [BK82, ABB+ 89, KRB91, Yok93, OI94, YAYT93, Mal90, Pae90].

2.3.2

Inheritance

Inheritance is a relation between classes. If class B inherits form class A then class Bs instance template is implicitly extended by all the additional properties dened in As instance template. The inheriting class A is called the subclass and the donor class B is called the superclass. By its reliance on instance templates, inheritance is tightly interwoven with instantiation and with the existence of a dynamically bound self parameter (2.1). In some languages, subclasses additionally share the class properties of their superclasses. However, the range of variations of class property sharing in dierent languages is too wide to qualify as an essential characteristic of inheritance: In Smalltalk, a subclass can read and write the class variables and call the class methods of its superclass as if they were its own. Class methods are dynamically bound and class methods of subclasses override those of their superclasses. In C++, a subclass can also read and write the class variables and call the class methods15 of its superclass as if they were its own. However, class methods are statically bound and thus class methods of subclasses do not override those of their superclasses.
15 Static

members and static member functions in C++ parlance.

26

CHAPTER 2. BASIC CONCEPTS In Java, subclasses do not share the class properties of their superclasses at all. Use of class properties of a superclass requires explicit qualication of the property name with the superclass name, i.e. Superclass.var instead of var.

2.4

Summary

This chapter has reviewed the concepts of object-oriented programming that are essential for Darwin. Whenever there was a choice between dierent alternative denitions of a concept, we chose the one that was least restrictive with respect to sharing. In particular, we favour the self -application semantics of objects (2.1) in which self is bound during message sending, not instantiation, thus enabling execution of an objects methods on behalf of another one. Table 2.2 gives an overview of the, largely complementary, basic mechanisms of class- and prototype-based languages. Class-based Prototype-based systems systems templates that contain methods with an unbound self parameter operator on templates that does not bind self object creation from a template without binding of self method invocation with binding of self to the message receiver message forwarding that preserves the binding of self to the message receiver addition, deletion and update of methods and variables

Classes Inheritance Instantiation Message passing

Delegation

Structure change

Table 2.2: Basic concepts of class- and prototype-based systems

Chapter 3

Behaviour Evolution
3.1
3.1.1

Behaviour Evolution, Sharing and Reuse


Sharing

Much of the success of object-oriented programming is due to its ability to express various forms of sharing. Expressiveness, conceptual simplicity, and last but not least reusability of object-oriented programs depends in a critical way on the available sharing mechanisms. In the Treaty of Orlando [SLU89] it was recognised early that, in spite of the apparent dichotomy between class-based systems and prototype-based systems, both approaches provide solutions to the same basic forms of sharing that are essential to every object-oriented system: sharing by creating new objects from existing templates, and sharing by letting existing objects use the properties of other objects as if they were their own (empathy). Cloning and instantiation are variants of the template function, whereas delegation and inheritance are variants of empathy. These four mechanisms dier with respect to their: Scope: Do objects share only the specication of properties (structure sharing) or also the value of properties (value sharing)? Dynamics: Is sharing dened when an object is created (static sharing) or when it receives a message (dynamic sharing)? Can the sharing relationship change at any time (temporary sharing) or is it xed forever (permanent sharing)? Granularity: Is it possible to specify sharing per individual object or per group of objects, i.e. can individual properties be attached to an object (objectbased sharing) or can common properties be guaranteed for a group of objects (group-based sharing)? Explicitness: Must the object which is going to be shared be explicitly named in every message (explicit sharing) or is it given in a separate declaration that is implicitly taken into account when a message is sent (implicit 27

28

CHAPTER 3. BEHAVIOUR EVOLUTION sharing)? Implicit sharing fosters reuse because changes in a parent object are automatically available to child objects. Explicit sharing allows programmers to unambiguously express their intentions.

The strength of the dierent mechanisms are largely complementary. Therefore the authors of the treaty declare that no denitive answer as to what set of these choices is best can be reached. Rather [...] dierent programming situations call for dierent combinations of these features. The conclusion that no denitive answer . . . can be reached is true in a broader sense. Dierent sharing mechanisms are required not just by dierent types of software development processes but, more importantly, by dierent types of applications. Each type of sharing that is intrinsic to an application should easily be expressible be it static, permanent sharing between groups of objects or dynamic, temporary sharing between individual objects. Therefore, all types of sharing must be supported equally well by wide-spread production programming languages and conceptual modelling techniques.

3.1.2

Unanticipated Reuse

The authors of the treaty also pointed out the intimate relationship between sharing and reuse. Mechanisms that require to anticipate sharing support only anticipated reuse. Mechanisms for unanticipated sharing eectively support unanticipated reuse. A mechanism supports unanticipated reuse if it allows reuse of existing compiled software modules without the need to change them in any way. In a software industry whose main costs are attributed to program maintenance and evolution (some authors report rates of up to 80%), support for unanticipated reuse is of paramount importance. Stein, Lieberman and Ungar [SLU89] conclude that: We were searching for ways of implementing unanticipated behaviour extensions without modifying existing code, and concluded that the solution was to allow more dynamic forms of empathy. At the same time we wish not to minimise the importance of language mechanisms for traditional, anticipated sharing and believe future languages must seek a synthesis of the two. This thesis proposes such a synthesis. Before starting the presentation of the new model this chapter determines a non-redundant set of ingredients. The dierent candidate mechanisms are compared in the light of the functionality that motivated the synthesis: modelling of behaviour evolution.

3.1.3

Behaviour Evolution

Change and diversity are intrinsic to the real world, which continually evolves in ways that cannot be anticipated. Therefore a model of object-oriented languages that supports both, behaviour evolution and unanticipated extension would be a highly desirable tool for every analyst, designer and programmer.

3.1. BEHAVIOUR EVOLUTION, SHARING AND REUSE Prototype-based Systems


Scope Dynamics Granularity Explicitness
a Method

29

Class-based Systems
Instantiation structure and valuesb Inheritance valuesc

Cloning structure and values

Delegation valuesa

dynamic, temporary object


explicit implicit

static, permanent group


explicit implicit

code and variable values of the parent object. code and values of class variables. c The contents of the superclass template.
b Method

Table 3.1: Sharing mechanisms of prototype- and class-based languages

The class-based model (2.3), on which most widely-used object-oriented programming languages rely, cannot easily express changes in the structure or behaviour of an object. In contrast, prototype- and delegation-based languages can directly express changes of structure and behaviour but do not provide safety guarantees like static typing and guaranteed uniform structure of groups of objects, which are widely considered indispensable for production programming. Rather than trying to devise a completely new model this thesis seeks a minimal extension to the class-based model that allows the dynamic nature of the world to be expressed without compromising unanticipated reuse. This chapter compares class- and prototype-based languages and selects the concepts that appear to be a promising basis for integration. It answers the following questions: 1. What are the requirements for a behaviour evolution mechanism? 2. How can behaviour evolution be achieved with the mechanisms of prototypebased and class-based languages? 3. How do the reviewed mechanisms compare to each other with respect to the identied requirements? 4. Which are the candidate mechanisms for a unied model? Section 3.2 introduces two basic scenarios of object-specic behaviour change that will be used as a guideline for the comparison of dierent mechanisms. Section 3.3 states the requirements for a object-oriented model that supports behaviour change. In section 3.4 we review and compare the dierent behaviour change mechanisms of prototype-based languages. The class-based model is evaluated according to the same criteria in section 3.5. Section 3.7 summarises the lessons learned.

30

CHAPTER 3. BEHAVIOUR EVOLUTION

3.2

Scenarios for Behaviour Evolution

Modelling of object-specic properties and of their dynamic evolution can be broken down into three base functionalities: addition of properties, deletion of properties, update of properties. These functionalities are at the core of the following two scenarios, which will be used throughout this chapter for the comparison of behaviour evolution mechanisms: The addition/deletion scenario: A person named John gets a job and loses it again. This should be modeled as successively gaining and losing employee-specic properties, e.g. salary, work(), and askForSalaryIncrease(). Transitions from unemployed state to employed state and back should be expressed by the methods getHired() and getFired(). When employees get red they should lose their employee-specic properties should remember relevant details of their former employment (e.g. their salary). Similar scenarios have often been described in the context of so-called role models [ABGO93, Civ93, GSR96, KLRSR95, Kni96b, Per90b, RS91, VC93, WJS94], which try to express the dynamic acquisition and abandoning of behaviour when an entity plays dierent roles during its lifecycle. In our scenario we have the base role Person and the dynamic roles Employee, and Former employee. The update scenario: A TCP network connection responds dierently to requests (open(), close(), acknowledge()) depending on its current state (established, listening, closed). This corresponds to updating the connection objects method implementations when it changes state. The state transitions should be expressed by the methods becomeEstablished(), becomeListening(), and becomeClosed(). The TCP connection scenario has been used as an illustration of the state design pattern in [GHJV95].

3.3

Requirements for Behaviour Evolution

An object model that supports object-specic behaviour evolution must 1. provide means to eectively perform addition, deletion and update of properties in individual objects, 2. support unanticipated reuse (see section 3), 3. support conceptual modelling approaches, 4. make as few amendments as possible to the widely-known statically typed class-based model, thus being easy to learn and provide a smooth evolution path for existing software, and

3.4. BEHAVIOUR CHANGE IN PROTOTYPE-BASED LANGUAGES

31

5. be easy to deploy with respect to standard requirements of modularity, limited verbosity, etc. The next sections analyse how well dierent mechanisms can model the two behaviour evolution scenarios and to which extent they comply to the above requirements.

3.4

Behaviour Change in Prototype-Based Languages

Prototype-based languages dier markedly from traditional class-based languages by their two built-in concepts for modelling object-specic properties: object structure modication: addition, deletion, and update of properties (3.4.1), and dynamic delegation (3.4.2). This section compares both concepts with respect to the above-stated requirements seeking the best candidate for an integration into the class-based model.

3.4.1

Object Structure Modication

The object structure modication operations directly support all the three basic functionalities identied in the previous section. The built-in operations addProperty, dropProperty and updateProperty used in the pseudocode examples of this section have corresponding counterparts in every prototype-based language1 .The use of object structure modication in our scenarios is illustrated in Listing 3.1 and Listing 3.2 on page 33. Risk of uncontrolled use. The addProperty, dropProperty and updateProperty messages can be sent at any time by every object to every object, oering maximum support for unanticipated changes. The other side of the coin is the opportunity for arbitrary damage that may result from the uncontrolled use of object structure modication messages. For instance, the corrupted prototype problem [SLS+ 94] is a frequent error in prototype-based programs: when accidentally writing anObject dropProperty:salary instead of anObject clone dropProperty:salary the modication will be performed on the original prototype. Because the prototype and its clones respond to the same messages, there is no way how the system can detect such a semantic error. Even a static type system cannot help. In a class-based system a call to an instance method sent to a class is caught statically or results in a messageNotUnderstood error instead of corrupting its receiver.
1 For instance Self provides the messages addSlot: and deleteSlot:, which are understood by every Self object. For each property, say p, there is an update method p:, which may be used to assign a method body as the new value of the property.

32

CHAPTER 3. BEHAVIOUR EVOLUTION

John introduce()

addition of employee properties

John introduce()

deletion of employee properties

work() askForSalaryIncrease() auxWork() ...

Two dierent states of the object John. Added / removed properties are highlighted. object John is { var name = "John"; method introduce(){ return self.name; } method getHired(newEmployer, newSalary) { self.addProperty(salary); // acquire employee properties self.addProperty(work() {...}); self.addProperty(auxWork() {...}) ; self.salary = newSalary ; // initialize employee variable } method getFired() { self.addProperty(lastSalary); self.lastSalary = self.salary ; self.dropProperty(salary); self.dropProperty(work()); self.dropProperty(auxWork()); }

// remember last salary // abandon employee properties

Listing 3.1: Property addition and deletion via object structure modication. No backup. Object structure modication is destructive. When a method is replaced there is no way to backup its old version because there is no way to retrieve and store the code of a method as a value2 . The requirement to remember relevant details of a past employment can only be met in method getFired() of Listing 3.1 for the salary variable. Without recursing to reection, backup of methods is only possible if the programmer statically knows the old method code. When updating a method, the updating code has to explicitly include the new method code as well as the code to be backuped. This renders any attempt to write modular software ad absurdum because the client code has to know the exact current implementation of each method in the changed object. No grouping. By allowing to selectively address individual properties, the object structure modication operations oer the smallest possible level of granularity for unanticipated change. On one hand, this may be appreciated as maximum exibility for the programmer. On the other hand, it may be a source of verbose and error-prone programming techniques. Replacing a group of methods in an object, requires that the programmer manually keeps track of method interdependencies, including dependencies on auxiliary methods, as illustrated by the method auxWork() in Listing 3.1 and auxClose() in Listing 3.2 on the facing page. The listings give an impression of how cluttered
we have a reective system. Reection, however, raises currently unsolved problems with respect to static typing.
2 Unless

3.4. BEHAVIOUR CHANGE IN PROTOTYPE-BASED LANGUAGES


myTCP connection open(), close(), achnowledge() myTCP connection open(), close(), achnowledge()

33

initial state

property update

closed state

Two dierent states of the object John. Updated properties are highlighted. object myTCPConnection is { method open() {...} method acknowledge() {...} method close() {...} method becomeEstablished() {...} method becomeListening() {...} method becomeClosed() { self.updateProperty( open() {...} ) ; self.updateProperty( acknowledge() {...} ) ; self.updateProperty( close() {...} ) ; } }

// initial // initial // initial // established // listening // closed

state state state state state state

Listing 3.2: Property update via object structure modication. the code may become due to the need to add, remove, update, or backup (see Listing 3.1 getFired()) every property separately. One can easily imagine that the become...() methods in Listing 3.2 would have lled several pages without extensive use of ellipses for method bodies. No modularity. The previous two points can be seen as instances of a more general problem: the missing modularity of object structure modication. Each client of an object has to implement the new behaviour of the object, implement the consistent replacement of the old behaviour by the new one, and know statically the old behaviour of the object in order to be able to back it up. If extension is not anticipated the getHired() and getFired() methods in Listing 3.1 is typically not part of the object itself but of each of its clients. Burdening clients with structure modication code is also required for semantic reasons, in many cases: hiring a person as an employee of a nuclear power plant will require to provide her with a dierent set of methods as when hiring her as a university employee, for instance. Summarizing, the object structure modication operations apply to individual properties, preserve object identity, preclude sharing and are destructive. As a consequence they oer extreme exibility for unanticipated changes at the smallest level of granularity but are error-prone, verbose, and incompatible with conceptual modelling and modular programming.

34

CHAPTER 3. BEHAVIOUR EVOLUTION

3.4.2

Delegation

Delegation requires and enables separation of a conceptual entity into multiple objects in order to achieve behaviour modication. This separation results in one object that represents the permanent properties of the entity and in dierent objects that represent groups of properties that can be added, removed, or mutually exchanged. Delegation is the glue that makes these dierent objects act together as if they were one. For an external observer change of delegation parents lets an object appear as if it changed its properties. Exploiting this basic idea, the eect of property addition, deletion and update can be accomplished by delegation in two ways that dier only in their degree of support for unanticipated changes. 3.4.2.1 Property Addition and Deletion

Locally anticipated extension. In the scenario illustrated in Listing 3.3 the addition of properties is partially anticipated. Looking ahead, a parent variable (2.2.4) is added to the object that is to be extended. Property addition is simulated by initialising this variable with null and by letting it later refer to an object that contains the additional properties. Property deletion works the opposite way, resetting the variable to null. This approach preserves the object identity of the extended object, i.e. the same object appears to acquire and lose properties. Note that only the fact that an extension will take place is anticipated in the child object. According to Stein, Lieberman and Ungar [SLU89] this still is an unanticipated extension: the parent object (employee) is reused as is and the child object does not need to know the exact structure of the extension (i.e. the exact properties that will be added).

introduce(), getHired(), getFired()

add

introduce(), getHired(), getFired()

work(), askForSalaryIncrease()

John

delete John employee

object John is var name = "John"; method introduce(){ return self.name; } parent employeeRole = nil; var pastEmployments = ...;

// permanent "Person" properties

// transient employee properties // collection of past employments

method getHired(newEmployeeRole) { self.employeeRole = newEmployeeRole; // acquire employee properties } method getFired() { self.pastEmployments.add(self.employeeRole); // remember last job self.employeeRole = nil; // abandon employee properties }

Listing 3.3: Locally anticipated property addition and deletion via delegation.

3.4. BEHAVIOUR CHANGE IN PROTOTYPE-BASED LANGUAGES

35

Fully unanticipated extension. Fully unanticipated extension is achieved by letting the object that contains the additional properties delegate to the one that is to be extended. This kind of extension does not preserve object identity, i.e. it only becomes visible in places where references to the initial object are replaced by references to the extending object. This may either be a problem or a feature. Used as a feature, multiple extensions of the same object may exist and may be alternatively used at the same time. For instance, each extension object might represent a dierent perspective from which the initial object can be regarded [Sci89] or a dierent version of the same object [Kat90]. Due to the high degree of modularity enabled by delegation the example in Listing 3.4 has been worked out at a level of detail that would have been prohibitive with object structure modication. It shows the code of four objects that serve as repositories of shared structure and behaviour and may be used for object creation via cloning3 : the object Person, the ProgrammerRole object, whose clones are intended to serve as children of Person clones, the RoleManager object, which manages role changes and remembers past roles its clones are intended to serve as children of role objects, and an Employer object, which provides methods to hire and re employees in a certain position (= role) when hiring a person it associates the proper employee role with that person. The gure at the top of Listing 3.4 shows a typical object hierarchy created by cloning these traits. It represents the person John hired as a programmer by some employer. It is noteworthy that the code contained in Employer, Person, ProgrammerRole, and RoleManager does not need to be replicated in clients that want to hire a person. The client code performing behaviour evolution is reduced to the three lines shown at the bottom of Listing 3.4. New behaviour can be introduced incrementally by adding new ...Role objects and passing them as parameters to the existing code, which does not need to be changed in any way. 3.4.2.2 Property Update

Property update can also be accomplished by delegation in two ways that dier in their degree of support for unanticipated changes. Fully unanticipated update. The fully unanticipated property update scenario is similar to the corresponding scenario for property addition, The sole dierence is the contents of the added child objects: instead of additional methods, they contain redenitions of methods from the parent object. Messages sent to the child object will behave as if they had been sent to the parent object after changing some of its method denitions. The code of our example would be the same as the one shown in Listing 3.4 on the next page up to the addition of an introduce() method in the EmployeeRole object. Therefore there is no specic listing for fully unanticipated behaviour update.
3 In

Self, such objects are called traits.

36

CHAPTER 3. BEHAVIOUR EVOLUTION

work(), askForSalaryIncrease() An object that refers to JohnAsEmployee sees Johns properties plus the "added" ones

introduce(), getHired(), getFired() An object that refers to John does not see the "added" properties

JohnAsEmployee delegateTo

John

object Person is // permanent "Person" properties var name = nil; method introduce(){ return self.name; } } object ProgrammerRole is { parent person = nil; method setParent(aPerson) { person = aPerson; }

// attach this role to a person

// role-specific code: salary, employer, work(), ... } object RoleManager is { parent role = nil; var player = ...; var pastRoles = nil; method become(aRole) { self.role = aRole; aRole.setParent(player); } method abandon() { self.pastRoles.add(self.role); self.role = nil; }

// current role // who plays the role // collection of past roles

// acquire role // ... for my player

// remember current role // abandon role

object Employer is { var employees = ...;

// collection of employees

method hire(aPerson, anEmployeeRole) { // hire a person as employee aPersonRoleManager = RoleManager.clone(); aPersonRoleManager.player = aPerson; aPersonRoleManager.become(anEmployeeRole); employees.add(aPersonRoleManager); } method fire(anEmployee) { // fire an employee employees.remove(anEmployee); anEmployee.abandon(); }

// Client code (in some clone or child of Employer): john = Person.clone().name = "John"; johnAsProgrammer = ProgrammerRole.clone(); self.hire(john, johnAsProgrammer); // hire John as a programmer

Listing 3.4: Fully unanticipated property addition and deletion via delegation.

3.4. BEHAVIOUR CHANGE IN PROTOTYPE-BASED LANGUAGES

37

initial State setState() myTCP connection dynamic parent switching

open(), close(), achnowledge()

...
closed State

object myTCPConnection is { parent currentState = ...; // refers to object that implements open(), ... method setState(aStateObject) { self.currentState = aStateObject; } }

Listing 3.5: Locally anticipated property update via delegation. Locally anticipated update. Again, we follow the corresponding scenario for property addition and deletion (Listing 3.3), with the dierence that the respective parent variable never becomes null. Instead, it successively references parent objects that have dierent implementations of the same properties, as shown in Listing 3.5. The breakdown of conceptual objects into multiple physical objects that perform well-dened tasks, eases program maintenance and evolution: the problem of how to manage dynamic behaviour change is separated from the problem of providing chunks of consistent behaviour that may be interchanged. The rst one is dealt with in the myTCPconnection object, the second one is dealt with in the dierent state objects. Neither the parent object itself nor its exact structure need to be statically known. Each parent object may contain various auxiliary methods in addition to open(), close(), and acknowledge(). Keeping track of these auxiliary methods is neither the responsibility of the myTCPconnection programmer nor the responsibility of client code. As a consequence of the separation of concerns achieved by delegation, all three state switching methods can be collapsed into one. The resulting state switching code is trivially simple and easy to maintain, as shown in Listing 3.5. It works regardless of the exact contents of objects passed as parameters to the setState() method. Passing objects that provide dierent implementations for the same state is as easy as passing objects that represent dierent states. Most importantly, adding new states does not require any changes to the myTCPconnection object. This stands in sharp contrast to the use of object property modication (3.4.1), which tightly couples the code that performs behaviour evolution with the code that represents the new behaviour.

3.4.3

Summary Prototype-based Behaviour Evolution

To recap, object structure modication is the primary choice if fully unanticipated, identity-preserving changes of individual properties of individual objects are required. However, this ultimate exibility has a high price. Property-level granularity makes keeping track of properties that belong together and are to

38

CHAPTER 3. BEHAVIOUR EVOLUTION Structure modication Identity-preserving? Unanticipated? Typing? Sharing? Modularity? Non-Destructive?
a no b typing

Delegation yes / no no / yes nob yes yes yes

yes yes limiteda no no no

unrestricted and semantically sound subtyping (2.2.5.6). only for static delegation to statically known parent object (2.2.5.1)

Table 3.2: Object structure modication versus delegation be modied simultaneously, a tedious and error-prone task. As a further consequence, object structure modication is incompatible with modularity and conceptual modelling. When the code that manipulates the structure of an object can be scattered through the whole program there is no modularity anymore. All aspects of object-specic functionality and dynamic change of behaviour required in 3.3 can also be modelled by delegation. Fully unanticipated changes are possible if object-identity does not need to be preserved, whereas identity-preserving changes have to be partially anticipated by providing suitable methods in the object that is to be changed. We call this partial anticipation, compared to class-based systems, where changes have to be anticipated in the class to be changed and the class that represents its modied behaviour (see 3.6.3). Compared to object structure modication, delegation is more convenient when multiple properties have to be changed at once. It allows programmers to choose between objects that contain a consistent set of relevant properties, instead of forcing them to keep themselves track of all the properties that together achieve the intended functionality. A look at Listing 3.1 through Listing 3.5 shows that the methods in the delegation-based variants are signicantly simpler. Because most conceptual changes of an entities behaviour involve consistent changes to a set of interrelated object properties, delegation directly supports conceptual modelling and modularity. Unlike structure modication, delegation enables sharing of properties among multiple objects, which in turn enables modelling of (among others) objects that can be regarded from dierent alternative points of view, or dierent versions of an object. The major conceptual restriction of delegation that prevented its use outside prototype-based languages is its (claimed) incompatibility with static typing. Like object structure modication, delegation is typable only under major restrictions (2.2.5). Especially, no system that includes subtyping and delegation is known so far. In view of all these aspects the we may conclude that: 1. Object structure modication as well as delegation eectively support unanticipated behaviour evolution. 2. From the point of view of typing there are no clear arguments in favour

3.5. BEHAVIOUR EVOLUTION IN CLASS-BASED LANGUAGES of either one of both alternatives.

39

3. From the point of view of conceptual modelling, modularity and maintainability delegation is by far superior. Therefore, in the end, delegation is the preferred behaviour evolution mechanism to be integrated into a unied model of class- and prototype-based systems. The apparent incompatibility with static typing and subtyping is a problem that is to be solved as one step towards the unied model.

3.5

Behaviour Evolution in Class-Based Languages

So far we have simply conjectured that the class-based model has the be extended. However, even if the model provides no direct support for behaviour evolution there are ways to simulate behaviour evolution. So let us see whether simulations can achieve the requirements stated in section 3.3. Because they have no object structure modication operations (which would contradict their essential property, creation of objects with xed structure by instantiation), class based languages essentially model object specic properties and their dynamic change by trying to mimic delegation. The class-based variants of the two basic scenarios for modelling anticipated and unanticipated change by simulating delegation (see 3.4.2) are illustrated in Figure 3.1 and Figure 3.2. These scenarios can be regarded as meta-patterns that distill the technical essence common to various design patterns [GHJV95]. The scenario illustrated in Figure 3.1 is characteristic for the yweight, state, and strategy pattern. The scenario illustrated in Figure 3.2 on the following page is characteristic for the bridge, decorator, chain of responsibility and proxy pattern.

ModifiableEntity immutable Interface

AbstractModificationEntity interface to be added, updated or removed

ConcreteME_1 interface implemantation 1

ConcreteME_N interface implemantation N

-> manual message forwarding modifiableEntity concreteME_N

Figure 3.1: Class-based modelling of anticipated property addition, deletion, and update (modulo simulation of delegation) Indeed, patterns like state and strategy, for instance, are conceptually and technically identical. From the conceptual point of view of an analyst both express

40

CHAPTER 3. BEHAVIOUR EVOLUTION

ModificationEntity_1 implementation of added or overridden methods ModifiableEntity basic interface and implementation

ModificationEntity_N implementation of added or overridden methods

modificationEntity_N -> manual message forwarding

modifiableEntity

Figure 3.2: Class-based modelling of unanticipated property addition, deletion, and update (modulo simulation of delegation). The term unanticipated should be read as the expression of an intention rather than as the statement of a fact. The analysis in the next section shows that, when delegation must be simulated, any extension must be anticipated. an anticipated range of variation in the behaviour of an object with the concrete variations being subject to unanticipated renement. From the technical point of view of an application programmer both involve aggregation and message forwarding. Both patterns are just instances of the same scenario applied to dierent problems: either state-dependent behaviour change (state pattern) or externally determined behaviour change (strategy pattern). The gures 3.1 and 3.2 reect only the structural aspects of the two scenarios. What is missing are the operational details of how delegation is simulated.

3.6

Simulation of Delegation

In this section we list the requirements for a faithful simulation of delegation, discuss the classic proposals for simulating delegation, extend them and evaluate them with regard to the stated requirements. We concentrate our discussion on approaches that can be generally applied as a design pattern to any typed, class-based language. Idioms that are built on language-specic features (e.g. [Mey92b, Mey96, Cop92]and patterns that achieve only restricted functionality (e.g. [Hau93b, Hau93a, Hau93c] will not be considered.

3.6.1

Requirements for a Simulation

A faithful simulation of delegation in a strongly-typed, class-based language must meet the following technical (1-3) and usability (4-5) requirements: 1. The information about self , the initial receiver of a delegated message, must be propagated to parent objects. 2. All messages sent to self in parents have to be redirected to the initial receiver of the delegated message, in order to give child objects a chance to override methods of parent objects.

3.6. SIMULATION OF DELEGATION 3. Static type safety must be preserved.

41

4. The simulation must not require modications to child classes and their subclasses when methods are added to parent classes. 5. The simulation must enable unanticipated reuse. In particular, it must not depend on the assumption of a certain structure of the class- or object-level delegation hierarchy, e.g. that a parent object will be shared only by a xed number of children, that only certain classes will ever be used as parents, or that delegation will not be recursive. Depending on the technique for meeting the rst requirement, simulation approaches can broadly be classied in two categories: storing of references to self in the parent and passing of self as an additional argument of delegated messages. It has already been shown in [Kni98] that storing of references to self (employed by [Hau93b, Hau93a, Hau93c], COM [Box98] and the meanwhile withdrawn proposal for an object aggregation and delegation model for Java Beans [CH97]) has a very limited applicability, violating all requirements listed in item 5 above. Therefore, the following evaluation concentrates on the second class of approaches.

3.6.2

Passing self as Message Argument

In dynamically typed languages like Smalltalk, explicit passing of self as a message argument is a common technique for simulating delegation and many authors have adopted this approach also for typed languages [Fow97], [GHJV95], [HOT97]. In this approach methods have to be extended by an additional, explicit sSelf parameter with the convention that explicit messages pass the receiver object as the sSelf argument, i.e. every message object.msg(arg) is replaced by object.msg(object,arg), implicit messages in parents are redirected to sSelf, i.e. every message msg(arg) and self.msg(arg) is replaced by sSelf.msg(sSelf,arg) message forwarding passes the current value of sSelf further up the delegation hierarchy, i.e. if parent is the variable that references the parent object then delegation is simulated by the forwarding message parent.msg(sSelf,arg)

42
sender

CHAPTER 3. BEHAVIOUR EVOLUTION


receiver
object object . m(object) sSelf object . p(object)

delegatee
parent parent . n(object)

Figure 3.3: Simulation of delegation by passing self as message argument

The basic interaction pattern between a message sender, a message receiver and a method holding parent in this approach is illustrated in Figure 3.3. The message msg() sent to object results in the (sub)message n() being delegated to parent and in the message p() being subsequently sent to object, the actual value of the sSelf argument. The actual value of sSelf is shown with each message. The names of formal arguments are shown as role names. This approach works for shared parents as well as for recursive delegation. Moreover, due to the rst rule of the above message sending convention, instances of parent classes can be used on their own as message receivers, not just as parents. Of the requirements stated above only type-safety and support for unanticipated reuse are still to be met. The remainder of this section explores the wide-ranging implications of typing for the simulation and its usability. 3.6.2.1 The Type of self

If the declared type of sSelf were one xed child type, as suggested, for instance, in Fowlers proposal for a Role Object Pattern and in JavaSofts Glasgow proposal [CH97], then delegation from any other child type would be excluded by the type checker, resulting again in a very restricted model (non-recursive, non-shared parents, non-extensible). Moreover, parents which expect sSelf to be of a child type could not be used on their own, because the application of the above normal message sending rule would be vetoed by the type-checker (the parent itself is not substitutable for a sSelf parameter of child type). A better alternative is to declare sSelf to be of an interface type4 that corresponds to the interface of the parent class containing the method5 :
interface ParentInterface { public aMethod(ParentInterface sSelf, ...); } class parent implements ParentInterface { public aMethod(ParentInterface sSelf, ...) { ... sSelf.m(...); // message from parent is sent "back" to sSelf ... C++, Java interfaces translate to purely abstract classes, interface extension is derivation of a purely abstract subclass, and interface implementation is derivation of a concrete subclass. 5 It might appear as a drawback of this solution that parents cannot send messages to sSelf that are specic to a particular child type. If really needed, one can work around this restriction with run-time type checks. However, one should use this technique sparingly, since it again makes the parent dependent on certain child types.
4 In

3.6. SIMULATION OF DELEGATION


<<interface>> I2
...
delegatee

43

<<interface>> I1
m( self: I1 )

Legend Standard UML notation for classes, interfaces, method signatures, aggregation, inheritance and interface conformance

C2
m( self: I1 )

C1
m( self: I1 )

Figure 3.4: Typing of the self parameter (rst approximation).

Child classes implement at least the ParentInterface. Therefore any instance of a child class can be passed in the sSelf argument, making parent classes independent of their potential children and hence reusable in nonanticipated contexts. In Java the substitutability of children for parents has to be made explicit by declaring child interfaces as extensions of parent interfaces:
interface ChildInterface extends ParentInterface ... ;

Figure 3.4 illustrates the basic approach to typing sSelf using Java interfaces. In the example, I1 is the ParentInterface, I2 is the ChildInterface, C1 is a parent class, and C2 is a child class. A child class must contain one method for every signature dened in the parent interface (Figure 3.4). This method will either forward the received message to the parent object or implement local behaviour. Message forwarding is trivial:
class child implements ChildInterface { // "parent" is the object to which messages are forwarded: ParentInterface parent; // A forwarding method: public aMethod(ParentInterface sSelf, ...) { parent.aMethod(sSelf, ...); }

Method overriding is more complicated, due to strong typing. Whereas in child classes it is still manageable (3.6.2.2) it leads to an unsolvable dilemma in subclasses of parent classes (3.6.2.3). 3.6.2.2 Overriding in Child Classes

A local method in class child will more often than not need to call other methods that are specic to child. However, the type-checker will veto any such attempt on the premise that the declared type of sSelf is ParentInterface. Therefore, any local implementation will need a dynamic type check in the rst place to verify whether the current value of sSelf is an object of type

44

CHAPTER 3. BEHAVIOUR EVOLUTION

ChildInterface. Note that the dynamic check is only needed to overcome the pessimism of static typing. In a child class that is not itself a parent of another class, the receiver object is sSelf. Hence the check will succeed because an object can always be cast to its own type. The dynamic check merely recovers type information that was lost while forwarding sSelf up the delegation hierarchy. A child class C2 that provides own behaviour for method m() from the parent interface I1 will appear as shown in Figure 3.5. Methods that perform a checked type cast in order to adjust the type of sSelf are called casting methods 6 and are indicated in diagrams (e.g. Figure 3.5) by a down-arrow () next to the method signature.

class C2 implements I2 { I1 parent; public m(I1 sSelf) { // Will always succeed: I2 selfI2 = (I2) sSelf; // Method body using selfI2: selfI2.n() } public n(I2 sSelf) { ... }
m(I1)

<<interface>> I2
n( self: I2)
delegatee

<<interface>> I1
m( self: I1 )

C2
m( self: I1 ) n( self: I2)

C1
m( self: I1 )

C2

!
m(I1) m(I2) ! n(I2) !

C1

Figure 3.5: Method overriding in child classes

3.6.2.3

Overriding in Subclasses of Parent Classes

The above considerations apply also to subclasses of child classes that are not themselves parents. The situation is subtly dierent if we consider method overriding in subclasses of parent classes. In this case the dynamic type of sSelf is no subtype of the type of the current parent object Thus the cast of sSelf to the parent type will fail. Consider for instance the scenario illustrated in Figure 3.6 on the facing page. The parent class C1 has a subclass (C11) whose type (I11) is a subtype of the declared parent type (I1). An instance of the child class C2 delegates to an instance of C11. Obviously, the type of the child object (I2) is not a subtype of the parent objects type (I11) because the method n() is not contained in I2. Thus an implementation of m() in C11 as a casting method will always produce a run-time error due to the inadmissible cast at the beginning of the method:
class C11 implements I11 { public m(I1 sSelf) {
6 In

GJ [BOSW98] casting methods are called bridge methods.

3.6. SIMULATION OF DELEGATION


<<interface>> I2
... parent

45
<<interface>> I11
n( self: I11)

<<interface>> I1
m( self: I1 )

C2
...

C1
m( self: I1 )

C11
m( self: I1) n( self: I11)

m(...) C2 sSelf

m(...)

C11

n(...)

Figure 3.6: Overriding in subclasses of parent classes

I11 selfI11 = (I11) sSelf; // This cast will always fail!!! ... } }

Obviously, messages specic to C11 (resp. I11) can only be sent to the local object (this in Java) because it is the only guaranteed method holder for these messages:
class C11 implements I11 { public m(I1 sSelf) { sSelf.m(...); // message from I1 is sent to sSelf ... this.n(...); // message from I11-I1 is sent to this } }

In general, messages dened in the static type of this but not in the static type of sSelf (e.g. in I11-I1) can only be sent to this. The distinction between these two message receivers has to be hard-coded into overriding methods in subclasses of parent classes.

Class hierarchy evolution. If programs were static, unchangeable entities, hard-coding the distinction between sSelf and this would be acceptable. However, programs typically evolve. A type like I11, which is a subtype of a declared parent type might become itself a declared parent type when new types and classes are added to the program. For instance, Figure 3.7 on the next page shows the program from Figure 3.6 extended by a child type (I3) and a child class (C3) whose declared parent type is I11. We have to be prepared for such changes in the structure of the delegation hierarchy if our simulation is not to break in the next release of the program. In general, all modications of other parts of a program that do not aect the interface of a given class should not require changes in the implementation of that class [Sny86].

46
<<interface>> I2
... parent

CHAPTER 3. BEHAVIOUR EVOLUTION


<<interface>> I1
m( self: I1 )

<<interface>> I11
n( self: I11) parent

<<interface>> I3
...

C2
...

C1
m( self: I1 )

C11
m( self: I1) n( self: I11)

C3
m( self: I1) n( self: I11)

m(...) C2 sSelf

m(...)

C11

m(...) C3 n(...) sSelf

m(...)

n(...)

Figure 3.7: Program extension that invalidates existing classes (gray background indicates added classes and objects).

Addition of child classes. In the light of the aim to make the simulation independent of assumptions about the specic use of a class, the addition of new child classes poses serious problems. The set of messages that may be sent to sSelf varies depending on the type of child object. Future changes of the program may lead to situations where the same parent object (e.g. c11) may have children of dierent, unrelated types (e.g. C2 and C3). When we write an overriding method within a parent subclass (e.g. C11) we cannot know which type of child object will be used at run-time. Therefore we cannot determine which messages may be sent to sSelf and which may only be sent to this. For instance, in Figure 3.7 the object c11 can either have c2 or c3 as child. In the context of messages delegated from c2, c11 may not send the message n() to sSelf, whereas in the context of messages delegated from c3 it must send the message n() back to c3 (= sSelf) in order to enable overriding. However, since sSelf has type I1, the message n() will never be sent to sSelf. Thus the typing assumptions hardwired into the existing code prevent the new code (from class C3) from being able to override n(). The denition of n() from C3 will never be invoked during the execution of messages delegated to c11. Preparing for change. If we do not want to be forced to change existing code every time new code is added, we need a general criterion to determine which messages may be sent to sSelf, depending on the current value of sSelf. But what can be a suitable criterion? Direct checking whether an object contains a particular method is not feasible without reection. We do not want to base our simulation on reection, because this technique is unavailable in many widely-used languages (e.g. C++ and Eiel), surpasses type checking and involves a dramatic run-time penalty (e.g. in Java, invoking methods via the reection API is reported to be up to 40 times slower than direct invocations). The alternative is to check whether an object is an instance of a particular type, which contains the method we want. Safe checks of type membership are

3.6. SIMULATION OF DELEGATION

47

relatively ecient and generally available (e.g. in Beta, C++, ComponentPascal, Eiel, Java, Modula, Oberon, Simula, ...). The question remains, for which type to check when we want to send a message, say sSelf.m() within class C11 (see Figure 3.7)? We could start by checking whether sSelf is an instance of C11, and, in case of failure, continue by checking the supertypes of C11 that contain m(): C1, I11, and I1. However, all but one of these checks would be redundant. Indeed, it suces to check whether sSelf is an instance of I1, the most general supertype 7 (MGS ) of C11 that contains m(): if the check is positive we can safely send the message; if it is negative, then it will be negative for all subtypes of I1, so we do not have to check them separately. Naturally, we need this check only for methods that are not in the static type of sSelf. This reduces the set of candidate types further: we are interested only in most general supertypes of C among the subtypes of sSelfs static type. For instance, in class C11 from Figure 3.7, messages sSelf.m() need no dynamic check at all (because m() is in sSelfs static type, I1) and messages sSelf.n() only need the check sSelf instanceof I11 (because I11 is the most general supertype of C1 that contains a denition of n() and is a subtype of I1). Iterative safety checks. Now, we have a reasonably simple checking criterion. Still, we have to consider what to do if the check fails. One might be tempted by the illustration in Figure 3.7 to send the message directly to this:
if (sSelf instanceof I11) ((I11) sSelf).n(); else this.n(); // if it is safe // then send the message to sSelf // else send the message to this

However, in general, the chain of delegating objects may consist of more than two objects. Therefore, we must check whether there are other delegating objects between sSelf and this that possibly contain an applicable method:
Object o = sSelf; while (! (o instanceof I11)) { o = o.getparent(); } ((I11) o).n(); // sSelf is the first candidate // while the candidate is not safe // ... get the next delegating object // finally, send the message

As if it wouldnt have been tedious enough to wrap every message that is not in sSelfs static type into an if-clause, the iterative check involves further complications. Not just that it clutters the code even more, making its essential meaning dicult to understand among all the simulation noise. It additionally depends on the assumption that all classes provide a way to determine parent objects, like the getparent() method above. We can request that child classes provide such a method but we cannot assume that children do not have multiple parents. Therefore, the method must return a collection of parents, unlike in the oversimplied scheme just shown. Also, there might be no unique most general supertype from which a particular method originates. Both aspects further complicate the check and make it potentially very inecient.
7 In most languages a class can have multiple independent supertypes. Therefore, in general, we have to check for a message sSelf.method() in the code of a class C whether sSelf is an instance of any of the most general supertypes of C that contain method().

48

CHAPTER 3. BEHAVIOUR EVOLUTION

What is gained? In practice, the drawbacks just mentioned will prevent most programmers from implementing iterative safety checks if there is no immediate need to do so. So let us assume that a program that started out with the initial, simple simulation scheme, evolved in a way that made it necessary to implement iterative checks. After this eort, the programmer can nally be sure that addition of further child classes will not require any more changes of the simulation code. Unfortunately, the code that removes dependency on addition of child classes introduces new dependencies. When superclasses and parent classes are refactored [Fow99] by moving methods up or down the hierarchy the types used for the iterative safety checks have to be modied accordingly. So we have traded undesirable dependencies on child classes for equally undesirable dependencies on superclasses and parent classes plus excessive simulation costs. We may thus conclude that our search for a faithful simulation of delegation in strongly typed object oriented languages has reached a dead end.

3.6.3

Summary

Models for simulating delegation in statically typed class-based languages rely on aggregation and resending of messages within forwarding methods. Of the requirements listed in section 3.6.1 they achieve the technical ones (1-3) at the cost of neglecting most usability aspects (4-5). The main disadvantages of simulations are: the need to anticipate the use of a class as a parent and to build in hooks that allow the correct treatment of self for resent messages. Classes that do not provide such hooks cannot be used as parents. the need to obey rigid coding conventions for implementing the required hooks. On one hand, the absence of a standard convention goes against any attempt to make composable software by simulating delegation. Components made according to dierent conventions cannot be deployed together. On the other hand, the risk of standardizing immature proposals has been recently demonstrated by JavaSofts Object Aggregation and Delegation Model, which had been contained initially in the Glasgow Proposal [Jav97] for a new JavaBeans standard and has been dropped as result of public criticism of its limitations. the need to anticipate which messages in a class can be overridden in child classes (i.e. which ones are sent to sSelf) and which ones can only be redened in subclasses (i.e. which ones are sent to this). We have shown that no acceptable solution exists for this problem in current typed class-based languages. Making a class insensitive to addition of new child classes by implementing the proposed iterative safety check scheme requires considerable programming overhead, clutters the code and makes the class sensitive to refactoring of its supertypes. the need to edit (or at least recompile) a delegating class when the interface of one of its superclasses or parent classes changes (e.g. by addition of a method). This introduces two variants of the syntactic fragile base class problem:

3.7. CHAPTER SUMMARY

49

Fragile superclass: If a superclass is extended by a method that is forwarded in the subclass, the forwarding method must be deleted and the subclass recompiled, otherwise the new inherited behavior would not take eect in the subclass. Fragile parent class: Changes to the parents interface require adding / deleting forwarding methods in child classes in order to propagate the change. In both cases, existing compiled code of subclasses / child classes is invalidated. the tedious and error-prone process of writing forwarding methods, casting methods, explicit interface denitions and explicit subtyping relations among interfaces. This problem could partly be alleviated by intelligent development environments nevertheless, a language design with well-dened semantics is preferable to dependence on the availability and intelligence of a particular tool. Thus, we may conclude that an integration of delegation into the class-based object model is useful because it would signicantly improve the exibility of class-based languages, adding support for modelling object-specic properties, dynamically changing properties, as well as unanticipated changes, and it cannot be simulated without excessive implementation and maintenance costs by the existing facilities of class-based languages.

3.7

Chapter Summary

In this chapter we have stated the requirements for a behaviour evolution mechanism (3.3) and have analysed prototype-based languages (3.4) and class-based languages (3.4.1) in the light of these requirements. We have compared structure change and delegation (3.4.3) as well as delegation and class-based design patterns (3.6.3). We now proceed to summarise the lessons learned and to draw conclusions as to what appear to be the most promising candidate mechanisms for a unied model. Class-Based Systems. Traditional class-based systems do not eectively support behaviour evolution. The class-based model cannot easily express changes in the structure or behaviour of an object. Required behaviour evolution has to be hard-coded into application programs. As seen in the previous section, manually coded behaviour evolution is a possible workaround for the limitations of the classbased model but not a satisfactory solution. The essence of various design patterns suggested in this context are more or less faithful simulations of delegation. These simulations do not achieve the full functionality of delegation (see. 3.6.3). Because the functionality missing from the language is simulated by a set of cooperating classes, these classes tend to be tightly dependent on each other in

50

CHAPTER 3. BEHAVIOUR EVOLUTION

ways that are not grounded in the application but just in the technical details of the pattern. Such additional dependencies and assumptions built into the design require consistent changes in a complete hierarchy of classes even for small unanticipated changes in the application logic, e.g. addition of a method to a class or addition of a new class (see. 3.6.2.3 and 3.6.3). Thus the application of design patterns for simulating behaviour evolution can impede reuse (of existing code and designs) and complicate program maintenance, achieving the opposite of what is widely regarded as a main benet of object oriented programming. Therefore, extension of traditional class-based object models by a mechanism for unanticipated behaviour evolution is ultimately required. Prototype-based Systems. Prototype-based languages oer two built-in concepts for modelling object-specic properties and their unanticipated, dynamic change: object structure modication (3.4.1) and delegation (3.4.2). Object-structure modication works at the level of individual properties. Due to this extreme granularity and its missing support for sharing, object structure modication is verbose, error-prone, and incompatible with modular programming and conceptual modelling. Delegation avoids these problems. It allows programmers to break down a conceptual entity into an object that represents its permanent properties and in dierent objects that represent groups of properties that can be added, removed, or mutually exchanged. Thus the problem of how to manage dynamic behaviour change is separated from the problem of providing chunks of consistent behaviour that may be interchanged. As a consequence of this separation of concerns the resulting behaviour change code is trivially simple and easy to maintain. Most importantly, adding new behaviour that can be dynamically acquired does not require any changes to existing objects. This stands in sharp contrast to the use of object property modication. From the point of view of typing there are no clear arguments in favour of either one of both mechanisms (2.2.5.6). Proposals for typed delegation are limited to static delegation to objects whose exact type is known statically. Neither subtyping nor dynamic delegation is supported. In contrast, there is a signicant number of proposals for typed systems based on object structure modication. However, none of them provides an unrestricted, generally applicable and semantically sound solution to behaviour evolution in the presence of subtyping. On balance, delegation is the preferred behaviour evolution mechanism to be integrated into a unied model. The reconciliation of delegation with static typing and subtyping is a problem that is to be solved as part of the integration process. This problem is harder than the related problem in the reviewed prototype-based object calculi (2.2.5) because, in contrast to these calculi, delegation allows an object to lose properties to a certain extent (3.4.2). The advantages to be gained from typed dynamic delegation are, however, worth the eort. Conclusion. In the nal analysis, the most promising approach to unanticipated behaviour evolution is the integration of delegation into statically typed class-based models. This is the approach that will be pursued in the second part of this work.

3.7. CHAPTER SUMMARY Problems to be solved as part of the integration process include

51

1. the reconciliation of dynamic delegation with static typing and subtyping, 2. the development of a unied model and of an exemplary language, and 3. the development of ecient implementation techniques for static and dynamic delegation.

52

CHAPTER 3. BEHAVIOUR EVOLUTION

Part II

Darwin

53

Chapter 4

Static Delegation with Subtyping


4.1 Part and Chapter Overview

This part describes the Darwin model. It discusses problems and solutions related to type-safe delegation at a general, language-independent level. Occasionally, dierent alternative options are presented and compared but, in general, no commitments to a specic solution are made. Decisions for one option or the other cannot be made only with regard to a single aspect of a language. Dierent factors tend to interact in possibly complex ways. Therefore this part focuses on the analysis of technical problems and their possible solutions. Decisions as to which set of choices appears best are presented in the next part, which describes the design and implementation of the Lava language. This chapter introduces the basic aspects of Darwin. First, the relation of Darwin to traditional object models is described in section 4.2 and section 4.3. The extension of traditional models by two forwarding mechanisms (delegation and consultation), including corresponding terminology and graphical notation is introduced in section 4.4 and 4.5. Section 4.6 describes the type system. The sections 4.7 to 4.9 discuss necessary conditions for type-safe delegation. An extension of dynamic binding that guarantees type-safe static delegation with subtyping is presented in section 4.11. Section 4.12 goes beyond type-safety, basing dynamic binding on a criterion for semantic compatibility. It enables unanticipated independent extensions of dierent parts of a delegation hierarchy without risk of accidental overriding of methods with incompatible semantics. This chapter concludes with a summary section (4.14). Chapter 5 presents and compares dierent approaches to safe dynamic delegation with subtyping.

4.2

Classes and Prototypes in Darwin

The focus of Darwin is the safe integration of delegation with static typing and subtyping. In this context, it is irrelevant whether the delegating objects are 55

56

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING

self-contained objects in a prototype-based model or whether they are specied incrementally by a class hierarchy in a class-based model. The presentation of Darwin is mainly class-based. Even if the class-based model is more complex, it is nevertheless more familiar to most readers. Therefore, I hope that a class-based presentation eases understanding of the basic ideas behind Darwin and of their impact on practical modelling and programming tasks. Furthermore, in order to make examples as independent as possible from the syntax of a specic programming language, the standard UML notation [BRJ97] is used, which is essentially class-based. Last but not least, the class-based presentation of the general ideas eases the understanding of the Lava design in the next part. However, all essential denitions have been formulated to be applicable to prototype-based systems as well. Where this was not possible, dierent increasingly complex denitions have been presented, starting with simple object-based ones, extending them to class-based ones and occasionally complementing the general presentation by a Java-specic denition.

4.3

Darwin as a Class-Based Object Model

The version of Darwin introduced in this chapter is an extension of strongly typed, class-based object models. This section summarises the common classbased aspects of Darwin. Objects. Objects consist of attributes (variables) and methods (operations). The attributes and methods of an object are also called its properties. Each object has a unique identity. Classes. Each object is a direct instance of one class, say C, and an indirect instance of all superclasses of C. Classes specify the type of their instances, their attributes (variables), and the implementation of their methods (operations). Types. The Darwin object model is strongly typed [CW85]. Typing issues and especially the implications of delegation on type-safety are discussed in detail in section 4.6 to 4.11. Inheritance. Classes may inherit from superclasses.

4.4

Delegation in Darwin

Darwin supports both forms of automatic message forwarding introduced in section 2.2.4: delegation and consultation. In the following the term forwarding is used to denote both relations. Declared Parents and Children. In Darwin objects may automatically forward locally inapplicable messages to the objects referenced by their parent attributes. Parent attributes are declared in an objects class by adding the keyword delegatee (for delegation) or consultee (for consultation) to an instance

4.4. DELEGATION IN DARWIN

57

variable declaration. If class C declares a parent attribute of type T we say that T is a declared parent type of C (and of C s subclasses), and C is a child class of T (and of T s subtypes). A declared parent type may be either concrete (a class) or abstract (an interface). Classes used as declared parent types are called declared parent classes. Objects referenced by parent attributes are called parent objects or simply parents. Potential Parents and Children. A subtype of a declared parent type of class C is called a potential parent type of C. The parent types of a class are the union of its declared and potential parent types. Classes that implement (abstract) parent types are called potential parent classes. The parent classes of a class are the union of its declared and potential parent classes. Ancestors and Descendants. At class level, the declared ancestor relation is the transitive closure of the declared parent relation and the descendant relation is the transitive closure of the child relation. At object level, the ancestor (resp. descendant) relation is the transitive closure of the parent (resp. child) relation. Dynamic Delegation and Consultation. Since a parent attribute can reference any value that conforms to its declared type, assignment to a parent attribute can be used to change (apparently) the behavior of an object at runtime by changing its parent object(s). This is called dynamic delegation (resp. dynamic consultation). If the value of a parent attribute may not change after initialisation we talk about static delegation (or static consultation).

4.4.1

Split Objects

In a delegation-based model parents are shared parts of objects [CUCH91b]. An object and its delegation parents (and ancestors) form one conceptual entity, called a split object [DMC92], whose constituent parts cooperate in answering messages. If o is a delegating object then o denotes the corresponding split object, which includes all ancestors of o. For instance, in Figure 4.3 on page 59, a1 = a1 bn = a1 bn (c2 |d) where denotes delegation and (...|...) denotes multiple parents of the same object. In the case of dynamic delegation the composition of a split object o can vary, the only xed subobject being o itself. In Figure 4.3, for instance, two possible compositions of b1 are shown. Please observe that b1 = b1 an is indeed legal, due to the fact that child classes are subtypes of their declared parent types (section 4.6.3). So a1 is a legal value for a parent attribute of declared type C. This has wide-ranging implications, increasing the expressiveness of Darwin but also the technical problems encountered in making Darwin

58

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING

type-safe. Related issues are discussed, among others, in section 4.6.5, 5.1 and 5.5.

4.4.2

Mandatory Attributes

An attribute is called mandatory if its value is always dierent from null, otherwise it is called optional 1 . Note that always includes object construction: a mandatory attribute must be given a value prior to execution of any other code during construction of an object. The new value must be computed prior to calling the constructor and must be passed as an argument of the constructor invocation. In Lava, attributes are declared to be mandatory by adding the keyword mandatory to their declaration. Assignments to mandatory attributes are checked by the compiler and the run-time system in order to enforce their dening invariant. The exact checks performed in the Java implementation of Darwin are described in chapter 6. The concept of mandatory attributes is particularly important for parent attributes. It helps to guarantee that a parent object to which locally unknown messages can be delegated will always exist. This is an essential prerequisite for treating child classes as subtypes of their declared parent types and for the type-safety of delegation. These issues are discussed in detail in section 4.6 and 4.8.

4.5

Graphical Notation

The structure of Darwin programs is illustrated graphically using an extension of the Unied Modelling Language [BRJ97] to delegation and consultation. Delegation is depicted as an aggregation relation with two additional, nested arrowheads (Figure 4.1): the outer, white arrowhead points in the direction of automatic forwarding (to the parent), whereas the inner, black arrowhead points to self (to the child). The dual notation inner arrowhead pointing to the parent depicts consultation, i.e. automatic forwarding with binding of self to the parent resp. to the method holder. The object diagram notation (Figure 4.2 on the facing page) slightly diers from standard UML in that it depicts objects as boxes with rounded corners
1 This

terminology has been adopted from [Kni96b] and [Cos98].

Inheritance
Superclass Declared Child Class

Delegation
Declared Parent Type

Consultation
Subclass1 Subclass n Declared Child Class Declared Parent Type

Figure 4.1: Extended UML class diagram notation for delegation and consultation

4.5. GRAPHICAL NOTATION


1. initialMsg() child self 1.6 > delegatedSubMsg( ) 1.6.3. subSubMsgToSelf() Notation: child object permanent reference temporary reference 1.6.3. msg()
> msg()

59
parent

message sequence number message delegated message

Figure 4.2: Notation for object collaboration diagrams for better visual dierentiation from classes. An object label of the form x is used as an abbreviation for x : X in UML (meaning that the object labelled x is an instance of class X). Permanent references (instance variable values) are depicted by solid arrows. Temporary references (values of local variables and arguments of a method, including the value of the implicit self argument) are depicted by dashed arrows. As customary in collaboration diagrams, references may be labelled with messages and hierarchical message sequence numbers, which indicate the order of (possibly nested) messages. A program graph is an extended UML class diagram including aggregation, delegation, consultation and inheritance relationships. Object hierarchies are usually shown together with the program graph that

D0

...
A1 An B1 Bn D

...

a1

bn

c1

d b1 c2

b1

an

c3

Figure 4.3: Split objects and their dening program graph

60

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING

denes which object references are parent attributes (e.g. Figure 4.3 on the page before2 ). Therefore no extra object-level notation for delegation and consultation is introduced. Similarly, no extra notation is needed for the distinction of mandatory and optional attributes. Mandatory attributes may be indicated by specifying the cardinality 1 for the parent type. Optional attributes are indicated by cardinality 0,1 for the parent type. For simplicity, cardinality 1 (mandatory) is assumed for parent attributes in all examples that contain no explicit cardinality specication.

4.6

Type System

Although the main goal of Darwin is type safety, this work does not aim at developing a type system. On the contrary, its basic approach is to develop an operational semantics which is intended to be as independent as possible of any particular type system. Stressing this independence, one could have dened a set of minimal assumptions about the type system and explained Darwin with respect to these very general assumptions. However, such a presentation would be needlessly complicated to understand and to relate to real systems. Therefore, this presentation of Darwin includes a type system that is simple enough to ease understanding yet complex enough to include all aspects that are relevant for delegation. The type system denes the notion of type, subtype, applicability and safety. Notation. The notation a b means a is equal by denition to b and a b means a is syntactically identical to b.

4.6.1

Interface Types

Darwin builds on the well-known concept of interface types [CCHO89]. The notion of signature, interface and type are dened as follows: Denition 4.1 (Signature) A method signature, = name(T1 , ..., Tn) : T0 , species the name, argument types and result type of a method. is the set of all signatures: {i |i = mi (Tj1 , . . . , Tji ) : Tj0 }

Variables are treated like pairs of accessor methods (setV ar(value) and getV ar()) that cannot be overridden. The non-overriding property of accessor methods is modeled by including them only in the client interface of a type, but not in its specialisation interface (see below). Denition 4.2 (Interface) An interface I is a, possibly empty, set of signatures.
observe that the split object at the bottom of Figure 4.3 is indeed legal, due to the fact that child classes are subtypes of their declared parent types (section 4.6.3).
2 Please

4.6. TYPE SYSTEM

61

A type describes the properties of an object in an abstract way by two interfaces. The client interface describes the properties of an object that may be called by its message sending clients. The specialisation interface describes the properties that may be overridden in delegating child objects. Denition 4.3 (Type) A type T is a pair of interfaces: T (Iclient , Ispecial )

The client interface is the same as the public interface in Java and C++. The specialisation interface appears to be the same as the protected interface of Java and C++. However, the analogy should not conceal the fact that the specialisation interface is dened at object-level. In Darwin a class indeed denes two interfaces related to method overriding: one mentioning what can be overridden in child objects and one mentioning what can be overridden in subclasses. The latter corresponds to the protected interface of Java an C++. In general, the specialisation interface and the protected interface have the same contents the only exception are the split-self classes introduced in Chapter 5. The specialisation interface is used for statically type-checking messages to self and as an additional criterion for subtyping: in order to be usable in a given context an object must full the expectations of clients in that context with respect to the messages it can answer and the expectations of delegation children with respect to the methods they can override. For notational convenience we say that a signature is contained in a type if it is contained in either of its interfaces: T Iclient Ispecial

Similarly, the union of two types is their component-wise union: T1 T2 (I1client I2client , I1special I2special )

4.6.2

Typing of Delegation

Delegation extends the type of an object by all the signatures from the declared types of its parent attributes3 .Unlike inheritance it does not make the additional properties part of the objects template. So delegation extends the interface of an object but not its physical structure. Because interface and physical structure of split objects do not coincide, delegation requires to distinguish between dierent notions of type of an object: The physical type of an object o, denoted [o], contains the signatures of properties that are dened in the class of the object and its superclasses (or in the object itself, if a prototype-based model is used). It corresponds to the traditional type notion for atomic objects. Their possible role as components of a split object is reected only in the distinction
consultation extends the type of an object by all the signatures from the client interface of any declared parent type.
3 Similarly,

62

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING between the client and the specialisation interface. In a class-based language, like Java, the physical type can be identied with the class from which an object has been instantiated. The expected compound type of an object o, denoted [o] , includes its physical type and the declared types of its mandatory parent attributes. It is invariant with respect to the composition of the split object and species all the messages that are guaranteed to be answered by the object or its parents4 . The concrete compound type of an object o at a given time, denoted [[o]], is the union of the physical types of the object and of all its current ancestors. It may vary depending on the current composition of the split object.

In contrast to the above notions which denote dynamic object types of increasing precision, the static type of an expression, denoted expr , is the type of expr known to the compiler. It is inferred from the type annotations contained in a program and from the typing rules of the programming language5 . Inferred static types approximate (pessimistically) the expected compound type of objects to which an expression will evaluate at run-time.

4.6.3

Subtyping

In a traditional object model, subtyping considers only the public interface of an object. In a model with delegation, the public and specialisation interface must be used together as a criterion for subtyping: in order to be usable in a given context an object must full the expectations of clients in that context with respect to the messages it can answer and the expectations of delegating children with respect to the methods they can override. The idea that a type may only be subsumed by another one that provides at least the same guarantees regarding message sending as well as overriding is expressed by the following denition. It is an adaptation of the widely-used width subtyping relation [AC96], in which a longer object type is a subtype of a shorter object type: Denition 4.4 (Non-variant subtype) An object type T2 is a subtype of another type T1 ,written T2 T1 , if and only if each interface of T2 is a superset of the corresponding interface of T1 : (I2client , I2special ) (I1client , I1special ) I2client I1client I2special I1special

Delegation children are subtypes. According to this structural subtyping rule, the expected compound type of a split object is a subtype of each of the objects declared parent types.
guarantee can only be given for complete objects (see 4.8) typing rules that are unrelated to delegation are not listed here since they are wellknown and do not contribute further to the understanding of delegation.
5 The 4 This

4.6. TYPE SYSTEM

63

Note that the type of an object dened by consultation is no subtype of its parent types according to the above denition, because consultation does not inherit the specialisation interface of declared parent types.

4.6.4

Declared Subtyping

Darwin does not rely on a purely structural notion of subtypes. Explicit declaration of subtypes gives programmers a way to express those subtype relations that are really meaningful for a given program. The notions of semantic compatibility and semantic subtyping introduced in section 4.12 and 5.2 take advantage of this additional semantic information. If is a structural subtyping relation, denotes a compatible declared subtyping relation, i.e. a subtyping relation that is explicitly declared by the programmer but does not contradict the structural subtyping rules. Formally, is simply a subset of . Denition 4.5 (Declared subtyping) If is a structural subtyping relation then a declared subtyping relation, , is an explicitly declared subset of : T1 T2 = T1 T2

The freedom of declared subtyping to eliminate elements from the structural subtype relation is constrained by rule 4.4 on page 70.

4.6.5

Atomic instances

The importance of the fact that delegation children are subtypes cannot be underestimated. It is an essential prerequisite for fully unanticipated extension. For instance, it enables a decorator implemented via delegation to be passed into all contexts where an instance of the decorated type is expected, thus preventing the need to change any clients. There are, however, situations where passing of a child instance instead of an instance of the declared type is undesirable, for technical reasons as well as application semantics6 . For this purpose the notion of atomic instances is introduced. Intuitively, an atomic instance of a type is an object that implements itself the type rather than relying on forwarding to other objects that provide suitable implementations. Denition 4.6 (atomic instance) The set of atomic instances of a type T , denoted @T , is the set of all objects whose physical type is a subtype of T but that do not forward themselves to objects of type T , neither directly nor indirectly. @T atomicInstance(o, T ) {o | atomicInstance(o, T ) } [o] T a ancestors(o) : [a] T

6 Examples that will be treated in this work include the type-safety problem introduced by incomplete objects (section 4.8 on page 66), covariant redenition of self in child types in conjunction with dynamic delegation (section 5.5 on page 93), and security (section 6.9.1 on page 127).

64

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING

An atomic instance of a type T can still be a split object that delegates to ancestor objects of types dierent from T . This is less restrictive than being an atomic object, which implies that there is no delegation at all. Atomic instances and atomic types. Types are often identied with the set of their instances. A set of objects can be regarded as the denition of a type and a subset as the denition of a subtype. The subtype-as-object-subset rule is consistent with the subtyping of interface types: if the interface type T2 is a subtype of interface type T1 then the set of legal instances of T2 is a subset of the set of legal instances of T1 . On the basis of this formal justication, an expression @T denes a type that is a subtype of T . Therefore, we also call @T an atomic subtype of T . The symbol @, read at, should be a mnemonic for atomic. From the same arguments it follows that if S is a subtype of T but not a descendant of T , then @S is a subtype of @T . For convenience, we will often say that an expression has atomic type @T instead of the more precise but convoluted the values of an expression are restricted to atomic instances of type T .

4.6.6

Type-Safety

The main use of a type system is to determine whether messages are safe. In a nutshell, a message is safe if its receiver contains a method that may be used to answer the message. Such methods are said to be applicable to the message. The type system denes safety and applicability. Both notions can be dened on types and signatures instead of relating them directly to objects and methods. Syntactic notions of applicability and safety are introduced in this section. They capture the usual notion of type safety. Semantic notions, which additionally ask whether the method used to answer a message has the expected meaning, are introduced in section 4.12. Denition 4.7 (Syntactic applicability) A signature = m(T1 , ..., Tn) : T is syntactically applicable to a method call c = m(e1 , ..., en) if the static type of each actual argument expression ei is a subtype of the corresponding formal argument type Ti : synApplicable(m(T1 , ..., Tn) : T, m(e1, ..., en)) i=1...n ei Ti

Safety may be checked either statically, by a compiler, or dynamically, during program execution. Static safety relies on the distinction between the client interface and the specialisation interface of a type (section 4.6.1 on page 60). Only messages to self may use methods from the specialisation interface. Denition 4.8 (Static type-safety) A message to self is statically safe if the static type of self contains a syntactically applicable signature: statSafesyn (self.call) self synApplicable(, call)

4.7. SAFE FORWARDING

65

A message to a receiver expression dierent from self is statically safe if the client interface of its static type contains a syntactically applicable signature: statSafesyn (recv.call) recv self recv = (Iclient , Ispecial ) Iclient synApplicable(, call)

At run-time, objects may receive messages sent to self by their delegation parents in addition to the messages captured by the static check. Therefore, at run-time, type-safety is evaluated with respect to all methods of an object, those in the public as well as those in the specialisation interface: Denition 4.9 (Local / abstract / dynamic type-safety) A method call c = m(e1 , ..., en) is locally / abstractly / dynamically safe for an object if the objects physical / expected compound / concrete compound type contains a (syntactically) applicable signature: locSafesyn (call, obj) abstSafesyn (call, obj) dynSafesyn (call, obj) [obj] synApplicable(, call) [obj] synApplicable(, call) [[obj]] synApplicable(, call)

4.7

Safe Forwarding

The benet of Darwin over previous approaches is that it guarantees safe delegation (and consultation). This means that for a message sent to a non-null receiver the dynamic binding process will terminate and will nd an applicable method. This is a stronger guarantee than type-safety, which just guarantees that an applicable method will be found provided that the search terminates. The next section introduces the notion of incomplete objects, explains the problems raised by incomplete objects with respect to termination and typesafety and shows how they can be avoided. Avoidance of incomplete objects is a common prerequisite for the safety of delegation and consultation. It is indeed the only prerequisite for safe consultation. The special typing problems of delegation will be the topic of the main part of this work. Safety of static delegation with subtyping is discussed in the remainder of this chapter, starting from section 4.11 on page 71. Safe dynamic delegation with subtyping is the topic of chapter 5.

66

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING

4.8

Incomplete Objects

The restriction that abstract classes may not be instantiated is well-known and commonly accepted in traditional class-based languages. It prevents the creation of objects that do not contain implementations for all the properties declared by their class. Although the basic idea is the same, the issue of incompleteness is peculiar to split objects: Denition 4.10 (Incomplete objects) An object o is incomplete if its concrete compound type is not a subtype of its expected compound type: incomplete(o) [[o]] [o]

A split object o may become incomplete if its parent objects do not provide the expected methods. In spite of static type-checking and of the constraint that abstract classes must not be instantiated, this may happen in two cases: if the parent is null and if the parent is itself a child of o, i.e. if there is a forwarding cycle. Denition 4.11 (Forwarding cycle and cyclic assignment) A forwarding cycle is a a sequence of references from an object to itself such that every reference on the cyclic path is the value of a parent attribute. We say that an assignment is cyclic, if, after the assignment, there is a forwarding cycle that did not exist before. Forwarding cycles create incomplete split objects if, taken together, the objects in the cycle do not provide implementations for all the methods from the declared type of the assigned parent attribute. Incomplete objects due to forwarding cycles compromise termination of dynamic binding: the search for a method from the expected compound type of the receiver will loop forever if the method is not contained in its concrete compound type. Incomplete objects due to null parents compromise type-safety: the search for an applicable method will terminate but fail. Both cases are clearly undesirable, so the following rule is not surprising: Rule 4.1 (No incomplete objects) Incomplete objects must not be created. In the remainder of this section we will discuss how this rule can be enforced. Preventing incomplete objects due to null parents is easy because we can rely on the concept of mandatory attributes: Rule 4.2 (Parents are mandatory) Parent attributes must be declared to be mandatory.

4.8. INCOMPLETE OBJECTS

67

The mandatory declaration obliges the compiler and run-time system to enforce that the respective attribute will never hold the null value (see 4.4 on page 56 and 6.4 on page 114). The remainder of this section discusses ways to prevent incomplete objects due to forwarding cycles. The two possible causes of such cycles are the subtype relation between children and their declared parents and cycles in the program graph.

4.8.1

Forwarding Cycles due to Subtyping

Because the type of a child object is a subtype of each of the objects declared parent types, cyclic assignments to parent attributes are possible even in fully acyclic programs. The assignment of an object as its own parent is illustrated in Figure 4.4. After the assignment, the object child is incomplete since it has no parent that provides the methods from the declared parent type.

legal class hierarchy

Child c() { ... p() ... }

Parent p() { ... }

illegal object hierarchy

child

Figure 4.4: Incomplete cyclic object due to subtyping Because the problem of cyclic assignments is intrinsic to the type system it cannot be solved by static analysis. Therefore, it appears that the only option is to treat it at run-time. Option 4.1 (Dynamic cycle prevention) Assignments to mandatory parent attributes must be checked at run-time to guarantee that no cyclic assignments that create incomplete objects are performed. Dynamically checking whether a forwarding cycle really creates an incomplete object may be prohibitively expensive. Therefore concrete implementations may choose to perform simpler, yet more restrictive checks. The simplest solution is to detect and generally prohibit any forwarding cycle. Fortunately, an alternative to dynamic checking exists. Because the too permissive subtyping relation is the cause of our problems a simple solution is to come up with another, more restrictive subtyping relation. In fact, we do not have to invent anything new. What we need is already provided by the notion of atomic instances (denition 4.6 on page 63): an atomic instance of type T never has an ancestor object of type T . Hence, it cannot have itself as an ancestor. The denition of atomic instances prevents cyclic assignments.

68

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING

Option 4.2 (Static cycle prevention) The values of parent attributes must be atomic instances of their declared type. So cycles due to subtyping can be prevented statically. Because unsafe cycles in the program graph can also be detected at compile-time, a completely static solution is possible.

4.8.2

Forwarding Cycles in the Program Graph

Cycles consisting of delegation links, consultation links, and of any combination of delegation, consultation and inheritance links7 in a program graph are legal, subject to the usual subtyping rules and the constraint that no incomplete objects may be created. Subtyping. The inuence of subtyping is straightforward: because each of the classes in a forwarding cycle is a subtype of the others all have exactly the same type. If C1 , ..., Cn are the classes participating in the cycle then T ypeC1 ... T ypeCn T ypeC1 implies T ypeC1 = ... = T ypeCn . Thus properties that override each other in C1 , ..., Cn must have identical (nonvariant) signatures, even if the employed subtyping relation would be more permissive. Incomplete objects. The previous section has shown that incomplete objects can be created even from fully acyclic programs, due to the extended subtyping relation, which accepts child objects as instances of a declared parent type. Even if this eect were excluded (e.g. by reverting to the traditional subtyping relation of Java, which accepts only subclass instances), incomplete objects could still be created. This would be possible if cycles in the class graph, abstract types (e.g. interfaces and abstract classes in Java) and declared subtyping are used together. When a class that is part of a cycle declares that it implements some abstract type, the compiler may not accept a forwarding relation as a fulllment of that declaration. Otherwise, the program from Figure 4.5 on the facing page would be accepted, although neither class A nor class B contains an implementation of method m()8 . Instead, the compiler must enforce the following constraint: Rule 4.3 (Legal class-level cycles) If Tabstract is the union of all abstract types declared to be implemented by classes from a cycle then for each signature from Tabstract at least one method implementation must exist in some class from the cycle (or one of its superclasses)
only forbidden cycles are pure subclassing cycles, as usual. example is oversimplied: the constructor of class B would be rejected by the compiler because it initialises a mandatory attribute with a value that has not been passed as argument.
8 The 7 The

4.8. INCOMPLETE OBJECTS

69

interface I { void m()} class A implements I { mandatory delegatee B b; A(B b) { b = b;} }


A

I m( )

<<implements>> B

class B implements I { mandatory delegatee A a; B() { a = new A(this);} }


a b

(a) Lava code

(b) Class and object graph

Figure 4.5: A cyclic program that would create incomplete objects or at least one of the classes from the cycle must be declared to be abstract thus preventing its instantiation.

The rst condition ensures that object-level cycles consisting only of instances of classes from the class-level cycle will not be incomplete. The second condition ensures that a class-level cycle that does not provide all the methods that it declares cannot be used to create object-level cycles.

4.8.3

Cycle Summary

Class-based inheritance is typically acyclic. Therefore it may come as a surprise that delegation and consultation are not necessarily subject to this restriction. Conceptually, forwarding cycles are legal, subject to the usual subtyping rules and the constraint that no incomplete objects may be created. Two options for the prevention of incomplete objects have been presented: Static Prevention Cycles due to the subtyping relation between mandatory children and their declared parents are prevented statically by giving parent attributes a more restrictive type. Cycles at class-level are rejected or accepted subject to a simple static analysis. Dynamic Prevention The attempt to create cycles is detected at run-time by checks inserted before each assignment to a mandatory parent attribute. Negative checks result in run-time errors (exceptions). When dynamic checks are performed, detecting and prohibiting unsafe forwarding cycles in the program graph would not make the system any safer. It would, nevertheless, make its semantics more consistent, since it would not create the illusion that certain programs are legal, although all object structures instantiated from them will be rejected at run-time. When aiming for consistency, however, the checks performed at program graph level will have to be exactly the same as those performed dynamically otherwise, there would

70

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING

still be statically legal programs from which no objects can be instantiated at run-time. Ensuring this is easy if the dynamic checks are very restrictive. For instance, if the dynamic checks prevent any cycles, the class graph may contain no such cycles either. So, paradoxically, more precise run-time checks, which are desirable on one hand in order to make the approach less restrictive, are undesirable on the other hand, because they would compromise the consistency of the static and dynamic semantics of a program. This discrepancy is unavoidable if the run-time checks are more precise than the static ones, due to the more precise information available at run-time. In the nal analysis, the static prevention approach is obviously more ecient and accepts all cycles that can statically be shown not to create incomplete objects. It also guarantees that the run-time behaviour of the program presents no surprises to programmers: any statically accepted cycle will be legal at run-time. So the tradeo between the static and the dynamic approach is the tradeo between restricting parent attribute values to atomic instances versus reduced eciency and a possible mismatch between static and dynamic semantics. Which alternative presents a bigger practical restriction strongly depends on the chosen dynamic check and, therefore, remains to be evaluated on dierent implementations. The current implementation of Lava (Chapter 7) uses dynamic checks.

4.9

Delegation Children Must be Subtypes

Avoidance of incomplete objects is a necessary and sucient condition for typesafe consultation. Due to its more complex nature, delegation is more demanding. Passing of a child instance as the value of self in a method of a parent object is the dening characteristic of delegation. Therefore the type of the child object must be a subtype of its declared parent type. Without this constraint delegation will be type-unsafe right from the start. So the rst rule of type-safe delegation is: Rule 4.4 (Delegation needs subtyping) Delegating child classes must be subtypes of all their declared parent types. This requirement is obviously satised by the rule that all forwarding relations must be mandatory (rule 4.2 on page 66), the denition of compound object types based on mandatory attributes (section 4.6.2 on page 61), and the structural subtyping relation from denition 4.4 on page 62. So it might seem that rule 4.4 is redundant. It is not, because it also constrains systems with declared subtyping that implement the Darwin model for instance, it constrains the subtype relation that may be dened for Lava (chapter 6).

4.10

Complete Objects in Unsafe Contexts

So far, Darwin provides two guarantees: 1. delegation will never have to deal with incomplete objects and

4.11. STATIC DELEGATION WITH SUBTYPING

71

2. the expected compound type of a delegation child is a subtype of all its declared parent types. The rst guarantee ensures that every object created at run-time is able to answer all messages from its expected compound type. The second one ensures that it is safe to pass a child object as the value of self to a method of a declared parent type DP T . Formally, it is guaranteed that in a method of a declared parent type, values of self are always abstractly safe, that is their expected compound type is a subtypes of the static type of self : [eval(self)] self = DP T We can now address the question how it may happen that delegation passes references to complete objects into contexts where they are abstractly unsafe and how this error can be repaired or prevented. Recall that abstract unsafety means that the evaluation of an expression expr may produce references to objects whose expected compound type is not a subtype of the expressions static type: [eval(expr)] expr

Repair means to let the error happen if it can be guaranteed that the dynamic type of the object resulting from the evaluation of an expression satises the subtype constraint [[eval(expr)]] expr which in turn guarantees that all statically safe messages are dynamically safe. Prevention means to ensure that the error does not happen in the rst place. Then the expected compound type of an object resulting from the evaluation of an expression is always a subtype of the expressions static type [eval(expr)] expr .

Abstract safety implies dynamic safety but not vice versa. Put dierently, prevention is more restrictive. From the point of view of expressiveness, repair is therefore preferable, whenever possible. A repair approach for static delegation is presented next. Chapter 5 evaluates two dierent repair approaches for dynamic delegation, shows that they lead to semantic inconsistencies, and presents two better behaved prevention approaches.

4.11

Static Delegation with Subtyping

The typing problem. Dierent authors claimed that delegation cannot be safely combined with static typing and subtyping [AC96, FM94]. They argued that when instances of subtypes of the declared parent type may be used as parents, run-time type errors would occur. Translated to the Darwin model, this scenario is illustrated in Figure 4.6 on the following page: c, an instance of class Child, delegates to p, an instance of a potential parent class of Child. The argument of Fisher and Mitchell [FM94] is related to the type inconsistency of the two methods named bang dened in Child and PotentialParent

72

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING


Child bang (arg:StockExchange) DeclaredParent t()

PotentialParent t () { self.bang(aBomb) } bang(arg:Bomb) 1. t() c : Child 1.1 > t() p: PotentialParent 1.1.1. bang(aBomb)

Figure 4.6: Unsafety of delegation to subtype instances

Child contains a method bang(arg:StockExchange) whereas PotentialParent contains a method bang(arg:Bomb). During the evaluation of the message c.t() delegated from c to p, the message self.bang(aBomb) sent back to c is unsafe, because its argument does not have the expected type, StockExchange9 . The cause of the unsafe message is that Child, the physical type of the object bound to self during the evaluation of the delegated message t(), is not a subtype of self s static type, PotentialParent. In general, parent objects that are instances of potential parent types can send unsafe messages to self . If they pass the value of self as a parameter or result, other objects will also send unsafe (explicit) messages. The latter case is illustrated in Figure 4.7 on the next page, which is just a slight variation of the example considered so far. In both cases, it looks as if the static type system betrayed the trust of the parent object p by enabling an unsafe assignment. Correspondingly, a delegated message that is bound to a method in an instance of a potential parent type is called a traitor message, the respective instance is called the betrayed object and the unsafe value of self passed by the traitor message to the bound method is called the intruder. In Figure 4.6 and Figure 4.7 on the next page t() is the traitor message, p is the betrayed object and c is the intruder. The solution. The conclusion of Fisher and Mitchell about the unsafe interaction of delegation with subtyping is true from the point of view of a traditional object model and type system. However, in a delegation-based model parents are shared parts of objects [CUCH91b]. An object and its delegation parents form one conceptual entity, a split object, whose constituent parts cooperate in answering messages. Locally unsafe messages are delegated to parents. As long as parent objects are not exchanged dynamically, all that is required as a solution for the apparent typing problem is to acknowledge the split object nature of the message receiver and apply the general dynamic binding principle characteristic of split objects: delegation of locally unsafe messages. That is exactly what is to be done also in the above example: as illustrated
9A

similar problem would occur if Child did not contain any bang() method at all.

4.11. STATIC DELEGATION WITH SUBTYPING


Child ... DeclaredParent PP = PotentialParent) t(arg:Other) Other pp:PP PotentialParent t(arg:Other) { arg.store(self) } bang() store(arg:PP) { pp = arg } ask() { pp.bang() }

73

1. t(other)

intruder: Child

1.1 > t (other) 2.1.1 -> bang()

betrayedObject: PotentialParent

1.1.1

store(child)

other

2. ask()

2.1. bang()

Figure 4.7: Propagation of unsafe values throughout the program. The intruder may be passed into other contexts where an object of type PotentialParent is expected. It may thus receive locally unsafe messages also from objects dierent from the betrayed one.

in Figure 4.8, the locally unsafe bang(aBomb) message just has to be delegated further to the parent object p. Being the sender of the self.bang(aBomb) message, p is guaranteed to possess an applicable method.
.1 > t () .1.1.1. -> bang(aBomb) .1.1. bang(aBomb)

. t()

p: PotentialParent

c : Child

Figure 4.8: Safe completion of the example from Figure 4.6 on page 72 Please observe that p is also guaranteed to possess an applicable method for every message sent to c by other (Figure 4.7). In p self has static type PotentialParent. Therefore, self can only be assigned to variables whose static type, say T , is equal to or a supertype of PotentialParent other assignments are prevented by the static type check. The static check also prevents messages that are not in T being invoked on these variables. So every message sent by other to c will be contained in T and its subtype, PotentialParent.

4.11.1

Extended Dynamic Binding

We now give a precise denition of the extended dynamic binding algorithm sketched above. The version presented here is applicable to single static delegation10 . When each object has a single direct parent which can be initialised at run-time but cannot be exchanged afterwards, dynamic binding is fairly simple:
delegation is not treated in this work. The reasons for this deliberate omission are discussed in section 6.5.1 on page 118.
10 Multiple

74

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING Locally safe messages are bound to the local method or eld body in the searched object whereas locally unsafe ones are delegated to the parent object. If the message is a eld access then the eld body already is the result. If the message is a method call then the method body is evaluated recursively.

This process is implemented by the procedures eval, evallocal and evaldeleg in denition 4.12. Each has an expression as its rst argument. The second argument of evallocal and evaldeleg is the initial receiver of the message. Their third argument is the object currently searched by the evaluation process, the method holder. Notation. Without loss of generality, the unique parent attribute of an object o is assumed to be f ield1 . For better readability it is denoted also as parent. ref The expression bodyi denotes the body of the i-th eld or method of the object referenced by ref. Substitution of the values val for the variable var within the expression expr is denoted expr{ |var val| . } Denition 4.12 (Dynamic binding for single static delegation) The evaluation of a message of eld access expression recv.call is dened as eval(recv.call) obj = eval(recv); if locSafesyn (call, obj) then return(evallocal (call, obj, obj)); else return(evaldeleg (call, obj, evallocal (parent, obj, obj))); evallocal (f ieldi , receiver, holder) holder return(bodyi ); evallocal (methi (), receiver, holder) holder return(eval(bodyi { self receiver | )); | } evaldeleg (call, receiver, holder) if locSafesyn (call, p) then return(evalLocal(call, receiver, holder)); else return(evaldeleg (call, receiver, evallocal (parent, receiver, holder)));

Note that the evaluation of a eld accesses the object referenced by the holder parameter. This means that the result of eld access expressions is not inuenced by delegation. Field accesses are bound at object creation time, whereas methods are bound at message sending time. If child classes should be able to override a eld access expression then that expression should be replaced by the invocation of an accessor method. The accessor method can be overridden.

4.11. STATIC DELEGATION WITH SUBTYPING

75

4.11.2

Static delegation is type-safe

We can now state the rst main result of this work: Theorem 4.1 (Type-safety of static delegation) For static delegation, the dynamic binding algorithm from denition 4.12 preserves static type-safety, i.e. it guarantees that all statically type-safe messages are dynamically type-safe. statSaf esyn (expr.call) = dynSaf esyn (call, eval(expr)) Proof Outline. This result can be veried easily by considering the scenarios that may lead to sending of locally unsafe messages to an object: 1. In the rst place, it is obvious that as long as messages are not delegated, no unsafe messages will be sent at run-time just as in any delegation-free statically typed object model. 2. The same holds for delegated messages, when the parent objects dynamic type is the same as its static type (as in Cecil). 3. The interesting case is when a delegated message nds an applicable method in an instance of a potential parent type (the betrayed object). Then the expected compound type of the object bound to the self pseudovariable (the intruder) is not a subtype of the declared type of self in that method. There are two possible continuations of this scenario that might lead to unsafe messages: In the method executed by the betrayed object, messages might be directly sent to the intruder via self (see Figure 4.6) or self might be assigned to a variable, returned as a result or passed as an argument of a message, thus letting the intruder escape from the self pseudovariable into places where it may receive messages sent by objects dierent from the betrayed one (see Figure 4.7). In both cases, it is guaranteed that all expressions that can possibly return a reference to the intruder either are of the betrayed objects type or of a supertype otherwise the assignment of self to that expression would have been rejected by the type-checker. Hence only messages contained in the betrayed objects type will be sent to these expressions. If a message is locally safe for the intruder it will be answered immediately. A locally unsafe message will be delegated, possibly visiting a number of parent objects for which it is unsafe too. However, sooner or later, it will reach the betrayed object, which is itself a parent of the intruder (see Figure 4.8 on page 73): by denition, the betrayed object was a parent of the intruder at the time when the intruder was bound to self and it is guaranteed still to be among the intruders parents, because it was assumed that parents are not exchanged dynamically (dynamic delegation is discussed in chapter 5).

76

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING

Type safety follows from the fact that the betrayed object is guaranteed to possess an applicable method for every message from its own physical type. We may thus conclude that static delegation is type-safe even in the presence of subtyping. Any statically safe message received by a split object is dynamically safe: even if the message is unsafe with respect to the expected compound type of the receiving object it is safe with respect to its concrete compound type because there always exists a parent object that has an applicable method. Darwin departs from the traditional approach of typed object models in that its operational semantics tolerates temporary violations of static type constraints when the dynamic type of a split object is guaranteed to be safe. Instead of enforcing a restrictive static type discipline, which prevents type errors, Darwin guarantees that its extended dynamic binding will nd the parent object that satises the static type constraints.

4.12

Independent Extensibility

From the point of view of type safety the description of Darwin given so far can be considered complete. However, with a view to Darwins practical relevance, there still is one subtle yet important aspect to be considered before moving on to the discussion of dynamic delegation. Independent Extensibility and Overriding. During the discussion of basic typing problems the astute reader might have already noted that the classes Child and PotentialParent in Figure 4.6 on page 72 may have been developed and compiled independently, knowing only DeclaredParent but not each other. Therefore, even if the two independently introduced denitions of bang() had the same signature (as illustrated in Figure 4.9 on page 78) it would still be very unlikely that they have the same semantics. Overriding of the bang method from Parent by the bang method from Child would therefore be undesirable anyway, because it would silently change the semantics relied upon by methods from Parent, leading to obscure, hard to locate errors. It is obviously necessary to be able to use independently developed subclasses (PotentialParent) and child classes (Child) without fear of undesired side-eects. Especially for component-oriented programming independent extensibility is essential ([Szy96, Szy98]). Therefore, a generally applicable delegation scheme must go beyond purely syntactic type-safety based on signature compatibility it must also include criteria for semantic compatibility. Overriding and Semantic Compatibility. The point of the above discussion of type-safety and independent extensibility is that both seemingly unrelated problems have a common cause: the implicit assumption that methods with the same name (and possible same signature) are semantically compatible and may thus override each other. This assumption can be safely made only if it is guaranteed that the author of the overriding method knows which methods it overrides. This is always the case with class-based inheritance: a method implementation in a class can only override others in a known superclass. Assuming a sensible documentation of

4.12. INDEPENDENT EXTENSIBILITY

77

the superclass it is safe to assume that authors of subclasses are aware of the impact of method overriding. Such a guarantee cannot be given if independent extensibility via delegation is possible, because each declared parent type may have multiple, unknown implementations. Therefore, a model with practical relevance must base overriding on stronger criteria for semantic compatibility. A solution could be the use of a richer type system, which describes also semantic aspects of each method (behavioural types, e.g. [Ame91]). Unfortunately, behavioural types still have not made their way into widely used object-oriented languages. The solution proposed here is less ambitious but generally applicable. Two methods are considered semantically compatible if the declared subtyping relation provides evidence that they are both semantically compatible to a method in a common supertype. Denition 4.13 (Semantic compatibility) Type T1 is semantically compatible to type T2 with respect to the signature if is contained in T1 , T2 and some common declared supertype T of T1 and T2 : semCompat(, T1 , T2) T1 T2 T ( T T1 T T2 T )

Note that subtyping must be declared explicitly (the denition uses , not the structural subtyping relation ). Otherwise, any two types that contain the same method signature would have the non-empty implicit common supertype {}. Explicit supertype declaration carries the additional semantic information that is essential for independent extensibility: the common declared supertype is a common semantic base on which implementors of independent extensions can rely. Note that the denition assumes identical signatures. This is essential. If any signature change was permitted, then letting a childs method override a parent method would lead to errors. An example is given in the comparison of subtyping in Lava and Darwin in section 6.9.1 on page 129. Semantic compatibility can be used to dene a semantic notion of safety: Denition 4.14 (Semantic safety wrt an object) A message is semantically safe for an object if the type of the object (1) contains a syntactically applicable signature and (2) is semantically compatible with the static type of the receiver expression with respect to that signature: locSafesem (recv.call, obj) abstSafesem (recv.call, obj) dynSafesem (recv.call, obj) [obj] synApplicable(, call) semCompat(, recv , [obj]) [obj] synApplicable(, call) semCompat(, recv , [obj] ) [[obj]] synApplicable(, call) semCompat(, recv , [[obj]])

78

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING

The conjunction of syntactic applicability (see denition 4.7 on page 64) and semantic compatibility is also called semantic applicability. More precisely: Denition 4.15 (Semantic applicability) A signature from type T is semantically applicable to a method call c = m(expr1 , ..., exprn) invoked on a receiver expression of type Trecv if is syntactically applicable to c and the types T and Trecv are semantically compatible with respect to : semApplicable(, c, T, Trecv) synApplicable(, c) semCompat(, T, Trecv )

Using semantic applicability, semantic safety can be dened more concisely: A message is semantically safe for an object if the type of the object contains a semantically applicable signature. Applicability plays an important role in the specication of Javas dynamic binding behaviour (see [GJS96, p. 325]). Recasting the denition of safety to make explicit where applicability ts in, eases the integration of Darwin and Java pursued in chapter 6. Whereas Java is based on syntactic applicability (see [GJS96, p. 325]) Darwin and Lava support semantic applicability (see chapter 6). Semantic compatibility and applicability are illustrated in Figure 4.9-a and -b, two variations of Figure 4.6 on page 72 in which all bang() methods have the same signature. In Figure 4.9-a DeclaredParent is a common declared supertype that contains bang(), hence all bang() methods are considered semantically compatible. Correspondingly, the bang() method from c is applicable to the self.bang() message sent by p. In Figure 4.9-b there is no such common declared supertype, hence the bang() denitions in Child and PotentialParent are considered incompatible and the method from c is inapplicable to the message from p.

Child bang() { ... }

DeclaredParent b() bang()

Child bang() { ... }

DeclaredParent b()

Parent common origin of bang() b() { self.bang() } bang() { ... }

Parent different origin of bang() b() { self.bang() } bang() { ... }

b() c : Child

b() p : Parent self.bang()

b() c : Child

b() p : Parent

self.bang()

a) Semantically applicable: overriding enabled

b) Semantically inapplicable: no overriding

Figure 4.9: Semantic applicability and overriding

4.13. DELEGATION CHILDREN MUST BE SUBTYPES

79

4.12.1

Dynamic Binding

The denition of a more powerful safety check directly inuences the behaviour of dynamic binding. The following version of the dynamic binding algorithm is the same as the one presented in denition 4.12 on page 74, up to the checks for semantic safety (changes are highlighted by underlining):

Denition 4.16 (Dynamic binding for single static delegation) The evaluation of a message expression recv.call is dened recursively as eval(recv.call) obj = eval(recv); if locSafesem (recv.call, obj) then return(evallocal (call, obj, obj)); else return(evaldeleg (recv.call, obj, evallocal (parent, obj, obj))); evaldeleg (recv.call, r, h) if locSafesem (recv.call, h) then return(evallocal (call, r, h)); else return(evaldeleg (recv.call, r, evallocal (parent, r, h)));

The eect of the change is illustrated in Figure 4.9 on the facing page. In Figure 4.9a the message self.bang(...) sent from p to c will nd a semantically applicable method in its receiver object. In Figure 4.9b the same message will not nd a semantically applicable method in c and will therefore be delegated further up the object hierarchy, back to p (where the search will succeed). The inuence of semantic applicability resp. semantic safety on dynamic binding can alternatively be described by saying that methods in a child object will only override methods from its declared ancestor types.

4.13

Delegation Children Must be Subtypes

If declared parent types were no declared supertypes of their child classes then none of the messages to self sent in a parent object would pass the semantic compatibility test. They would all be delegated and none of the redened child methods would eectively override any parent method. Without overriding, the delegation relationship declared in a child class would degenerate to consultation, although it would still perform the redirection of self messages to the child object. So, delegation without subtyping would be just a horribly inecient form of consultation. These considerations provide a second line of arguments why delegation children must be subtypes (see 4.9 on page 70). A language that implements the Darwin model must guarantee that this constraint will always be enforced. Allowing a class to use delegation without enforcing that it will be treated as a subtype of the declared parent type is illegal.

80

CHAPTER 4. STATIC DELEGATION WITH SUBTYPING

4.14

Summary

This chapter has presented the basic aspects of the Darwin model. It has shown how consultation and delegation can be introduced into a statically typed classbased environment, creating the opportunity to construct split objects whose composition can evolve at run-time in unanticipated ways. The main focus of the chapter has been the discussion of safety problems that can arise from such an extension: the risk of creating incomplete objects, which do not contain methods for all the signatures from their expected compound type and the risk of passing complete objects into contexts where they are statically unsafe, i.e. where their expected compound type is not a subtype of the expressions static type. A solution for the problem of incomplete objects has been presented, which consists of the rule that parent attributes are mandatory (rule 4.2 on page 66) and two alternative options for preventing cyclic incomplete objects (section 4.8.3 on page 69). Regarding the second problem it has been shown that for static delegation it has no negative eect. Static delegation ensures that the concrete compound type of an object will be safe in any context where a reference to the object can be passed. So, every message that is statically safe is dynamically safe. The unanticipated extension that can be achieved via delegation enables dynamic composition of independent extensions of the same base type. This is a very powerful technique but it entails the risk that components which have not been designed to work together might interact in undesirable ways. In particular, the risk of undesired overriding of semantically unrelated methods has been discussed, and a semantic compatibility criterion based on declared subtyping has been presented. It smoothly integrates with the operational semantics of delegation presented in this chapter, providing the guarantee that dynamic composition of split objects from instances of independently created classes will be type-safe and will not lead to accidental overriding of incompatible methods.

Chapter 5

Dynamic Delegation with Subtyping


This chapter is devoted to type-safe dynamic delegation. It rst discusses the typing problem that arises when the dynamic binding semantics introduced for static delegation in chapter 4 is applied in a system that allows delegation attributes to be reassigned at run-time. Then it presents dierent solution proposals: pure specialisation (section 5.2), use of frozen states (section 5.3), waiting for future states (section 5.4), and the invariant split self approach (section 5.5). A comparison of all proposals with respect to the criteria introduced in section 3.3 concludes the chapter. Its results provide the basis for the language design introduced in chapter 6. Readers who are not interested in what does not go may safely skip section 5.2 to 5.4.

5.1

Unsafety of Dynamic Delegation with Subtyping

The additional problem introduced by dynamic delegation is illustrated in Figure 5.1 on page 82: when a locally unsafe message is delegated, the only safe method holder for that message might have been replaced by another parent object. If the new parent is of a dierent type, it might not contain a matching method. With respect to the new composition of the split object the message would be dynamically unsafe. The example in Figure 5.1 is an extension of the one from Figure 4.6 on page 72 by dynamic delegation. Now the traitor() method in PotentialParent rst calls self.n(), thus changing the intruders parent (compare composition of the split object intruder in part a) and b) of Figure 5.1). 81

82

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

Child n() { self.setParent( new ... ) } setParent (arg:DeclaredParent)

DeclaredParent traitor n() m () ()

PotentialParent traitor () { self.n() self.bang() self.m() } bang()

1.1.1.1 setParent(...

) 1.1. > traitor() 1.1.1. n ()

. traitor()

intruder: Child

betrayedObject: PotentialParent

a) State immediately before exchange of the parent object

1.1.2.1 > bang()

n ew: DeclaredParent

intruder: Child 1.1.2. bang()

betrayedObject: PotentialParent

b) After parent exchange the bang() message would be delegated to an object that has no applicable method

Figure 5.1: If parents that are instances of subclasses are exchanged at run-time, an abstractly unsafe message might not nd a safe method holder among the receivers current parents.

Since child classes are subtypes of their declared parent classes, their instances can also be passed into contexts of declared parent type. In particular, they can also be passed as values of parent attributes. Therefore, the scenario shown in Figure 5.2 on page 83, in which the parent of intruder is an instance of a child type, is legal. The problem is the same as the one illustrated in Figure 5.1 and it follows exactly the same pattern as soon as the parent of intruder is replaced, previously safe messages cannot nd a safe method holder any more. The only dierence is that now the betrayedObject is an instance of a child class instead of a subclass. The two examples show that dynamic delegation may be unsafe whenever objects that are instances of subtypes of declared parent types are used as parents regardless of whether these subtypes are subclasses or child classes. Before discussing possible solutions it is worthwile to consider the driving forces that interact in making the above examples unsafe:

5.1. UNSAFETY OF DYNAMIC DELEGATION WITH SUBTYPING

83

PotentialParent traitor() { self.n() self.bang() self.m() } bang()

Child n () { self.setParent( new ... ) } setParent(arg:DeclaredParent)

DeclaredParent traitor( ) n( ) m ()

1.1.1.1 setParent(...)

1. traitor() 1.1.1. n()

intruder: Child

1.1. > traitor() betrayedObject: PotentialParent c: Child dp: DeclaredParent

a) State immediately before exchange of the parent object

1.1.2. bang( )

intruder: Child

1.1.2.1. > bang()

new: DeclaredParent

betrayedObject: PotentialParent

c: Child

dp: DeclaredParent

b) After parent exchange the bang() message would be delegated to an object that has no applicable method

Figure 5.2: The same problem as in Figure 5.1 arises if parent objects that are instances of child types are exchanged dynamically

1. Instances of any subtype of the declared parent type are accepted as new parents. 2. Parent objects expect that delegating children are subtypes of their own type. Thus they send them messages from their own type and pass them further into other contexts where objects of this type are expected. 3. If messages are evaluated with respect to the most recent state of a split object, the parent object that provided the only applicable method might no longer be available when the message is evaluated. Unsafety arises only if all of the above forces interact. Correspondingly, each of the dierent solution proposals presented in the remainder of this chapter tries to eliminate one of these causes. Depending on the factor that is eliminated these proposals strongly dier in their range of applicability, safety and semantic consistency.

84

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

5.2

Pure Specialisation

A rst step towards type-safe dynamic delegation is to note that any replacement of a parent of type Told by a new parent of type Tnew is safe as long as Tnew is a subtype of Told . Put dierently, safety could be achieved by eliminating the rst cause mentioned above: instead of accepting objects of any subtype of the declared parent type, assignments to delegation attributes should accept only subtypes of the concrete compound type of the previous parent object. A corresponding check could be included in the code that performs assignments to delegation attributes thus preventing unsafe assignments1 .This is called the pure specialisation approach. The practical utility of pure specialisation strongly depends on the employed notion of subtype. Consider for instance the delegation-based variant of the strategy pattern (Figure 5.3 on page 84), assuming that the subtyping between AbstractStrategy and each of the ConcreteStrategyi classes is explicitly declared. Although all ConcreteStrategyi classes have the same interface, it will not be possible to switch from one strategy to another, in a system that relies solely on declared subtyping. With structural subtyping, all ConcreteStrategyi classes are trivial subtypes of each other, hence strategy switching would be possible. The downside of structural subtyping, however, is that any structural subtype will be accepted, including e.g. the semantically unrelated type Other (Figure 5.3).
Context contextInterface() AbstractStrategy algorithm () Other algorithm ()

ConcreteStrategy1 algorithm ()

ConcreteStrategyN algorithm ()

Figure 5.3: The strategy pattern [GHJV95] with dynamic delegation An acceptable notion of subtype should ideally combine the exibility of structural subtyping with the semantic information provided by declared subtyping. Therefore the notion of semantically compatible subtype is introduced. It builds on the semantic compatibility of types with respect to a common signature (denition 4.13). Denition 5.1 (semantically compatible subtype) Type T1 is a semantically compatible subtype of type T2 if T1 is semantically compatible to T2 with respect to all signatures from T2 .
1 Remember that assignments to delegation attributes are guarded by a check to prevent assignment of null and possibly also cyclic assignments (see section 4.8). Therefore the programmer anyway has to catch exceptions possibly produced by a parent change.

5.3. ACCESSING FROZEN OBJECT STATE

85

T1 T2

T2 semCompat(, T1 , T2)

As desired, semantic subtyping prevents switching to instances of the semantically unrelated type Other but enables switching between instances of any of the sibling classes ConcreteStrategyi and from an instance of a class to an instance of a subclass (e.g. from ConcreteStrategyi to ConcreteStrategySubi ). However, even with the added exibility of semantically compatible subtyping many interesting cases cannot be captured. In Figure 5.4 on the next page, for instance, it is not possible to switch from a ConcreteStrategySubi back to any superclass. Even worse, the scenario in Figure 5.4, in which each of the ConcreteStrategyi classes employs some specic local methods, precludes almost any interesting strategy change because there is no more semantic subtyping relation between sibling classes.
Context contextInterface () AbstractStrategy algorithm() Other algorithm ()

ConcreteStrategy1 algorithm() special_1()

ConcreteStrategyN algorithm() special_N()

ConcreteStrategySub1 algorithm() special_1_1()

Figure 5.4: A version of the strategy pattern in which semantic subtyping between sibling classes fails So pure specialisation is safe and sound but much too restrictive to be of signicant practical use.

5.3

Accessing Frozen Object State

Aiming at a more general solution it is helpful to reconsider the source of the problem. As a consequence of dynamic delegation a split object may change in three ways: it may change the implementations of methods from its declared parent types, it may acquire methods that are not part of its expected compound type and it may again abandon the acquired methods.

86

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

The described type problem arises from the fact that abandoning of acquired methods takes eect even in contexts where objects of a type that includes these methods are expected. Therefore, it appears sensible to delay method abandoning in contexts that depend on abandoned methods.

5.3.1

Partially Frozen Object State

In a delegation-based system, delay of method abandoning means delay of parent exchange. Thus, in a nutshell, a general solution could be to continue using the betrayed object2 as a parent, for all messages that depend on its specic methods. All other messages use the receiver object and its current parents, thus guaranteeing the expected behaviour with respect to the current composition of the split object. Put dierently, all messages that are abstractly safe for a split object are evaluated with respect to its current composition, whereas messages that are abstractly unsafe are evaluated with respect to its composition that contained the betrayed object for the respective message hence with respect to the composition that was guaranteed to be dynamically safe. This approach is called partial freezing because part of an old state of a split object is conserved (frozen) for use in contexts that depend on this state. Methods are regarded to be part of an object mutable state since delegation enables to achieve conceptualy the eect of method addition, deletion and update. An example. Let us illustrate the semantics of partial freezing on the example from Figure 5.1 on page 82. In that example, the split object intruder with expected compound type {setParent(), traitor(), n(), m()} had acquired the method bang() by acquiring betrayedObject as a parent. Thus, the assignment of intruder to self was safe with respect to the concrete compound type of intruder at that time. It then abandoned the additional method again by acquiring a parent object of another type. With partial freezing, this change will take eect for all messages that are safe for the intruder s expected compound type. For instance, the message self.m() will be delegated to the new parent, new, and will thus be bound to the new implementation of m(). However, the change will not take eect for messages that are unsafe for the intruder s expected compound type. For instance, the message self.bang(), which will still be delegated to the former parent betrayedObject, thus guaranteeing its safe execution (Figure 5.5 on the next page). Frozen paths. Unlike in the above example, the betrayed object may in general be an ancestor, not an immediate parent of the message receiver. In this case, the betrayed object is still the only method holder that is guaranteed to exist, but there might be other, more specic method holding ancestors of the message receiver.
2 The rst time when a reference to a child object c is passed into a context where an object of a potential parent type is expected, c is guaranteed to have a parent p of the expected type. The object p is called the betrayed object (see section 4.11).

5.3. ACCESSING FROZEN OBJECT STATE

87

1.1.3.1 > m()

n ew: DeclaredParent

intruder : Child

1.1.2.1 > bang() 1.1.2. bang() 1.1.3. m()

betrayedObject: PotentialParent

Figure 5.5: Frozen path approach: Safe completion of the example from Figure 5.1 on page 82 (replaces part b of Figure 5.1). The thick dotted arrow represents the frozen path used for delegating the unsafe message to a safe method holder In order to be able to nd the most specic method holder for an unsafe message, one has to know the full frozen path, i.e. the path from the message receiver to the betrayed object that was traversed by the traitor message (compare Figure 5.5 and Figure 5.1a). It is called the frozen path, because it is still used for delegating unsafe messages, even if the object hierarchy has changed. With frozen paths dynamic binding does not proceed directly to the betrayed object but sequentially visits the objects on the frozen path until one with an applicable method is found. Due to dynamic delegation an object o may have betrayed parents of various, incompatible types at dierent stages of its life. So references to o may be passed into contexts that rely on the methods of dierent betrayed parents. In order to produce the expected behaviour in every context a specic frozen path has to be associated to every reference. For an object o, the term o|f p denotes a reference to o with associated frozen path f p. The frozen path f p is either empty or has the structure o1 |f p1 , . . . , om |f pm , with o1 to om being the parent objects of o traversed during delegation (o1 is the immediate parent of o and om is the betrayed object). For simplicity, a reference with empty frozen path is identied with the object to which it refers and written o instead of o|empty . Dynamic binding. Informally, an operational semantics for dynamic delegation based on partial freezing can be described by the following rules: 1. Every reference to a newly created object has an empty frozen path. 2. Normal message sending, parameter passing and assignment does not change frozen paths. 3. Locally safe messages are bound to the value of the respective property in the searched object. 4. Locally unsafe messages that are abstractly safe are delegated to the current parent object and the traversed reference3 is appended to the frozen path of the reference passed as the value for self .
reference through which a parent object has been accessed by a delegated message is called the traversed reference.
3 The

88

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING 5. All other unsafe messages are delegated along the frozen path associated to the object reference of the receiver.

For single delegation, dynamic binding with partial freezing is dened recursively by the functions eval, evalDeleg and evalDelegV iaF P listed in denition 5.2. The third informal rule above is implemented by lines 3,4,14,15 of the algorithm and by the procedure evallocal from denition 4.12 on page 74, which has not been reproduced here. The fourth rule corresponds to lines 5-10 and 13-20. The fth rule corresponds to lines 11 and 21-24.

Denition 5.2 (dynamic binding with partial freezing) eval(recv.call) obj|f p = eval(recv); if locSafesyn (call, obj) then return(evallocal (call, obj, obj)); else { if abstSafesyn (call, obj) then { next = evallocal (parent, obj, obj); return(evaldeleg (call, obj|next , next)); } else return(evaldelegV iaF P (call, obj|f p , fp)); } evaldeleg (call, s, p) if locSafesyn (call, p) then return(evallocal (call, s, p)); else { next = evallocal (parent, s, p); obj|o1 , ..., on = s; return(evaldeleg (call, obj|o1, ..., on, next , next)); } evaldelegV iaF P (call, s, |o1 , o2 , ..., om ) if locSafesyn (call, o1) then return(evallocal (call, s, o1 )); else return(evaldelegV iaF P (call, s, |o2 , ..., om ));

In the previous chapter, it has been shown that delegation is safe if the betrayed object is guaranteed still to be among the receivers parents at the time when a message is evaluated. Partial freezing preserves exactly this invariant even in the context of dynamic delegation. We may thus conclude that type safety can be enforced even when subtyping and dynamic delegation are combined. However, focusing our consideration only on type-safety would be rather short-sighted, as demonstrated below.

5.3. ACCESSING FROZEN OBJECT STATE

89

5.3.2

Type-Safety is not enough

In section 4.12 it has been shown that assuring the existence of some syntactically applicable method is not enough one would also like to know whether the method has the expected semantics. Therefore the semantic compatibility criterion has been introduced and integrated into the dynamic binding process. In the context of dynamic delegtion even type-safety that respects semantic compatibility is not enough4 semantic inconsistencies can arise due to the fact that dierent messages access dierent conceptual states of a split object. This is no surprise. It is an intrinsic problem of object-oriented programming. Even for atomic objects, problems may arise whenever one object is allowed to access mutable state of another one [IWA99]. For instance, consider a virtual reality environment that lets visitors interact via so-called avatars, which are virtual substitutes for the interacting persons. The following code
public class Visitor { Avatar avatar = ...; boolean idle = avatar.idle(); public void moveTo(Coord c) { if (idle) avatar.moveTo(c); }

might not produce the expected behaviour if the stored value does not reect the real state of the avatar, which might have been modied by messages sent to the it between the assignment to idle and the call of moveTo(). If this is undesired, the program can be rewritten easily:
public void moveTo(Coord c) { if (avatar.idle()) avatar.moveTo(c); }

This version will produce the expected behaviour in a non-concurrent environment. If concurrency is an issue one can add proper locking code:
public void moveTo(Coord c) { synchronized(avatar) { if (avatar.idle()) avatar.moveTo(c); }

What is wrong with freezing. The point of discussing consistency issues that are intrinsic to object-oriented programming is to illustrate that in every case the problematic portions of code can be identied and the problems can be xed.
version of denition 5.2 based on semantic safety is straightforward to derive following the schema shown in denition 4.16 on page 79.
4A

90

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

The pitfall of freezing is that it creates programs in which these two properties do not hold any more. In the rst place, programmers are not aware any more that certain code might cause problems. For instance, even the synchronized version of moveTo() might produce unexpected behaviour. Part a) and b) of Figure 5.6 on page 91 illustrate how the problem manifests itself for partial freezing. The example shows an application of dynamic delegation to our assumed virtual reality environment. Delegation is used to implement mutable avatars, which can change their appearance and behaviour in the course of a session. Each type of avatar may perform according to its character. Standard avatars have a xed way of performing. Mutable avatars can adapt their performance to reactions of their players. They pass their players a reference to themselves in order to enable bidirectional interaction. The problems of freezing become evident in the course of the following scenario: 1. The object mutable is is an instance of MutableAv and a child of the interactive avatar intractive. Its expected compound type contains the method move() but not the method idle(). 2. When the perform() message is sent to mutable, a reference to mutable is assigned to the avatar variable of player. The reference has an associated frozen path that refers to intractive. This is illustrated in part a). 3. Then mutable changes its parent, referring now to another avatar. Part b) and c) illustrate the state of the involved objects after this step. 4. Finally, the moveTo() message is sent to player. Due to partial freezing, the two subsequent messages are adressed to dierent parents of mutable: The abstractly unsafe idle() message is delegated via the frozen path to the outdated parent interactive the abstractly safe moveTo() message is delegated to the current parent other . This is illustrated in part b) of the gure. So other will be moved on the basis of a test performed on interactive. Although the programmer of Player has done everything to ensure consistent execution, the intended invariant that only idle interactive avatars are moved will be violated. Worst of all, the programmer will not notice what happened until a strange bug occurs in a possibly remote part of the program. Fixing this kind of errors may be extremely dicult because one has almost no clues as where to start searching: usually one would not expect that a standard avatar, for instance, has been moved by code meant to manipulate interactive avatars only.

5.3.3

Fully Frozen Object State

Looking at the previous example one might think that the inconsistency could be avoided if dynamic binding was based on full freezing. Full freezing means that all locally unsafe messages are delegated via the frozen path. Indeed, if all messages are delegated along the frozen path then the intended invariant will be maintained in the previous example: if the test performed on the state of balloon succeds then balloon will be moved.

5.3. ACCESSING FROZEN OBJECT STATE

91

MutableAv ...

Avatar Coord position() moveTo (Coord) perform () Player register (arg:IAV) { avatar = arg } move(...) { ... see text... } test (...) { ... see text... }

StandardAv perform()

InteractiveAv perform () { ... ; p.register(self) } idle()

avatar

a) Creation and dissemination of a reference with frozen path


1. perform() mutable 1.1 > perform() 1.1.1 store (self) interactive frozen path player

b) Inconsistency of partial freezing


3.2.1 -> move() mutable 3. 1.1 -> idle() 3.1. idle() 3.2 move()

other

3 . move() interactive frozen path p layer

c) Inconsistency of full freezing


4.2.1 -> position() mutable 4.1.1 -> position() 4.1. position() 4.2. position()

other

interactive frozen path

4 . test() player

Figure 5.6: Inconsistency due to freezing: a) state before parent exchange, b) inconsistancy due to partial freezing, c) inconsistancy due to full freezing.

92

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

However, the maxime that examples are a language designers trap, coined by the designers of Self [SU95], proves once more to be true. Although full freezing helps in the above scenario it fails in others. Consider the following small extension to our example in conjunction with part c) of Figure 5.6. The method test() of class Player has an Avatar parameter and tests whether it is in the same as the avatar stored locally:
public Boolean test(Avatar arg) { if ((avatar == arg) && (avatar.position() != arg.position()) print("Huu? The same object in different places?"); }

If a reference to mutable with no (resp. empty) frozen path is passed as a parameter5 , then the full freezing approach will lead to execution of the two dierent position() messages on dierent parents of mutable. The message sent via the reference stored in the avatar variable will be delegated along the frozen path associated to that reference. The message sent via the reference passed as argument will be delegated to the current parent of mutable. So the paradoxical situation would arise that the same object would appear to be in dierent places at the same time. Finally, it is worthwile noting that the described problems do not depend on the existence of a Player. The code from Player could be moved to InteractiveAv itself with the result that the same problems would arise in the execution of messages to self sent by interactive. So full freezing does not provide the expected benet. On the contrary, its primary eect is that even abstractly safe messages access outdated ancestors although they could safely be answered by the current ancestors of the receiver.

5.3.4

Freezing Summary

Both freezing approaches enable unrestricted, type-safe dynamic delegation with subtyping at the price of introducing semantic inconsistencies that cannot be circumvented by programmers. The inconsistency problem described here for full freezing are exactly the same as those pointed out in our critique of [RS98] (see 2.2.5.5). This is no surprise because the proposal of Riecke and Stone is also based on the idea of full freezing. Instead of freezing a delegation path within a split object, they freeze the result of dynamic binding in an atomic object (by replacing it with static binding). The use of dynamic binding for explicit messages and static binding for messages to self raises exactly the same inconsistency as the use of current parents in some contexts and of frozen paths in others. So we may conclude that in the context of programming language semantics any kind of freezing is not a good idea, even if it helps ensure type-safety. Typesafety alone is not enough.
will be the case if the method is invoked from a context where the reference is type-safe. For instance, if the object mutable invokes player.test(this).
5 This

5.4. WAITING FOR FUTURE SAFE STATE

93

5.4

Waiting for Future Safe State

The previous section has shown that using a safe but outdated state of an object in certain contexts and the current state in other contexts is no acceptable solution, due to the semantic inconsistency that arises from accessing dierent inconsistent states from dierent contexts. Obviously, accessing the same outdated state in all contexts is no option either. Being able to exchange parents without noticing any dierence would be worthless. In eect it would be equivalent to disallowing dynamic delgation altogether. However, one can alternatively circumvent the unsafety of a message with respect to the current composition of its receiver by delaying the evaluation of the message, waiting for a future composition that makes the message safe again6 . This is called the future state approach. Compared to frozen object states, future object states have the denite advantage of producing no semantic inconsistencies by denition of this approach message evaluation is either suspended or it accesses the current, safe state of the receiver object. The shortcoming of message suspension is that in a single-threaded system it would indenitely block the whole application because there is no other thread that eventually will change the state of the receiver object. It might appear that the future state approach could be an interesting option in concurrent systems with a high rate of parent attribute updates. However, high update frequency alone would only reduce the probability that the evaluation of individual threads might be delayed too long. It is no guarantee against starvation or deadlock. For instance, the synchronized version of the simple moveBalloon() method discussed in the previous section in the context of Figure 5.6 would already produce a deadlock: In order to guarantee atomic execution of the radius() test and the move() operation, the client rst acquires a lock on the mv object referenced by the balloon variable. When it then sends the unsafe radius() message, waiting for a legal future state would last indenitely, because no other thread could change mv, due to the lock acquired previously. The type of deadlock described in this example is comparable to the inconsistency problems of freezing. In both cases the run-time behaviour of a program diers from the one expected by its author and in both cases the reason is the systems unexpected intervention by freezing object states or giving up control to another thread. Because it is unapplicable in single-threaded systems and raises the risk of unexpected deadlocks in concurrent systems the future state approach will not be discussed in more detail.

5.5

Invariant split self

The previous sections try to avoid type-unsafety of dynamic delegation either by restricting the updates that may be performed on delegation attributes or by avoiding to evaluate a message with respect to the current state of an object, if this state does not contain applicable methods.
6I

am indebted to my supervisor for pointing out this alternative to frozen object states.

94

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

In the course of this chapter, another reason for unsafety has been mentioned repeatedly: the fact that a child object may be passed into contexts that expect objects of another, incompatible type. On one hand, this can be considered the primary cause of all problems. If one allows a child object of expected compound type C to be assigned to a self pseudovariable of static type P although C is no subtype of P , the latter occurrence of type-errors comes as no big surprise. On the other hand, it seems as if there is nothing to be done about it: passing of child objects as the value of self in a parent method is the dening characterisic of delegation and, interestingly enough, it is safe in parent objects of the declared parent type.

5.5.1

Invariant self

Although passing of child objects as the value of self is unavoidable one may well ask whether the covariant change of the type of self is unavoidable too. If the type of self was invariant, i.e. if self had the declared parent type in all potential parent objects, the static type-check would already prevent messages that are not in the declared parent type being sent to self or to any expression to which self can be assigned. So no type-errors could occur at run-time because messages from the declared parent type are safe for all possible child objects. This observation alone is not of much practical value: without covariant change of self , objects could not access their own specic properties. For instance, a method from type ColorPoint whose self still had type Point could not access the color attribute of its receiver object via self.color. Similarly, a method from type Employee whose self still had type Person could not access the work() method of its receiver object via self.work(). The need for covariant change of self is there regardless of whether the subtypes are subclasses or child classes of the declared parent type.

5.5.2

Split self

In subclasses, the apparent dilemma results from the fact that self is used indiscriminatingly for two dierent purposes: denoting child objects and denoting the object whose method is currently being executed. The obvious solution is is to give dierent uses of self dierent names:

receiver The pseudovariable receiver refers to the initial receiver of a message. Values of receiver can either be instances of the statically declared type of receiver or child objects. The type of receiver is invariant within an inheritance hierarchy but it changes covariantly from parent types to child types. holder The pseudovariable holder refers to the method holder. Values of holder can be only direct instances of the static type of holder . The type of holder changes covariantly.
Because it makes the split nature of self explicit and lets the type of receiver be invariant this proposal is called the invariant split self approach.

5.5. INVARIANT SPLIT SELF

95

Anticipated dynamic delegation. In many uses of dynamic delegation the child and the parent classes are designed as a group. In applications like the state or strategy pattern, for instance, the classes which implement states and strategies are designed to be used as delegation parents. Knowing about their role as potential parents, their authors may take advantage of the distinction between receiver and holder : messages addressing local auxilliary methods, which are not in the declared parent type, may be sent to holder , whereas other messages may be sent either to receiver or to holder , giving the programmer ne-grained control of which messages may be overridden by child objects and which may not.

5.5.3

Anticipated parent types

In Darwin, classes or abtract types whose use as parents is anticipated during design can be annotated with the keyword delegatee. They are called delegatee classes and abstract delegatee types, respectively. Abstract delegatee types and their abstract subtypes are called abstract anticipated parent types. Delegatee classes, subclasses of delegatee classes, classes implementing abstract anticipated parent types and their subclasses are called anticipated parent classes. The set of all anticipated parent classes derived from the same abstract type T is said to be rooted in T . When the distinction between classes and abstract types is not relevant we simply talk about anticipated parent types. Dening Constraints. Attached to a class C, the delegatee annotation 1. enables and enforces the use of the receiver and holder pseudovariables in the body of methods, within C and all subclasses of C (further use of self will be agged as an error), 2. implicitly declares receiver to be of type C within C and all subclasses of C, 3. enables the use of C and of its subclasses as declared parent types of dynamic delegation attributes. Attached to an abstract type T , the delegatee annotation 1. enables and enforces the use of the receiver and holder pseudovariables in the body of methods, within all anticipated parent classes rooted in T (further use of self will be agged as an error), 2. implicitly declares receiver to be of type T within T , all abstract subtypes of T , and all anticipated parent classes rooted in T , 3. enables the use of T , all abstract subtypes of T , and all anticipated parent classes rooted in T as declared parent types of dynamic delegation attributes. Code inherited by an anticipated parent class from a superclass that uses self will behave as if all messages to self had been sent to receiver . This behaviour of inheritance is mandated by the desire for a consistent semantics of static and dynamic delegation. Please recall that every class can be used as a declared parent type for static delegation (see chapter 4), in which

96

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

case self will be redirected to the receiver object when methods are invoked via delegation. This behaviour must be preserved, if the code of a class should not behave inconsistently when executed via static delegation to an instance of that class or via dynamic delegation to an instance of a (anticipated parent) subclass. Redirection of messages to self to receiver is safe as long as the type of self in each superclass is a supertype of receiver . This constrains the cases in which anticipated parent classes may legally inherit from normal classes. Derived Constraints. The following constraints that control the ability of anticipated parent types to have multiple supertypes can be derived from the ones presented above. Summarised informally, they say that anticipated parent types must not have multiple supertypes with dierent compile-time types for receiver or this: For delegatee types A delegatee type may have multiple supertypes provided that none of them is an anticipated parent type. Trying to declare a subtype S of an anticipated parent type T itself as a delegatee would attempt to redene the type of receiver (from C to S), clearly violating the dening constraint number 2. Therefore, an attempt to do so results in a compile-time error. For non-delegatee types For an anticipated parent type AP T that is not i=1..n a delegatee type let AP Si be the supertypes that are themselves anticipated parent types and let Tjj=k..0 be the supertypes that are not. Then the type of receiver must be the same within all AP Si and it must be a subtype of the type of self within each Tj : i1 , i2 1..n, j k..0 : receiveri1 = receiveri2 selfj Each other case results in a compile-time error. If dierent anticipated parent supertypes have dierent invariant receiver types, one does not know which one should be valid in the subtype. If receiver selfj for some normal supertype Tj , then redirection of messages to self to receiver (see deninig constraints above) is unsafe. The denition of anticipated parent types lets us state concisely the idea of the invariant split self approach to type-safe dynamic delegation: Rule 5.1 (Dynamic delegation with invariant split self ) The type of delegation attributes whose value may change after initialisation must be an anticipated parent type.

An Example. Let us see how the example that has been used in section 5.1 to illustrate the unsafety of dynamic delegation can be handled gracefully by the invariant split self approach. The rewritten example is illustrated in Figure 5.7, its full code is shown in Listing 5.1. Figure 5.7 also shows the two new stereotypes [UML99] used to mark dynamic delegation attributes ( dynamic ), attributes whose values are restricted to atomic instances of their declared type ( atomic ), and delegatee classes ( delegatee ) in UML diagrams. Note that

5.5. INVARIANT SPLIT SELF

97

the declaration of the dynamic delegation attribute in the child class will not be accepted without declaration of class DeclaredParent as a delegatee class;

the messages n() and m() are sent to receiver , thus enabling overriding of the local denition in the child;

because the bang() method of class PotentialParent is not contained in DeclaredParent, it can only be sent to holder not to receiver . The last observation illustrates how the invariant type of receiver statically prevents unsafe messages that would have been allowed otherwise.

public delegatee class DeclaredParent { // Due to the delegatee annotation, the pseudovariable receiver of type // DeclaredParent and the pseudovariable holder of covariant type must // be used in this class and its subclasses instead of self / this: public void traitor() { ... } public void n() { ... } public void m() { ... } } // public class PotentialParent extends DeclaredParent { // Overriden method from DeclaredParent: public void traitor() { receiver .n(); // possibly sent to a child object holder.bang(); // only for this object receiver .m(); // possibly sent to a child object } // Local method: protected void bang() { ... } } // public class Child { // Dynamic delegation to atomic instances of type DeclaredParent: protected mandatory delegatee @DeclaredParent p; // Overriden method from DeclaredParent: public void n() { self.setParent(new DeclaredParent()); } // Local method: public void setParent(@DeclaredParent arg) { p = arg }

Listing 5.1: Safe dynamic delegation with invariant split self and atomic instances Lava code for example from Figure 5.7

98

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

<< dynamic>> <<delegatee>> Child n() { self.setParent(new ...) } setParent (arg:DeclaredParent) <<atomic>> DeclaredParent traitor n () m () ()

PotentialParent traitor() { reciever.n() holder.bang() reciever.m() } bang ()

1.1.1.1 setParent(...

) 1.1. > traitor() holder betrayedObject: PotentialParent reciever

1. traitor()

intruder : Child

1.1.1. n() a) State immediately before exchange of the parent object

1.1.3.1 > m()

n ew: DeclaredParent

1.1.2.
intruder : Child 1.1.3. m() b) All messages to reciever and holder are safe, even after parent exchange betrayedObject: PotentialParent

bang() holder

reciever

Figure 5.7: Safe dynamic delegation with invariant split self

5.5. INVARIANT SPLIT SELF

99

5.5.4
5.5.4.1

Typing of receiver in child classes


Generalized invariant split self

In the above example the typing problem has disappeared because the invariant type of receiver prevents child objects being passed into type-unsafe contexts within methods of parent objects. This solution, applicable to subclasses of the declared parent type, can be generalized to its descendant classes, by including them into the denition of anticipated parent types. Option 5.1 (Generalized invariant split self ) In addition to the denition given in section 5.5.3 the set of anticipated parent types rooted in a type T includes all the subtypes derived from T by delegation. However, requiring the type of receiver to be invariant also in descendant classes of a delegatee type would make them subject to the restrictions that already apply to subclasses of a delegatee class (see paragraph Derived Constraints on page 96): Descendant classes could not themselves be declared as delegatee classes and could not access their specic local methods via receiver . Therefore their own descendant classes could not eectively override these methods. Multiple dynamic delegation would not be possible due to the conicting invariant types of receiver in each of the dierent declared parent types. In order to allow child classes to delegate multiply and to be used as delegatee types, covariant redenition of the type of receiver must be possible. 5.5.4.2 Atomic parents

The alternative approach proposed here is to allow covariant redeniton in child classes but to prevent that instances of child classes of a type T are used as parents of instances of T . For this purpose, Darwin requires that the values of (dynamic) delegation attributes are atomic instances of their declared type (see 4.6.5). Option 5.2 (Atomic parents) Delegation attributes whose value may change after initialisation must always hold atomic instances of their declared type. An application of this rule is exemplied in Listing 5.1 on page 97 where the delegation attribute p is declared to be of type @DeclaredParent, which means that it may only hold atomic instances of DeclaredParent. With parent attributes that hold atomic instances the troublesome scenario from Figure 5.2 on page 83 cannot occur. Both options ensure type-safety. Both have their weaknesses, too. Those of the generalized invariant self have been pointed out above. Those of atomic parent type instances will be discussed at the end of this section and in chapter 8. The question, which option is better cannot be answered easily. There is much

100

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

to be said on both sides of the question. On balance, however, the arguments for the atomic parent option have most to recommend them. Therefore, it will be pursued further in this thesis. The complete invariant split self approach with atomic parents is summarized by the following rule: Rule 5.2 (Dynamic delegation with atomic anticipated parents) The declared type of any delegation attribute whose value may change after initialisation must be an anticipated parent type. Its values must be atomic instances of this type.

5.5.5

Transitive Dynamic Delegation

Delegation may be transitive: an object may delegate to an object that delegates to another object that delegates, etc. If we want to ensure safety of dynamic delegation statically, transitivity must be restricted where it would lead to dynamic violations of invariants assumed by static checks. Part (a) of Figure 5.8 illustrates such a case. If class B was allowed to delegate to a normal class (with covariant self ), it would pass the value of receiver into a self variable of incompatible type. For instance, at time t1 a reference to the object a would be passed as the value of cn s self , which is expected to be of type Cn . After change of as parent at time t2 no methods from Cn would be available in a any more. The same would happen if a non-delegatee class was allowed to delegate, even to an invariant split-self parent. A corresponding example is shown in part (b) of Figure 5.8. For comparison, the scenario illustrated in part (c) of the gure does not raise any problems. The distinction between legal and illegal transitive dynamic delegation is captured by the following rule. It enforces that the type of receiver in the delegating child class is a subtype of the receiver type expected by any potential parent class. Rule 5.3 (Transitive dynamic delegation) An anticipated parent class may delegate itself only if it is a delegatee class and the declared parent type is itself an anticipated parent type. Any other case must result in a compile-time error. Note that the rule only constrains delegaton from an anticipated parent class. Delegation to an anticipated parent class or type is unconstrained.

5.5.6

Observations

The sections 5.5.4 and 5.5.5 provide a complete technical description of the invariant split self approach. Before comparing it to other approaches it is instructive, however, to shed light on some of its peculiarities and to relate it to other topics discussed so far.

5.5. INVARIANT SPLIT SELF

101

<<dynamic>> A <<atomic>>

<<delegatee>> B

B1

Bn

C1

Cn

t1

bn self : Cn

cn

t2

b1

c1

<-- possible error: c1 not of type Cn

(a) Illegal: declared parent C is no split-self class


<<dynamic>> A <<atomic>> <<delegatee>> B

B1

Bn

<<delegatee>> C

t1

bn self : Cn

t2

b1

<-- possible error: b 1 not of type C

(b) Illegal: delegating class Bn is no delegatee class


<<dynamic>> A <<atomic>> <<delegatee>> B <<dynamic>> <<atomic>> <<delegatee>> C

Bn

Bn

C1

Cn

t1

bn receiver : C

cn

t2

b1

c1

<-- ok: c1 is of type C

(c) Legal transitive dynamic delegation

Figure 5.8: Transitive dynamic delegation with invariant split self

102

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

Dierent class-subclass and parent-child specialisation interface. Due to the invariant receiver type the set of messages that may be sent to receiver and may therefore be overridden in delegating children is xed for a delegatee class D and all its subclasses. So all atomic instances of D will have the same, xed specialisation interface. This holds even if subclasses of D dene additional protected methods. The additional methods extend the specialisation interface of the class, i.e. they can be overridden in subclasses. However, they do not extend the specialisation interface of its instances. Including them into the specialisation interface of instances would promise that they could be overridden by delegating objects which would be deceptive because static type checking prevents these messages from being sent to children via receiver . The above observation should only raise the awareness of an unusual eect of invariant split self. There is no need to change anything in the theory presented so far. In particular, the denition of type and subtype are formulated on the basis of objects, not of classes (see section 4.6 on page 60). Therefore they are still in place, even in the context of objects instantiated from anticipated parent classes. Enforcing overriding. The fact that additional methods in subclasses of delegatee classes do not appear in the specialisation interface of their instances only says that it is statically unsound to promise that they can be overridden in child objects. It does not say that overriding is not possible, if desired by the programmer of the respective class. Programmers may use dynamic type checking to enforce that additional local methods can be adressed by messages sent to receiver when its current value is indeed an instance of the desired type. They can thus explicitly give children the chance to provide own methods for the respective messages.

5.5.7

Dynamic type checking

In the context of split objects, the semantics of dynamic type check and dynamic cast operations must be adapted to take into account delegation and atomic instances. This section additionally introduces a parent cast operation. The dierent scope and use of all operation is illustrated by an example at the end of the section. Denition 5.3 (Dynamic type test) The dynamic type test expr instanceof T is always statically legal. At runtime, it returns true if the evaluation of expr produces a reference to an object whose expected compound type is a subtype of T . Otherwise it returns false: eval(expr instanceof T ) obj = eval(expr); return( [obj] T );

Denition 5.4 (Dynamic test for atomic instances) The dynamic type test expr instanceof @T is always statically legal. At run-time, it returns true if the evaluation of expr produces a reference to an

5.5. INVARIANT SPLIT SELF

103

object that is an atomic instance of T (denition 4.6 on page 63). Otherwise it returns false: eval(expr instanceof @T ) obj = eval(expr); return( atomicInstance(obj, T ) );

In the sequel every type variable T can be a class C, an interface I or an atomic instance designator for a class or interface (i.e. T = C or T = I or T = @C or T = @I). Denition 5.5 (Dynamically checked cast) The dynamically checked cast (T ) expr is always statically legal. At run-time, it returns the reference produced by the evaluation of expr if expr instanceof T evaluates to true. Otherwise it raises a ClassCastException. eval((T ) expr) obj = eval(expr); if (eval(obj instanceof T )) return(obj); else return new ClassCastException();

To realize the full potential of split objects an additional parent cast operation is provided in Darwin. It diers from the above operations by the fact that, if it succeeds, it does not necessarily return a reference to the object produced by evaluating expr. Instead, it can also return a reference to a parent object. Denition 5.6 (Parent cast) The parent cast <T > expr is always statically legal. If o is the object resulting from the evaluation of expr at run-time, then the parent cast returns a reference to o, if o is of type T and to the most specic ancestor of o that is of type T , otherwise. If neither o nor any of its ancestors is of type T then a ClassCastException is thrown. The run-time part is dened by the following algorithm (excluding exception handling): eval(<T > expr) obj = eval(expr); if (eval(obj instanceof T )) then return(obj); else return(eval(<T > obj.parent));

The dierence between the three operations and their complementary use is illustrated by Figure 5.9 on the next page and Listing 5.2 on the following page. The message

104

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

<<static>>

<<dynamic>> Bn <<atomic>>

<<delegatee>> C

bn reciever : C

Figure 5.9: A scenario where the parent cast is useful c.test() prints Atomic instance of C bn .test() and bn .m() prints Safe child, instance of Bn a.m() prints Unsafe child whose most specific safe parent is an instance of Bn Table 5.1 on page 105 compares the results of the dierent operations for dierent expressions expr. It shows how results dier for normal types and atomic instances. The table refers to the class and object hierarchy from Figure 5.9.
public delegatee class C { public void test() { if (receiver instanceof C) { if (receiver == holder) print("Atomic instance of C"); else print("Safe child, instance of " + receiver.getClass().getName()); } else print("Unsafe child whose most specific " + "safe parent is an instance of " + (<C> receiver).getClass().getName() ); } public class B { public void m() { ... } } public class Bn extends B { public void m() { test() } }

Listing 5.2: Code for class C from Figure 5.9

5.5. INVARIANT SPLIT SELF


T =C expr = a expr = bn false true T = @C false false

105

expr = c true true Results of Instanceof test: expr instanceof T T =C expr = a expr = bn expr = c ClassCastException bn T = @C ClassCastException ClassCastException c

c Results of normal cast: (T ) expr T =C

T = @C c c c

expr = a expr = bn expr = c

bn bn c Results of parent cast: <T > expr

Table 5.1: Dierence of casting to normal types versus atomic instances

5.5.8

Summary

The invariant split self approach proposes two dierent cures for the two dierent causes of unsafe dynamic delegation (section 5.1): The assignment of instances of descendant classes to parent attributes is forbidden by requiring that parent attribute values must be atomic instances (see rule 5.2 on page 100). The assignment of instances of subclasses of the declared parent type is permitted, subject to the condition that these objects are aware that they might play the role of parents. This is declared by the delegatee annotation that must be added to the declared parent type or one of its supertypes and by the implied use of receiver and holder instead of self . The rst constraint is the more restrictive one. Yet, it does not signicantly limit practical programs. In most applications, the intention of dynamic delegation will be indeed to delegate to atomic instances of the declared parent type. On the contrary, when considering the example from Figure 5.2 on page 83, ruling out the possibility to delegate to instances of child types appears to be a feature rather than a limitation. The behaviour of programs where instances of supertypes will override the behaviour of subtype instances the opposite of what one would expect when writing a program could be arbitrarily errorprone and hard to understand. The analysis of well-known design patterns and of the JDK has produced few examples where instances of subtypes of a declared parent type might sensibly

106

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

be used as parents. However, focusing the discussion on particular examples would miss the point. Even if there are examples that would take advantage of delegation to instances of a child type, ruling them out is a relatively small price to pay, related to the advantages of the invariant split self approach: it is type-safe, it is ecient, it produces no semantic inconsistencies, it is singicantly superior to simulation of delegation with respect to (re)usability and it is widely applicable to many typical uses of dynamic delegation. Proving type-safety is trivial. The approach simply prevents objects being passed into contexts where they would be type-unsafe. Because any message to receiver is abstractly safe by the denition of receiver , dynamic parent changes have no negative eect. Therefore, there is nothing to repair and the simple dynamic binding scheme for static delegation can be applied unmodied (see denition 4.12 on page 74). The implementation of this scheme can even be optimized for dynamic delegation, taking advantage of the fact that messages to receiver are guaranteed to be safe (see chapter 7). So, surprinsingly, anticipated dynamic delegation is more ecient than unanticipated static delegation. It is equally easy to see that the specic consistency problem of freezing does not arise with split self . Due to the explicit separation of the receiver and holder role, programmers are always aware of what they are doing. The dierent identity and type of the two pseudovariables is obvious at compile time, so any problems can be identied and treated as usual. Regarding usability the invariant split self approach automates all the work that the programmer had to do manually in the simulation discussed in section 3.6 and presents none of the limitations of the simulation, except for the need to anticipate the potential use of a class as a parent. However, compared to simulated delegation, Darwin limits the need for anticipation and minimizes its impact: Simulations of delegation require anticipation for dynamic and static delegation. In Darwin, it is required only for dynamic delegation, whereas static delegation may be fully unanticipated and requires no preparation of parent classes, whatsoever. In Darwin, turning a class into an anticipated parent class requires the addition of the delegatee keyword to the class declaration and replacement of this by receiver or holder in the class and its subclasses. The replacement of this by the simulatedSelf variable is also required by all simulations. The most general simulation approach passing of simulated self as a parameter additionally changes method signatures, requiring extensive subsequent changes of client code. The claim of wide applicability can only be justied here by the empirical arguments given above and discussion of some typical examples that cannot be modelled by other approaches (in chapter 8). A real justication can only come from continuous practical deployment and user feedback.

5.6. CHAPTER SUMMARY

107

5.6

Chapter Summary

Four dierent approaches to type-safe dynamic delegation with subtyping have been presented in this chapter: pure specialisation (section 5.2), use of frozen states (section 5.3), waiting for future states (section 5.4), and invariant split self (section 5.5). Their main characteristics are discussed in the following and summarized in Table 5.2.

Type-safety. All discussed approaches ensure type-safety. Pure specialisation depends entirely on dynamic checks of assignments to parent attributes. The other three approches ensure static type safety. Subtyping. All approaches allow unrestricted subtyping. Delegating child classes are subtypes of their declared parent types. There is no mutual exclusion of subtyping and delegation. An expression expr of type T may produce instances of type T and of any subtype of T , including types derived by inheritance as well as those derived by delegation. Simultaneously, objects produced by expr may be used as parents of other objects, without restriction (except for the constraint that delegation must not create incomplete objects, see section 4.8). Parent Values. Limitations of the values of parent attributes are required by the pure specialisation and invariant split self approach. Pure specialisation dynamically limits parent attribute values: the concrete compound type of the new parent must be a subtype of the concrete compound type of the previous parent. Invariant split self statically limits parent attribute values to atomic instances of anticipated parent types (see section 4.6.5 and 5.5).

Approach Frozen state Future state Pure specialisation Invariant split self

Safety static static

Subtyping unrestricted unrestricted

Parent Values any any specialisation of previous atomic instances

SideEects inconsistency deadlocks none none

Applicability none none minimal broad

dynamic unrestricted static unrestricted

Table 5.2: Comparison of approaches to type-safe delegation with subtyping

108

CHAPTER 5. DYNAMIC DELEGATION WITH SUBTYPING

Side-eects. Side-eects are exhibited by the frozen state and future state approach. The former one leads to an inconsistent operational semantics (see section 5.3.2). The latter one is only applicable in concurrent environments, where it may lead to deadlocks (see section 5.4). Applicability. The inconsistencies of the frozen state approach and the deadlocks introduced by the future state proposal cannot be avoided by application programmers. This is unacceptable. Therefore, in Table 5.2, both approaches are classied as unapplicable in practice. The practical signicance of pure specialisation is minimal. Being unable to express behaviour evolution beyond repeated specialisatition, it is unapplicable to essential behaviour evolution scenarios like the state and strategy pattern. In contrast, invariant split self is widely applicable. For instance, any variation of state and strategy change can be expressed, including asumption of states that are not specialisations of each other and alternating use of dierent strategies (see chapter 8). Conclusion. The only approach that enforces type-safety of dynamic delegation with subtyping, does not exhibit undesired side-eects and is widely applicable is the invariant split self approach. Denition 5.7 (Darwin) Darwin comprises the base object model, type system, unanticipated static delegation and independent extensibility approach described in chapter 4 together with the invariant split self approach to dynamic delegation described in section 5.5. The next part presents Darwins integration into the programming language Java (chapter 6), related implementation techniques (chapter 7), and application examples (chapter 8).

Part III

Lava

109

Chapter 6

Lava The Language


The previous part has introduced the Darwin model, which extends traditional class-based models by two object-level, forwarding-based variants of inheritance. It focused on the analysis of technical problems of such an extension, discussing various solutions and their potential pitfalls with respect to static type safety, independent extensibility, and semantic compatibility. This discussion led to the incremental description of an object model, type system, operational semantics and of a set of static and dynamic constraints. Occasionally, dierent alternative options have been presented and compared but no commitments to a specic solution have been made, reecting the aim of Darwin to be generally applicable. This part is for readers who prefer concreteness over generality. It describes the design (in this chapter), implementation (in the next chapter) and applications (in chapter 8) of the language Lava.

6.1

Lava Overview

Lava is an extension of Java that conforms to the Darwin model. It is the result of a specic selection from the possible set of choices described in the previous chapters and of an adaptation of Darwin to the peculiarities of Java. The goal that drove the design of Lava has been to provide an experimental language that is 100% backward compatible to Java in syntax and semantics and adds rst class support for forwarding-based designs and semantic compatibility. Lava extends Java by the following notions: 1. mandatory elds, 2. consultation elds, 3. delegation elds, 4. an explicit delegation operation, 111

112 5. a parent cast operation, 6. anticipated parent types and 7. atomic instances of a type.

CHAPTER 6. LAVA THE LANGUAGE

The rst two items represent Lavas general support for forwarding-based designs whereas item 3 to 7 are specic for delegation. Each feature is dealt with in one specic section that adapts the denitions given in the previous part to the peculiarities of Java. The denitions in this chapter are formulated as extensions of the Java Language Specication (JLS) [GJS96]. This is intended to provide an immediate comparison of Lava and Java that will help everyone who is already familiar with Java. Where extensions peculiar to Lava are included in the context of a citation from the JLS, the extensions are italicized. Insertions that ease understanding of a citation by providing more semantic context are included in [square braces]. Omissions from the original text are marked by [...]. Lava is governed by seven simple principles: 1. Any class may declare consultation and delegation elds. The only exception is the one mentioned in principle 5. 2. Assignments to consultation and delegation elds are guarded by run-time checks to prevent the creation of incomplete objects (see rule 4.1, rule 4.2 and option 4.1). 3. Consultation elds and final delegation elds may have any class or interface type. 4. A non-final delegation eld must be of an anticipated parent type and must hold atomic instances of this type (see rule 5.2 on page 100). 5. An anticipated parent class may delegate itself only if it is a delegatee class and if the declared parent type is itself an anticipated parent type (rule 5.3). 6. The relation of delegating classes to their declared parent types with respect to subtyping, overriding, the final annotation, hiding and visibility (access modiers) is exactly the same as the relation of subclasses to their superclasses in Java. 7. Any denition and constraint of the JLS that is not explicitly mentioned in the previous principles is left unaltered in Lava. All modications are conservative: Java programs compiled by a Lava compiler do not change their semantics. Given the denitions in chapter 4 and 5 and the JLS these principles provide an almost complete description of Lava, which might suce as an executive summary. For any user or implementor of the language, however, it will not suce. Because the devil is in the detail a precise specication of Lava is indispensable. In its course, the seven principles will reappear in somewhat convoluted versions, due to the strict adherence to the structure of the JLS, which occasionally required redundantly including dierent specialised instances

6.2. TERMINOLOGY

113

of one principle into the exhaustive enumerations of alternatives often found in the JLS. Each section presents an aspect of the design of Lava, followed by a discussion of possible alternatives, if any. Readers who just want to learn about the language but are not interested in the details of language design considerations may safely skip these discussions. A summary section concludes this chapter.

6.2

Terminology

The aim to be 100% compatible to Java is reected also in the choice of terminology. Whenever Java terminology exists it is employed also in Lava. The resulting small terminological dierences between Darwin and Lava are summarized in Table 6.1. Darwin self property attribute static delegation dynamic delegation abstract type Lava this member eld final delegatee eld non-final delegatee eld interface or abstract class

Table 6.1: Darwin to Lava terminology translation In this chapter, all references of the form 1.2.3 refer to sections of the JLS. References without the prex refer to sections of this thesis.

6.3

Syntax

Naming Conventions. Names starting with the symbol @ denote atomic instances of a type. For convenience, such names are called atomic types (see 4.6.5 on page 63). The part of the name following the atomic instance designator @ must be the name of a class type or interface type. Keywords. The list of keywords ( 3.9) is extended by: consultee, delegatee, mandatory, receiver, holder. Separators. The list of separators ( 3.11) is extended by: <-. Modiers. The list of class modiers ( 8.1.2, 19.1.2) is extended by delegatee. The list of eld modiers ( 8.1.2, 19.1.2) is extended by delegatee, consultee and mandatory. In the LALR(1) grammar the list of modiers (19.7, 19.1.2) is extended by delegatee, consultee and mandatory. A later stage of compiler analysis then sorts out the precise role of each modier and rejects modiers in illegal contexts.

114

CHAPTER 6. LAVA THE LANGUAGE

Primary expressions. The list of PrimaryNoNewArray expressions (15.7, 19.12) is extended by the terminal symbols receiver and holder. Method invocation. The list of MethodInvocation expressions (15.7, 19.12) is extended by one production for explicit delegation (underlined): MethodInvocation: Name ( ArgumentListopt ) Primary . Identier ( ArgumentListopt ) Identier <- Identier ( ArgumentListopt ) super . Identier ( ArgumentListopt ) Cast expressions. The list of cast expressions (15.15, 19.12) is extended by the syntax of the parent cast expression (underlined): CastExpression: ( PrimitiveType Dimsopt ) UnaryExpression ( Expression ) UnaryExpressionNotPlusMinus < Expression > UnaryExpressionNotPlusMinus ( Name Dims ) UnaryExpressionNotPlusMinus The above is the complete list of syntax extensions with respect to the JLS. The following sections dene the semantics of Lava.

6.4

Mandatory elds

Every practical program contains places where elds should not contain the null reference. Lava supports enforcement of this invariant via the mandatory modier. Static constraints 1. Addition of the mandatory modier to a eld declaration is legal if the type of the eld is a reference type. Using the mandatory modier in conjunction with a non-reference type results in a compile-time error. Dynamic constraints. Addition of the mandatory modier to a eld declaration means that the eld may never hold the null value. Note that never includes object construction: a mandatory attribute must be given a value prior to execution of any other code in the constructor of an object. Most of the enforcement of these constraints is performed by the extended denition of the simple assignment operator (see section 6.13). It guarantees that no null values will ever be assigned to a mandatory eld by programmerwritten code. Static constraints 2. What remains, is to enforce that a mandatory eld will be initialized explicitly (because otherwise Javas standard initialisation will assign them the null value) and that this initialisation will take place prior to the invocation of any instance method on a newly allocated object. This invariant is enforced by the following constraints: in a class that declares d mandatory elds and inherits i mandatory elds

6.4. MANDATORY FIELDS

115

there must be at least one explicit constructor declaration (which, by the rules of Java, guarantees that there is no implicit, zero-arguments constructor), each explicit constructor must have at least d + i arguments (for the initialisation values of the mandatory elds), in each explicit constructor the explicit or implicit invocation of a superclass constructor via super must be followed immediately by a sequence of d assignments that initialise every mandatory eld with a value passed as parameter to the constructor, if i = 0, then the invocation of an own constructor via this must also be followed immediately by a sequence of d assignments that initialise every mandatory eld with a value passed as parameter to the constructor. These properties can and must be checked at compile-time. Constructors must be translated in a way that ensures that assignments to mandatory values will be the rst code executed on a newly allocated object (see 7.2.4 on page 139). Ensuring that a value dierent from null is assigned requires dynamic checking in most cases (see discussion below).

6.4.1

Discussion

This section discusses the possible extension of mandatory to local variables and parameters of methods. Static checking. Some of the dynamic constraints dened above can and should be veried at compile-time. Simple cases are the direct assignment of the null value, the assignment of the result of a constructor, and the assignment of a value that is itself declared to be mandatory. The rst case must be immediately rejected by the compiler; in the second and third case the compiler must not generate additional code for checking the assignment at run-time. However, the benet of static checking in the third case is limited by the fact that only elds can be declared to be mandatory. Extended use of mandatory. If mandatory could be used also for anntotating parameters and local variables of a method, then many exceptions could be prevented at compile-time. For instance, the mandatory annotation of the parameter s in the following code:
private mandatory OutputStream s = new ...; public void setOutput(mandatory OutputStream s) { s = s }

would have the eect of moving the check for assignment of null from the setOutput() method where it is no longer necessary because both sides of the assignment are mandatory to the place of its calls or even further away to the outmost place where a non-mandatory expression is assigned to a mandatory one. So, general use of mandatory as a type annotation would make the semantics of programs more explicit,

116

CHAPTER 6. LAVA THE LANGUAGE

reduce the amount of dynamic checking and lead to detection of errors closer to the place where they originated. This might be a worthwile generalisation of the concept of mandatory elds. It has not been included into Lava for compatibility with Java. Its implementation would require changing the class le format, which currently allows no attributes to be added to local variables.

6.5

Parent elds

Parent elds. In Lava objects automatically forward locally unapplicable messages to the objects referenced by their parent elds. A parent eld may be either a delegation eld or a consultation eld. It is declared by adding the modier delegatee (for delegation) or consultee (for consultation) to an instance variable declaration. Parent and child types. If class C declares a parent eld of type T we say that C and each of its subclasses is a child class of T and T is a declared parent type of C and of each of C s subclasses. The declared ancestor relation is the transitive closure of the declared parent relation. The descendant relation is the transitive closure of the child relation. Parent and child objects. Objects referenced by parent elds are called parent objects or simply parents. Objects that contain parent elds are called child objects or simply children. Semantics. Denition of parent elds has three eects: Interface inheritance: The interface of a delegating child class is extended by all public and protected instance variables and instance methods from the declared type of its parent eld. The interface of a consulting child class is extended only by the public instance variables and instance methods from the declared type of its parent eld. Subtyping: Any class is a subtype of the declared type of its parent eld. The additional legal type conversions (casts) are listed in section 6.9. Automatic Forwarding: Every variable access and method invocation for which there is no applicable signature (see denition 4.15) in the type of a given object, o, is automatically forwarded to the current value of os parent eld. If the parent eld is a delegatee eld the dynamic binding scheme of denition 4.16 is applied (see also section 6.6). Further semantic details of parent elds are specied by a set of constraints. In the following, the static constraints (which can be checked at compile-time), and the dynamic constraints (which require run-time checking) are listed separately.

6.5. PARENT FIELDS

117

Static parent eld constraints. Violation of any of the following constraints results in a compile-time error: 1. A parent eld must be declared to be mandatory (see 6.4 on the facing page). 2. The declared type of a parent eld must be a class or interface type (see 4.3). 3. The declared type of a delegation eld must not be a final class. This is the delegation counterpart of the Java constraint that a class must not have a final superclass. 4. A non-final delegation eld must be of an anticipated parent type and must hold atomic instances of this type (see 6.8 on page 125). This constraint ensures type-safe dynamic delegation (see chapter 5). 5. A parent eld must not be private1 . 6. A class must not have more than one (either local or inherited, consultee or delegatee) parent eld. There are no other static constraints that limit the use of parent elds. Note that the type of a final delegation eld may be any interface or nonfinal class. This gives delegation the full power of class-based inheritance plus the added benet of dynamic composition: whereas superclasses are xed once and for all, declared parent types are just an upper bound on the possible values of parent elds. At run-time arbitrary subtype instances may be passed as initialisation values for parent elds, enabling dynamic behaviour composition. Dynamic parent eld constraints. Assignments to a parent eld must be checked at run-time to guarantee that no incomplete objects (denition 4.10) are created. If null is assigned to a parent eld an AssignmentOfNullException is thrown. If the assignment of a non-null value to a parent eld would create an incomplete split object an IncompleteObjectException is thrown. The check for creation of incomplete objects may be pessimistic: it may reject assignments that would create a forwarding cycle without verifying whether the created split object would be incomplete. In this case the check must throw a CyclicForwardingException (see section 6.14). Assignments to non-final delegation elds must additionally be checked to enforce that they hold only atomic instances of their declared type. The dynamic constraints are enforced by the extended denition of the simple assignment operator = in section 6.13 on page 132.

6.5.1

Discussion

This section discusses the reasons for requiring parent elds to be protected or public, for excluding multiple delegation, for preferring dynamic detection of forwarding cycles over static prevention and for eliminating the notion of optional forwarding, which was included in an early design of Lava ([Kni96b, Cos98]) referred to in the following as Lava 0.5.
1 See

discussion about visibility of parent elds ( 6.5.1 on the following page).

118

CHAPTER 6. LAVA THE LANGUAGE

Why public or protected parents? Delegation and consultation are conceptual relations between the classes and types of a program. The fact that a class is a child extends its interface, makes it a subtype of the parent type and inuences the way how its instances react upon method invocations. The semantics of a program could not be understood if the child status of classes were not made explicit in design and implementation. The extension of UML (section 4.5) allows to make delegation and consultation explicit during design. The constraint that parent elds must be protected or public makes the child status of a class explicit in its implementation and javadoc documentation. These conceptual arguments have two more technical ramications that are worthwile mentioning. First, if parent elds were not at least protected, subclasses of a child class could not use explicit forwarding in the denition of local methods. For instance, if p were a delegatee eld, then explicit delegation expressions (see 6.6 on page 121) of the from p<-methodFromParent() in a subclass would be rejected by the compiler. Thus the analog of super calls in the case of class-based inheritance would be lost, restricting the expressive power of overriding. Second, if parent elds were not at least protected, subclasses would experience unexpected error messages in conjunction with multiple delegation: eventually, the compiler would reject the denition of a local parent eld on the basis of ambiguous methods inherited via a a private or package visible parent eld of a superclass. All these considerations provide conceptual and technical arguments why parent elds must be at least protected. public visibility of parent elds is not excluded explicitly by the language design but discouraged as a matter of style. It is strongly recommended that parent elds be protected. This is the only option that is consistent with all possible arguments. Why no multiple delegation? The last one of the static parent eld constraints (item 6 on the page before) is not of technical nature. Unlike the others it is not required for type-safety or semantic consistency but reects the deliberate decision to exclude multiple delegation. There are three reasons against multiple delegation. The rst one is the desire for binary compatibility: Recall that in Java, the computation of numeric osets for instance elds in memory is done by the run-time system. This permits updating a class with new instance variables or methods without invalidating compiled code of subclasses. This property would be lost if multiple inheritance or multiple delegation were supported because methods with the same signature added to dierent superclasses or parent types would cause ambiguities in the respective subclass or child class. If the subclass were not recompiled these ambiguities would lead to errors during class loading or at run-time (depending on the implementation). The second obvious reason is the desire for simplicity paired with a wellbehaved semantics. The only approach to (class-based) multiple inheritance that is widely recognized to be powerful and well-behaved is the redefine-undefinerename-select mechanism of Eiel (specied in [Mey92a]). However, it is an open debate whether the related gain in expressiveness commensurates with the additional complexity introduced in the language and its implementation. Furthermore, Eiels select mechanism exhibits a subtle semantic problem

6.5. PARENT FIELDS

119

of its own. Because it always selects the same code, irrespective of the context of the method invocation, it reinstalls the problem that method renaming was intended to solve: the selected method is executed also in contexts where a method with the same signature but dierent semantics is expected (even if a semantically compatible method exists in a sibling superclass). This subtle aw in Eiels design can be eliminated. A solution, based on recording the path of delegated messages through an object hierarchy at runtime in a delegation stack, has been included in the rst version of Lava [Cos98]. However, it further increases the systems complexity and appears to have a quite signicant negative impact on performance. At least the measurements of Lava 0.5 performance reported in [Sch97] and [Sie98] reveal a 250% increase in run-time. This increase was measured relative to the performance of JDK 1.0.2, of which Lava 0.5 was an extension. These results are contradicted by measurements performed on implementations of the same scheme on recent versions of the JDK, so this issue needs further investigation. Still, the need to pair each object reference in the program with a reference to a delegation stack does not leave much hope for a reasonably ecient implementation of this extension. An approach that provides solutions for all aspects of the semantic compatibility problem without compromising simplicity and eciency is ultimately desired. When it will be found, one will have to reconsider whether its benets outweigh the added opportunities for load-time or run-time errors. For the time being, we think that eliminating multiple delegation is prefereable to a convoluted design and inecient implementation. Why dynamic cycle prevention? In order to make the language as expressive as possible, static cycle prevention (see section 4.8.3) has been rejected in the design of Lava. It would have constrained the values of parent attributes to atomic instances (see 6.8) even in the case of static delegation. This would have disabled some interesting applications that rely on the ability to construct chains of forwarding objects. With the current choice, static delegation remains unrestricted. It is possible, for instance, to implement delegation based FilterStreams2 , whose functionality can be combined at run-time by building chains of various lters, each ltering the output of the previous one. Why no optional forwarding? In the version of Darwin and Lava presented here, delegation and consultation are required to be mandatory. This diers from the design of Lava 0.5 ([Kni96b, Cos98]), which also enabled parent attributes to be optional, i.e. to take the value null. Having optional forwarding in the language saved programmers writing forwarding methods of the form
public class SequenceInputStream extends InputStream { InputStream in; ... // forwarding method: public int available() throws IOException {
2 FilterStream

is a predened type in the Java Development Kit.

120

CHAPTER 6. LAVA THE LANGUAGE


if (in == null) return 0; return in.available(); }

Instead, the programm could simply be written as:


public class SequenceInputStream { optional consultee InputStream in; // legal in Lava 0.5 ... }

For every method from InputStream not implemented by the programmer of the child class, a forwarding method was created automatically ([Cos98, Sie98]), e.g.
// automatically created forwarding method: public int available() throws IOException, AssignmentOfNullException{ if (in == null) throw AssignmentOfNullException; return in.available(); }

Unfortunately, throwing an exception was the only thing that code created automatically could do to inform the caller that something went wrong. Because the AssignmentOfNullException was a checked exception client code was forced to operate with try-catch blocks in order to dene a sensible error treatment in every place where available() was invoked, e.g.:
public class Test { SequenceInputStream sis; // defined with optional consultation int avail; public static void main() { try { avail = sis.available(); } catch( AssignmentOfNullException e ) { avail = 0; } catch( IOException ioe ) { ... } }

Furthermore, instances of classes that contained such automatically created forwarding methods could not be substituted for parent instances. The additional checked AssignmentOfNullException in the interface of every forwarding method precluded subtyping. So, the advantage of not having to write forwarding methods was mitigated by the need to handle exceptions at every call-site (or propagate them into the calling methods signature) and the loss of the subtyping relation between child and parent. Both problems could only be avoided by writing own forwarding methods that treated null parent elds without throwing exceptions or by using mandatory

6.6. EXPLICIT DELEGATION

121

forwarding instead (as explained in the next paragraph). Both alternatives rendered optional forwarding ad absurdum, because they eliminated its only advantage: not having to write forwarding methods. So, the decision to eliminate optional forwarding from the version of Lava described here was due to the insight that its usefulness is much too limited to justify a language extension. Simulating optional with mandatory. If subtyping between child and parent is desired, a child class that uses optional forwarding must reimplement locally all methods from the declared parent type. As shown above, each method would contain a test whether the parent attribute is null. This can be replaced easily by mandatory forwarding to a dummy parent, exploiting the null pattern [Fow99, page 260], [WB98]. In a design that uses mandatory forwarding all the code that denes what should be done in the case that the parent eld is null is moved to a new subtype of the declared parent type. Asignment of null to the parent eld is replaced by assignment of an instance of this new type. This alternative is more modular, is always applicable, eliminates the need of testing for null parents, and last but not least, allows to simplify the language design. The use of the null pattern aects performance, depending on the application. If null parents are unlikely, elimination of the test for null slightly reduces execution time. If null parents occur often, the eliminated conditional branch will be outweight by the additional indirection needed to execute the code for the corresponding case. However, both eects should be almost unnoticeable relative to the total execution time of a program. The simulation of optional behaviour via mandatory parents possibly forces the creation of large hierarchies of dummy objects, which just implement null behaviour. However, implementing dummy objects as singletons [GHJV95] will mitigate this eect [WB98].

6.6

Explicit delegation

The ability to override inherited methods raises the need for a way to refer to the overridden method within the code of the overriding one. For this purpose, Java oers the super keyword, which refers to the same method in the (single) superclass. Explicit delegation is the corresponding way to refer to the same method inherited via one specic parent eld. Its syntax is ExplicitDelegation: Identier <- Identier ( ArgumentListopt )

122

CHAPTER 6. LAVA THE LANGUAGE

Static constraints. A compile-time error occurs if any of the following constraints is violated: 1. The left-hand-side Identier must denote a parent eld. 2. The right-hand-side expression Identier(ArgumentList) must be a method invocation for which there is an applicable method signature in the declared type of the parent eld. The dynamic semantics of explicit delegation is as expected: the call on the right-hand-side of the <- separator is delegated to the object referred to by the Identier on the left-hand-side. Delegation proceeds as specied by the extended dynamic binding scheme from denition 4.12.1 on page 79 and 4.12. The dynamic binding scheme of Java (15.11.4.4, page 335) ts neatly into these denitions: it is just a Java-specic version of the procedure evallocal () from denition 4.12 on page 74. In the following example, the super.m() and d<-m() calls refer both to the respective inherited methods:
class C extends S { protected final delegatee D d; public void m() { ... super.m(); // call the version of m() from S d<-m(); // delegate m() to the object referenced by d ... }

The main distinction between the two ways to refer to inherited methods is that super calls are bound statically, whereas explicit delegation is bound dynamically: the compiler cannot know whether the run-time value of a parent eld will be an instance of its declared parent type or of a subtype. This added dynamicity accounts for the increased expressiveness of delegation over class-based inheritance. It does not necessarily imply a loss of performance, since modern dynamically optimizing run-time systems (e.g. Suns HotSpot [Mic99a]) can still inline the parent methods and perform extensive optimizations (especially in the case of final delegation elds). Related implementation issues are discussed in chapter 7. Another distinction is that the syntax for explicit delegation enables to specify via which parent eld a message should be delegated. This is applicable also in the case of multiple delegation, whereas refering to a parent type would be ambiguous if dierent parent elds have the same type. Referring to a parent type might also convey the wrong intuition, obscuring the fact that the run-time type of a parent object is not xed.

6.7

Anticipated parent types

Support for type-safe yet semantically consistent dynamic delegation requires that parent types know about their potential role as delegation parents and

6.7. ANTICIPATED PARENT TYPES

123

about the distinction between the receiver of a message and the holder of the method being executed (see section 5.5). Classes and interfaces with this property are called anticipated parent types. They may be used as declared types of any delegation elds, including non-final ones. A corresponding extension of Java requires addition of a new class modier (delegatee used to declare anticipated parent types) and of two pseudovariable keywords (reciever and holder used within anticipated parent classes instead of this). Denitions. Classes and interfaces whose use as parent types is anticipated during design can be annotated with the keyword delegatee. They are called delegatee classes and delegatee interfaces, or, when the distinction is not relevant, delegatee types. Delegatee interfaces and subinterfaces of delegatee interfaces are called anticipated parent interfaces. Delegatee classes, subclasses of delegatee classes and classes implementing anticipated parent interfaces are called anticipated parent classes. When the distinction is not relevant, anticipated parent classes and anticipated parent interfaces are called anticipated parent types. A child class of an anticipated parent type is not an anticipated parent class, unless it is declared itself as delegatee, extends an anticipated parent class or implements an anticipated parent interface. All anticipated parent types derived from the same delegatee type T via extends or implements relationships are said to be rooted in T. Every delegatee type is rooted in itself. Constraints. Anticipated parent types may be used as declared types of nonfinal delegation elds. Use of other types as declared types of non-final delegation elds results in a compile-time error. Within any anticipated parent class use of the pseudovariable this in message and eld access expressions results in a compile-time error. The pseudovariable receiver or holder must be used instead. code inherited from a superclass that uses this will behave as if all messages to this had been sent to receiver. Within all anticipated parent classes rooted in the same delegatee type T receiver refers to the initial receiver of a message. The compile-time type of receiver is T regardless of the class that textually contains the keyword. Values of receiver may be instances of any subtype of T. holder refers to the method holder. The compile-time type of holder is the class that textually contains the keyword, say C. Values of holder must be direct instances of C. Code inherited by an anticipated parent class from a superclass that uses this will behave as if all messages to this had been sent to receiver (see 5.5.3 for a discussion of this constraint).

124

CHAPTER 6. LAVA THE LANGUAGE

Redeclaration of the type of receiver is illegal. A compile-time error results if a subclass of an anticipated parent class, a class that implements an anticipated parent interface or an interface that extends an anticipated parent interface is declared itself as a delegatee. In Darwin anticipated parent types rooted in a delegatee type DT must not have any supertypes whose compile-time types of receiver is dierent from DT or whose compile-time type of this is not a supertype of DT (see section 5.5.3 on page 96). (see section 5.5.3 on page 96). The exact statement of this simple principle in Lava terminology results in the following enumeration of all the cases that imply subtyping: A compile-time error results if an anticipated parent interface extends dierent anticipated parent interfaces that are not rooted in the same delegatee interface, or an anticipated parent class implements dierent anticipated parent interfaces that are not rooted in the same delegatee interface, or an anticipated parent interface rooted in the delegatee interface DI extends a normal interface, that is not a superinterface of DI, or an anticipated parent class rooted in a delegatee interface DI implements a normal interface, that is not a superinterface of DI, or an anticipated parent class rooted in a delegatee class implements any interface. Transitive delegation from an anticipated parent type is additionally constrained by rule 5.3 on page 100 in order to guarantee type-safety. A compiletime error results if an anticipated parent class that is not itself a delegatee class delegates, or a delegatee class delegates (either statically or dynamically) to a declared parent type that is no delegatee type.

6.7.1

Discussion

Impact of change. The requirement that anticipated parent classes use holder and receiver instead of this helps prevent errors by enforcing explicit statement of programmers intentions. Neither a compiler nor a run-time system knows enough about the intended semantics of a program to decide automatically whether the use of this in a particular context should refer to receiver or holder.

6.8. ATOMIC TYPES

125

The downside, however, is that adding the delegatee modier to a class (or interface) requires editing and recompiling of all its existing subclasses (or implementing classes) to perform suitable replacements of this by receiver or holder. This is clearly an inconvenience. It is still a signicant advance over the pattern-based approximation of the same functionality reviewed in section 3.6. Because it also changed the externally visible signature of methods the simulation required to edit and recompile the changed class, its subclasses, and all their clients. Moreover, the change from a normal type to a delegatee type is required only if that type is to be used as a target of dynamic delegation, whereas the simulation required all changes mentioned above even in the case of static delegation. One can conclude that Lava simplies implementation of forwarding-based designs while reducing the need for changes of existing code and limiting the impact of change.

6.8

Atomic types

Darwin supports a very general notion of atomic instances, which imposes just the restrictions necessary for static type-safety and security (see section 4.6.5 on page 63). Due to implementation considerations, Lava supports a more restrictive denition of atomic instances: Denition 6.1 (atomic instance Lava) An atomic instance of type T must have all of the following properties: its physical type must be a subtype of T and it must be either an atomic object (i.e. it must not forward itself at all), or it must be a split object, all its ancestors must be values of final parent elds and must have types that are no subtype of T .

So, an atomic instance of a type T can be a split object o that delegates to ancestors of types dierent from T , provided that the composition of o will not change. Without this additional constraint, the atomic type annotation of an expression would not be a type invariant. Then a split object o could be an atomic instance of type T at the time of assignment to a variable v of compile-time type @T but it could later change one of its ancestors to an instance of T so users of v could not rely on the @T annotation. The property of being an atomic instance of a given type must be veried dynamically whenever an expression of non-atomic type is converted to one of atomic type by narrowing reference conversion (see 6.9).

6.9

Subtyping

The subtyping relation is dened implicitly in Java via constraints imposed on the extends and implements relation and via the denition of legal type

126

CHAPTER 6. LAVA THE LANGUAGE

conversions. The following extensions to the specication of type conversions implement the Darwin principles that a delegating class is a subtype of all its declared ancestor types, an atomic type @T is a subtype of T, and that an atomic type @S is a subtype of another atomic type @T if S is a subtype of T that is not a descendant of T (section 4.6.5). Additionally, they express that a consulting class is a subtype of all its declared ancestor types. This is possible because the subtype relation of Java is more liberal than the one of Darwin, disregarding specialisation interfaces (see the discussion in section 6.9.1 on page 129). Widening Reference Conversions. The following widening reference conversions are legal in Lava in addition to those specied in the JLS (5.1.4, p.58): From any class type S to any class or interface type T, provided that T is a declared ancestor type of S. From any atomic type @S to the type S. From any atomic type @S to any atomic type @T, provided that the widening reference conversion from S to T is legal according to the rules of the JLS (which exclude delegation). Such conversions never require a special action at run-time and therefore never throw an exception at run-time. Narrowing Reference Conversions. The following narrowing reference conversions are legal in Lava in addition to those specied in the JLS (5.1.5, p.59): From any class or interface type S to any class type T, provided that S is a declared ancestor type of T. From any type S to the atomic type @S. From any atomic type @S to any atomic type @T, provided that the narrowing reference conversion from S to T is legal according to the rules of the JLS (which exclude delegation). Such conversion require a test at run-time to nds out whether the actual reference value is a legitimate value of the new type. If not, then a ClassCastException is thrown. Assignment Conversions. According to the JLS (5.2, p. 61) all widening reference conversions are legal assignment conversion. Therefore, the extended specication of widening reference conversions above automatically extends the specication of legal assignment conversions in Lava.

6.9. SUBTYPING

127

Casting Conversions. According to the JLS (5.5, p. 67) all widening and narrowing reference conversions are legal assignment conversion. Therefore, the extended specication of widening and narrowing reference conversions above automatically extend the specication of legal assignment conversions in Lava.

6.9.1

Discussion

Extended subtyping. The above denition of subtypes is a conservative extension of the JLS. It reects the structure of the class graph: subinterfaces are subtypes of their superinterfaces, classes are subtypes of the interfaces that they implement, subclasses are subtypes of their superclasses, and child classes are subtypes of their declared parent types. This extension of the subtyping relation enables unanticipated reuse of client code for type T as client code for some subtypes of T derived via delegation. For instance, it enables a decorator implemented via consultation or delegation to be passed into all contexts where an instance of the decorated type is expected, thus preventing the need to change any clients. Atomic types. Expressions where this additional functionality is not desired, can be declared to be an atomic type. Syntactically, this is the name of an interface or class type prexed with the symbol @, which reads at and should serve as a mnemonic for atomic. For instance, the declaration
@SecurityManager originalSM;

is legal in Lava. It means that the variable originalSM can only hold instances of SecurityManager or of subclasses of SecurityManager. In essence, the notion of atomic types enables the continued use of the subtype notion from the JLS, which does not accept delegating classes as subtypes of their declared parent types. Therefore, atomic types are not so much of a new concept, they are more like an intuitive name for what is valid in Java. The added ability of atomic instances of a type T to delegate to objects that are no instances of T could be discarded, if achieving a one to one correspondence to the non-delegating objects of Java were desired. There are two standard uses of atomic types: First, non-final delegatee elds must be declared to hold instances of atomic types in order to ensure type-safe dynamic delegation (see section 5.5 and 6.5). Second, atomic types can be useful in security-sensitive contexts, to guarantee that no unanticipated extensions of a certain type are passed into that context. Type-safety has been discussed extensively in the previous chapters. It is now time to discuss the impact of delegation on security, which has been neglected so far. Security. Delegation raises no specic security problem but it extends the degree to which aliasing can be exploited for security breaches.

128

CHAPTER 6. LAVA THE LANGUAGE

The basic security problem of aliasing is that whenever a parameter or a result is passed into and from method invocations, the object to which it refers is accessible from at least two contexts: from the object that sent the message and from the one that received it. Both can manipulate the shared (aliased) object via all its public methods. Therefore, passing values of security-sensitve private variables to (non-trusted) objects opens the door to potential security breaches. For instance, [BV99] and [VB99] describe a security attack based on the fact that the getSigners() method of class Class returned a reference to a private array of trusted signers, thus enabling an attacker to add its own signatures to the array. Delegation complicates the problem. It allows to gain access also to parameters and results of protected methods, because all dynamically bound messages to this (including invocations of protected methods) are redirected to the original receiver of a message. A child object may therefore be used as an intruder, which intercepts invocations of messages to this sent by a parent and manipulates the objects passed as parameters. An example is presented in [SdM95]. For this reason, the authors of [SdM95] conclude that delegation breaks encapsulation and recommend not to use delegation. Their proposal disables any form of unanticipated extensibility. This is much too restrictive, in practice. Instead of completely banning a useful programming and modelling technique just because it can also be misused, a language should provide means to control its use. There are at least three ways how this can be done in the case of delegation: Sealed classes Sealed classes are classes that cannot be extended. In Java, a class can be declared as final in order to disable extension via subclassing. In Lava, the semantics of final also prevents extension via delegation (see section 6.12 for details). In Lava 0.5 [Cos98] a special keyword (delegatee) has been provided to enable a class to be used as a parent type3 . This was motivated by the desire to be able to selectively disable extension of a class via delegation without disabling also inheritance from that class. Sealed expressions Sealed expressions are expressions whose declared type excludes certain forms of extension. They enable more exibility than sealed classes because various extensions of a class may exist and can be used benecially in dierent parts of a program. Programmers can, however, speciy that the use of certain types of extension is not allowed in certain contexts. Atomic types are a delegation-specic variation of this idea. They prevent that extensions of a type T via delegation may be used as values of expressions of type @T. Atomic types have been integrated into the current version of Lava because they allow more exibility than sealed classes and also solve the problem of typing dynamic delegation. Reference-specic access control Even more expressiveness can be achieved if there is no need to disable either extension via delegation or the use of
that in the design of Lava presented here the delegatee annotation of a class has a completely dierent semantics (see section 6.5 and 6.7).
3 Note

6.9. SUBTYPING

129

extensions in certain contexts. Instead of constraining delegation one can adress the problem of aliasing, which is the basic enabler of the described security breaches. One possible approach is to control the undesired eects of aliasing by attaching access control information to object references. For instance, a references can transport the information that it may only be used to read but not to change the state of the referened object. Similarly, it can know who may use it in this way. A model that incorporates these ideas of reference-specic access control is described in [Kni96a]. It can prevent the type of delegation-specic security breach discussed in [SdM95]. A subset that can be enforced statically and its integration into Java (JAC = J ava with Access C ontrol) is described in [The99, KT99, KT00]. These ideas have not been integrated into Lava, because JAC is not applicable to delegation and no ecient implementation for the full ACE model of [Kni96a] is known yet. Subtyping in Lava and Darwin. The denition of (extended) subtyping in Lava is not compatible to the subtyping relation and of Darwin (see denition 4.4 on page 62 and 4.5 on page 63). The reason is that the subtype notion of Java and, due to the desire for backward compatibility, also Lava is more liberal than that of Darwin. It accepts 1. that the specialisation interface of a subtype contains less methods than the (union of the) specialisation interfaces of its supertypes (see denition 4.4) and 2. that methods in a subtype change their throws declarations. The impact of changed throws declarations is the same as the impact of any signature change (e.g. covariant return type redenition) forbidden by the denition of semantic compatibility in section 4.12. Assume a child class with method m() that declares that it throws exception A and B, its declared parent class in which m() declares the same exceptions and a potential parent class that declares that m() throws only exception A. If m() from the child overrides m() from the potential parent, then the exception B might be thrown in places that are not prepared to catch it. The impact of item 1 is that consulting classes are accepted as subtypes of their declared ancestor types and classes that contain final methods are accepted as subtypes of their superclasses (and implemented interfaces) that contain non-final methods with the same signature. This is incorrect: For instance, a class with a declared parent type that contains a non-final method, say m(), expects that local implementations of m() will override implementations of m() in every parent object. This expectation is violated if objects that contain a final m() method may be used as parents. Section 6.12 provides a denition of final methods that tries to reconcile as much as possible the contrary expectations of child objects and parent objects with respect to the possibility to override specic methods. Another alternative is the use of a specic run-time exception that is thrown upon assignment to a parent attribute, if the assigned values expectations with

130

CHAPTER 6. LAVA THE LANGUAGE

respect to overriding conict with the child objects expectations (regarding final status and throws clauses). This would be possible, given that assignments to parent attributes can throw exceptions already for other reasons. However, exceptions still are just another attempt to limit the damage introduced by an unsound subtype relation. If 100% backward compatibility with Java were no main goal, then the subtype relation could be dened to conform to Darwin: classes that contain a final method would not be subtypes of classes or interfaces that contain a non-final method with the same signature; similarly, classes that change the throws declaration of a method would also not be accepted as subtpyes.

6.10

Parent Cast

The previous section has redened the semantics of dynamic cast operations to take into account extended subtyping and atomic types. This implicitly extends also the denition of the dynamic type check expr instanceof type, which returns true if the cast (type) expr would succeed and false if it would throw an exception. This section additionally introduces the parent cast operation. It diers from the above operations by the fact that, if it succeeds, it does not necessarily return a reference to the object produced by evaluating expr. Instead, it can also return a reference to a parent object. Its syntax is ParentCast: < Expression > UnaryExpressionNotPlusMinus Like the other dynamic type checking expressions, the parent cast is always statically legal. If o is the object resulting from the evaluation of UnaryExpressionNotPlusMinus at run-time, then the parent cast returns a reference to o, if o is of type Expression and to the most specic ancestor of o that is of type Expression, otherwise. If neither o nor any of its ancestors is of type Expression then a ClassCastException is thrown. A detailed comparison of the parent cast with the Javas standard cast expression and an example of its use is contained in section 5.5.7 on page 102.

6.11

Overriding

The denition of overriding (JLS, 8.4.6.1) is adapted to Lava as follows: If a class declares an instance method, then the declaration of that method is said to override any and all methods with the same signature in the declared parent types, superclasses and superinterfaces of the class [deleted: that would otherwise be accessible to code in the class]. The denition of overriding at class-level extends to object-level overriding as follows: 1. If a method of a class overrides a method from a declared parent type it will override methods with the same signature from every object that is a legal value of the respective parent eld.

6.12. FINAL CLASSES AND METHODS

131

2. If a method of a class does not override a method from a declared parent type it will not override any method with the same signature from any object that is a legal value of the respective parent eld. Note that the rst rule of object-level overriding conicts with Javas notion of final methods. This issue is discussed in section 6.12 and the compromise established there represents an exception to the general denition given here.

6.11.1

Discussion

The denition of object-level overriding implements the semantic compatibility criterion of Darwin (see section 4.12). The deletion of the accessibility constraint (the last part of the above quote from the JLS) is due to the fact that it is considered unintuitive and too restrictive even by the designers of Java. This is mentioned in a note on the HotSpot virtual machine [Mic99b], which also hints that a removal of the accessibility constraint is being considered for Java. In fact, no Java Virtual Machine before HotSpot ever implemented the accessibility constraint [Mic99b].

6.12

Final classes and methods

The JLS species the semantics of the final classes as follows (8.1.2.2, page 133): A compile-time error occurs if the name of a final class appears in the extends clause (8.1.3) of another class declaration; this implies that a nal class cannot have any subclasses. [...] Because a final class never has any subclasses, the methods of a final class are never overridden (8.4.6.1). The JLS species the semantics of the final methods as follows (8.4.3.3, page 160): It is a compile-time error to attempt to override or hide a final method. These specications are replaced by the following Lava specications (changes are in italic font face): Final Classes A compile-time error occurs if the name of a final class appears in the extends clause (8.1.3) of another class declaration or in the declaration of a delegatee eld (section 6.5); this implies that a nal class cannot have any subclasses or delegating child classes. [...] All methods dened in a final class are implicitly final methods. All methods inherited by a final class from a superclass or declared parent type are implicitly final methods in the context of the inheriting class. This does not change their status in the context of the superclass or parent type from which they were inherited.

132

CHAPTER 6. LAVA THE LANGUAGE

Final Methods It is a compile-time error to attempt to override or hide a final method from a superclass or declared parent class. A message whose receiver expression has compile-time type T and whose invoked method is declared final in T must be bound to exactly that method from T. Inlining cannot be done at compile time unless it can be guaranteed that the class that contains the invocation and the class that contains the invoked method will always be recompiled together, so that whenever the latter changes the former will also be updated.

6.12.1

Discussion

In Darwin, child classes of a type that contains a non-final method, say m(), expect that local implementations of m() will override implementations of m() in any parent object. In Java, classes that contain final methods expect that they cannot be overridden in any subtype of the class. The denition of nal classes and methods given above attempts a compromise between these two conicting expectations. It enforces that the expectations that hold in the context of a method invocation are fullled. If an object, c, that expects to be able to override m() has a parent, p, that contains a final m() method, then invocations of m() in a context that expects that m() is bound statically, will be bound statically. For instance, invocations contained in the class of p will be bound to the implemention of m() valid in that class. The m() method from c will not be selected in this case, hence it will not override the final method. invocations of m() in a context that expects that m() is bound dynamically, will be bound dynamically. For instance, invocations contained in a superclass of ps class where m() is not final, will be bound to the implemention of m() from c. In this case cs implementation will override the final one of p.

6.13

Simple Assignment Operator

Enforcing the dynamic constraints of mandatory elds (section 6.4) and parent elds (section 6.5) requires the following extensions to the denition of the simple assignment operator = (15.25.1, page 369371). In the following citations from the JLS, modications and additions are highlighted by italic font face: At run-time, the [assignment] expression is evaluated in one of two ways. If the left-hand operand expression is not an array access expression, then ve steps are required: First, the left-hand operand is evaluated to produce a variable. If this evaluation completes abruptly, then [...]

6.14. EXCEPTIONS Otherwise, the right-hand operand is evaluated. If this evaluation completes abruptly, then [...] Otherwise, if the left-hand operand is a mandatory variable and the value of the right-hand operand is null, then no assignment occurs and an AssignmentOfNullException is thrown.

133

Otherwise, if the left-hand operand is a parent eld and assignment of the right-hand operand would create a forwarding cycle (denition 4.11 on page 66), then a CyclicForwardingException is thrown. Otherwise, if the left-hand operand is a parent eld and the right-hand operand is no atomic instance of the elds declared type, then a NonAtomicInstanceException is thrown. Otherwise, the value of the right-hand operand is converted to the type of the left-hand variable and the result of the conversion is stored into that variable. If the left-hand operand expression is an array access expression (15.12), then many steps are required: [... ve items omitted ...] Otherwise, the value of the index subexpression is used to select a component of the array [...] [... two items omitted ...] If class RC is not assignable to type SC [...] Otherwise, if the left-hand operand is a mandatory variable and the value of the right-hand operand is null, then no assignment occurs and an exception is thrown (an AssignmentOfNullException). Otherwise, the reference value of the right-hand operand is stored into the selected array component.

6.14

Exceptions

If null is assigned to a mandatory eld an AssignmentOfNullException is thrown. If the assignment of a non-null value to a (mandatory) parent eld would create an incomplete split object an IncompleteObjectException is thrown. The check for creation of incomplete objects may be pessimistic: it may reject assignments that create a delegation cycle even if the created object would not be incomplete. In this case a CyclicForwardingException must be thrown. If an assignment violates the constraint that a variable may only hold atomic instances of its declared type it must throw a NonAtomicInstanceException. NonAtomicInstanceException is a subclass of ClassCastException. AssignmentOfNullException and CyclicForwardingException are subclasses of RuntimeException. IncompleteObjectException is a subclass of CyclicForwardingException. All Lava exceptions are part of the package de.uni-bonn.lava.lang.

134

CHAPTER 6. LAVA THE LANGUAGE

6.15

Summary

This chapter has shown that the ideas of Darwin can be incorporated easily into the design of Java. Most changes to the Java Language Specication presented in this chapter are straightforward adaptations of Darwin denitions. The only conceptual problem encountered is related to Javas notion of final methods. This issue has been discussed in section 6.9 and 6.12. A real solution (which avoids the problem) would require to redene the subtype relation so that classes that contain a final method are no subtypes of classes or interfaces that contain a method with the same signature. However, this rule would render existing Java programs illegal in Lava. A solution that limits the extent of the problem while retaining backward compatibility to Java has been presented in section 6.12. The implementation of Lava is presented in the next chapter. Applications are discussed in chapter 8.

Chapter 7

Implementation
This chapter discusses dierent ways to implement Lava. The translation scheme presented here generalizes the one introduced in [Kni94] for C++-type native code compilers and incorporated into an extension of the Java Virtual Machine [AG96, LY97] in [Sch97, Cos98]. The advance over this precursor work is the description of a portable implementation scheme, which translates Lava programs to standard Java bytecode [LY97]. Although it generates bytecode, just like any Java compiler, the translation is described as if it generates Java sourcecode, in order to improve its readability. We describe the translation in terms of source and target programs. Syntactic and semantic checks required by the language specication (Chapter 6) are not described. The target programs are generated only after all required checks succeeded. The description starts from the basic scheme presented as a design pattern in section 3.6, discusses the extensions needed to implement the additional functionality of Lava over that pattern, summarizes the resulting general scheme for implementing static and dynamic delegation, describes possible optimisations for each of the two cases and nally summarizes the optimized scheme.

7.1

Terminology and Notation

In descriptions of translation schemes, placeholder code is set in italic font and code that appears literally is typeset in typewriter font. For instance, this.msg(arg) is some message sent to this. Here msg and arg are metavariables (non-terminal symbols) that will be replaced by suitable values (e.g. the method name clone and the eld name o) during the translation process. Examples are set completely in typewriter font, as usual. We distinguish original methods, contained in the source code, from translated methods, executed at run-time. A translated method that contains the compiled version of the body of an original method is called a worker method (because it is the one that performs the work of the corresponding original method). Translated methods can also be methods generated automatically by the compiler in order to perform additional tasks specic to the translation. Generated methods include bridge methods (7.2.2), forwarding methods (7.2.6.4), eld access methods (7.2.3), parent access methods (7.2.6.1), etc. 135

136

CHAPTER 7. IMPLEMENTATION

Worker methods and all generated methods except bridge methods have names that are enclosed in $ symbols, e.g. $getparent$(). Because these methods are not contained in the source code, they are also called hidden methods. External code is translated code that has not been generated according to the translation scheme described here. Bridge methods reroute invocations contained in external code to the corresponding hidden methods. They mediate between the external and internal view of a program (hence their name).

7.2

Basic Delegation Scheme

The translation to Java builds upon the design pattern for simulating delegation discussed in section 3.6. It preserves the basic idea of implementing forwarding by passing self (respectively this in Lava) as a message argument and of using interfaces to let child classes be subtypes of their declared parent types. The adaptations and extensions of these ideas required for Lava are described in this section.

7.2.1

Extended Method Format

Lava enables unanticipated (static) delegation to any interface type and class type in a program. In order to enable every type to be used as a parent type, all its methods (except native ones) are extended by an additional parameter, called receiver. This parameter is the direct implementation of Lavas receiver keyword, respectively of the implicit role of Lavas this as receiver in the context of delegated messages. The Lava keyword holder is mapped to Javas this. The translation of these keywords is shown in the rst three rows of Table 7.1 on page 137. Please recall that the keyword this has dierent meanings in Java and Lava. It is therefore necessary always to be aware of the context in which it is used (Lava source code or Java target code) in order to avoid confusion. Message sending expressions and explicit delegation expressions are translated on the basis of this extended method format. The corresponding translation of method headers, explicit and implicit messages and explicit delegation is summarized in Table 7.1. The common principle is that the rst parameter of each translated version of an original method is the receiver parameter (row 4 of Table 7.1), implicit messages are invoked on receiver (row 5 of Table 7.1), translated versions of implicit and explicit messages pass the object on which they are invoked as the rst argument of the invoked method (row 5 and 6 of Table 7.1) and explicit delegation passes the current value of receiver into the receiver parameter of the forwarded method invocation (row 7 of Table 7.1) In the translation of the original method signature to the signature of the corresponding worker method (row 3 of Table 7.1) the type of the receiver parameter is left unspecied. It is discussed in section 7.3.

7.2. BASIC DELEGATION SCHEME What Keyword Keyword Keyword Method signature Implicit message Explicit message Explicit delegation Source code this receiver holder T msg(T1 arg) msg(arg) object.msg(arg) parent<-msg(arg)

137 Target Java code receiver1 receiver this T $msg$(... receiver, T1 arg) receiver.$msg$(receiver,arg) object.$msg$(object, arg) parent.$msg$(receiver,arg)

1 2 3 4 5 6 7

Table 7.1: Implementation of delegation

7.2.2

Bridge Methods

The compiled program must be usable also from external code (see 7.1), which does not know about the modied signature of translated methods. Therefore the translation must preserve the original signatures of methods in all types of the translated program and must add corresponding signature bridge methods to all classes. For every non-private original method with signature
T msg(T1 arg)

the corresponding bridge method is


T msg(T1 arg) { return $msg$(this, arg) }

if T is not void and


void msg(T1 arg) { $msg$(this, arg) }

otherwise. The schema for bridge methods that return a result will be rened in section 7.2.6.5 on page 148. A bridge method has the same visibility status as the original method (public, protected, or package). Bridge methods add one extra indirection to every method invocation contained in external code. Translated code calls the hidden methods directly.

7.2.3

Accessor Methods

Local eld access. Any access of an object to its own elds is translated into an access to the pseudovariable this. For instance, the expressions var, this.var, holder.var are all translated into this.var. This is just what one would expect and involves no complications: because the value of this in Java is guaranteed to be an atomic instance (no delegating object) these eld accesses are safe. External eld access. In contrast, translation of direct access to public or package-visible elds of other objects is complicated by the fact that in Lava child objects can be passed wherever parent objects are expected2 . This is
where explicitly specied otherwise by the programmer: expressions of atomic type and the pseudovariable holder never evaluate to instances of child types.
2 Except

138 illustrated by the following code:


class T { public T var; }

CHAPTER 7. IMPLEMENTATION

class Client { void m(T arg) { ... = arg.var; // goes wrong if "arg" refers to a child of T arg.var = ... // goes wrong if "arg" refers to a child of T } }

Because child instances contain only the methods but not the elds of their declared parent types, the expression arg.var will try to access an unavailable eld if arg refers to a child instance. Therefore direct access to externally visible elds of other objects is replaced by the invocation of an (automatically generated) accessor method. For instance, the above source-code will result in the following translated code:
class T { public T var; // accessor method public T $getvar$() { return var }; public void $setvar$(T val) { var = val }; // accessor method } class Client { void m(T arg) { m(this, arg); } void m(... receiver, T arg) { arg.$getvar$(); arg.$setvar$(val); } }

// // // //

bridge method worker method accessor invocation accessor invocation

The previous example follows the general scheme for external eld access from row 1 and 2 of Table 7.2. Row 3 and 4 show the scheme for local eld access. Table 7.3 on page 139 shows the general scheme for for generation of local accessor methods. What External eld read access External eld write access Local eld read access Local eld write access Source code object.var object.var = val var this.var holder.var var = val this.var = val holder.var = val Target Java code object.getvar() object.setvar(val) this.var

1 2 3

this.var = val

Table 7.2: Local and external eld access

7.2. BASIC DELEGATION SCHEME Lava source code T var; (in local class) Target Java code T var; T getvar(){ return this.var } void setvar(T val){ this.var = val }

139

Table 7.3: Accessor methods for locally dened elds As a nal remark, it should be noted that only instance variables have accessor methods. Class variables do not need them because classes are global entities. From the transformations introduced so far, generation of bridge methods and extended format worker methods is applied to all classes and interfaces of a program. Generation of accessor methods and implicit interfaces (see 7.2.6.5 on page 148) is done for all classes. These transformations are required to prepare classes for their potential role as parent types.

7.2.4

Mandatory elds

After checking that the static constraints dened in section 6.4 hold, the translation of mandatory elds must enforce that assignments to mandatory values are the rst code executed on a newly allocated object (see 7.2.4) and that the left-hand side of every assignment to a mandatory eld is not null. Assignment Checking. The second constraint is enforced by prepending the code
if (val == null) throw new AssignmentOfNullException();

to each assignment of a value val to a mandatory eld var (Table 7.4). Constructor Translation. In order to enforce the rst constraint, constructors are split into two parts: one that allocates the new object by calling Javas default (zero argument) constructor and one that initialises the new objects variables. The initialisation part is an instance method that has the same arguments as the original constructor and and a void return type. It contains all the code from the constructors body, up to two changes: every invocation of a superclass constructor (via super(...)) is replaced by the invocation of the corresponding initialisation method of the superclass and this invocation is moved after the Lava source code var = val; Target Java code if (val == null) throw new AssignmentOfNullException(); var = val;

Table 7.4: Check of assignment to mandatory eld var

140

CHAPTER 7. IMPLEMENTATION

sequence of assignments to locally dened mandatory elds. Similarly, every invocation of another constructor of the same class (via this(...)) is replaced by the invocation of the corresponding local initialisation method and this invocation is moved after the sequence of assignments to locally dened mandatory elds. This schema is illustrated by the following code template. The constructor denition
class C { mandatory T var; C(T v, ...) { super(...); var = v; // rest }

is translated to
class C { T var; C() { super(); } void $initialize$(T v, ...) { var = v; super.$initialize$(...); // rest }

and every constructor invocation


C c = new C(v, ...); // allocate and initialise

is translated to
C c = new C(); c.$initialize$(v, ...); // allocate // initialise

As a result of this implementation, local mandatory elds are initialised rst, then the mandatory elds of the immediate superclass, then those of the next superclass, etc. This reordering of initialisation instructions would be illegal if the right-hand-side of assignments to mandatory elds were arbitrary expressions with side-eects. The restriction of right-hand-side values to arguments of the constructor (see 7.2.4) is motivated by the need of our implementation scheme to reorder initialisation statements safely. The rest of each constructor (that comes after the initialisation sequence for mandatory elds) will be performed only after the initialisation of all manda-

7.2. BASIC DELEGATION SCHEME

141

tory elds of the object, including those dened in subclasses. These rest parts will be executed in the right order, i.e. the one of the most general superclass rst, the one of the instantiated class last. The above target code is not really generated. It is subject to the insertion of assignment checking code (Table 7.4).

7.2.5

Atomic Instances

The property of being an atomic instance of a given type must be veried dynamically whenever an expression of non-atomic type is converted to one of atomic type by narrowing reference conversion (see section 6.8). So, the implementation of atomic instances reduces to the following three simple steps: Every atomic type designator @T in a type declaration is replaced by T . For a narrowing conversion of an atomic type @S to another atomic type @T the compiler emits the code for a narrowing conversion of S to T . For a narrowing conversion of a non-atomic type S to an atomic type @T the compiler inserts an invocation of the $isAtomicInstance$(T ) method. If the conversion in the sourcecode is not an invocation of an instanceof @T operation, then an additional check is inserted that throws a NonAtomicInstanceException if $isAtomicInstance$(T ) returns false. The isAtomicInstance method is implemented in every translated class as follows:
boolean $isAtomicInstanceOf$(Class targetType) { if (! targetType.isInstance(this)) return false; Child current = this; while (current.isChild()) { current = current.$getparent$(); if ( (targetType.isInstance(current)) return false; }; return true; }

For the $getparent$ and $isChild$ method see section 7.2.6.1. An example of source code that includes both variants of the dynamic test possible in Lava is shown in Listing 7.1. Listing 7.2 shows its translated version (after elimination of statically rejected assignments).

7.2.6

Delegatee Fields

Now we turn to the implementation of child classes, respectively of parent elds. Please recall that the declaration of a eld as delegatee or consultee has three eects: Interface inheritance: The interface of a delegating child class is extended by all public and protected instance elds and instance methods

142
GuardedObject g; @GuardedObject ag; g = ag; ag = g; ag = (@GuardedObject) g; if (g instanceof @GuardedObject) ... @SubclassOfGuardedObject ags; ag = ags;

CHAPTER 7. IMPLEMENTATION
// general type // atomic type // statically safe // statically rejected // statically safe checked cast // statically safe dynamic check // atomic type // statically safe

ags = ag; // statically rejected ags = (@SubclassOfGuardedObject) ag; // statically safe checked cast if (g instanceof @SubclassOfGuardedObject) ... // safe dynamic check

Listing 7.1: Checking for atomic instances source code


GuardedObject g; GuardedObject ag; g = ag; if (g.$isAtomicInstanceOf$(Class.forName("GuardedObject"))) { ag = (GuardedObject) g; } else throw new NonAtomicInstanceException(); if (g.$isAtomicInstanceOf$(Class.forName("GuardedObject") ) { ... } SubclassOfGuardedObject ags; ag = ags; ags = (SubclassOfGuardedObject) ag; if (g instanceof SubclassOfGuardedObject) ...

Listing 7.2: Checking for atomic instances translated code from the declared type of its parent eld. The interface of a consulting child class is extended only by the public instance elds and instance methods from the declared type of its parent eld. Subtyping: Any child class is a subtype of the declared type of its parent eld. Automatic Forwarding: Every eld access and method invocation for which there is no applicable signature (see denition 4.15) in the type of a given object, o, is automatically forwarded to the current value of os parent eld. In the following we assume that the delegatee eld is called parent, its type is either the interface PI or the class PC (for parent i nterface and parent class) and the child class is called accordingly CC or CI (for child of class and child of i nterface). In other words we show next how to compile the rudimentary denitions from Listing 7.3 on page 143. Note that the class PC contains a public eld, a

7.2. BASIC DELEGATION SCHEME

143

protected eld and a protected method, which account for most of the added translation complexity compared to the interface PI. The remainder of this section discusses the dierent eects of a parent eld denition.
class CI { final protected mandatory delegatee PI myParent; CI(PI p) { myParent = p } } interface PI { T m1() } class CC { final protected mandatory delegatee PC myParent; CC(PC p) { myParent = p } } class PC { public T m1() { v1 = m2(); return v1 }; protected T m2() { return v2 }; public T v1; protected T v2;

Listing 7.3: Example source code: declared parent interface versus declared parent class

7.2.6.1

Parent Access

The primary eect of a parent eld declaration is the eld declaration itself, as trivial as this might sound. So, the rst step of the translation is the generation of the eld declaration and of its hidden accessor methods. The normal read and write access methods described in section 7.2.3 are generated also for parent elds and used for all access expressions contained in the source program. However, there are two peculiarities: there is an additional read access method and the write access method is extended by code that performs specic checks. Additional read access method. The additional read access method for the parent eld has the standardized visibility and signature public Child $getparent$(). It enables uniform access from any part of the program to the parent eld of child objects of unknown type. The type Child is declared as:
interface Child { public boolean $isChild$(); public Child $getparent$(); }

Note that $getparent$() is expected to return an object that also provides the Child interface. Every object in a translated program must provide the Child interface. Accordingly, every translated interface is declared as a subinterface of Child and every translated class is declared to implement the Child interface. Every class that denes a parent eld contains the implementation:

144

CHAPTER 7. IMPLEMENTATION

public Child $getparent$() { return parent; }

Its subclasses inherit this implementation. Every class that is no child class contains the implementation:
public Child $getparent$() { return null }

All this is to ensure that we do not have to add casts to translated code in places where we know that they will be useless, either because the returned object is guaranteed to have a parent or because the translation scheme is guaranteed not to invoke $getparent$() on objects that have no parent. The $getparent$() method is used internally by the translation scheme, e.g. in the implementation of the parent cast operation (see section 7.3.3 on page 156) and in the implementation of cycle checking during assignments to parent attributes (described next). Extended write access method. There is no need to access uniformly all parent elds throughout a program for the purpose of modifying them. Therefore no extra mutator method with standardized signature is generated. The normal write access method described in section 7.2.3 is generated also for parent elds and used for all write access expressions contained in the source program. However, its body is extended to ensure that the assignment does not generate a forwarding cycle. The cycle checking code generated for an assignment parent = newvalue is shown below. The check starts from the object to be assigned as a new parent and checks whether it is a child of the object whose delegation attribute is about to be changed. If the check fails, it is performed on the ancestors of newvalue, which are accessed using the standardized $getparent$() method. If the check succeeds a CyclicForwardingException is thrown.
void $setparent$(DeclP arentT ype newParent) { // mandatory check: if (newParent == null) throw new AssignmentOfNullException(); // full cycle check: if (newParent == this) throw new CyclicForwardingException(); Child current = newParent; synchronized(Lava.ParentUpdate) { while (current.isChild()) { current = current.$getparent$(); if (current == this) throw new CyclicForwardingException(); } ; // intended assignment: parent = newParent; }; // end synchronized

7.2. BASIC DELEGATION SCHEME


}

145

In the case of parent elds of an atomic type (@DeclP arentT ype) the iterative part of the cycle checking code is replaced by the stronger check for being an instance of an atomic type an exception is thrown if any of the ancestors of the new parent is itself an instance of DeclP arentT ype. No synchronisation is needed in this case because the atomic instance check will complete normally only if all parent elds are final, in which case no interference from other threads is possible:
void $setparent$(DeclP arentT ype newParent) { // mandatory check: if (newParent == null) throw new AssignmentOfNullException(); // reduced cycle check: if (newParent == this) throw new CyclicForwardingException(); // atomic instance check: if ($isAtomicInstanceOf$(Class.forName(DeclP arentT ype))) throw new NonAtomicInstanceException(); // intended assignment: parent = newParent; }

The code of parent modication methods always starts with the check for null values because all parent elds are mandatory (see 7.2.4 on page 139 and 6.4 on page 114).

7.2.6.2

Interface Inheritance

The eect of interface inheritance is achieved by extending every child class with automatically generated method denitions. A consulting child class is extended with denitions of all public methods from the (translated version of the) declared parent type for which there is no method with the same signature in the original version of the child class or the translated version of its superclass. A delegating child class is extended with denitions of all public and protected methods from the (translated version of the) declared parent type for which there is no method with the same signature in the original version of the child class or the translated version of its superclass. Note that in the case that the parent type is a class the above denitions include interface inheritance of public and protected members this eect is achieved by inheriting their hidden accessor methods or worker methods (see 7.2.6.3 on the next page). Interface inheritance is closely related to automatic forwarding (section 7.2.6.4 on the following page): inherited methods must forward their invocations to the parent object. Therefore they are also called forwarding methods.

146 Source method public protected package private Source eld public protected package private

CHAPTER 7. IMPLEMENTATION Bridge method public protected package Target eld public protected package private Hidden worker method public public package private Hidden accessor methods public public package

Table 7.5: Visibility of translated methods and elds. 7.2.6.3 Access to protected members

In Lava child classes have access to all the protected methods and elds of their declared parent classes. Because direct access to protected members of another object is not possible in Java, the translation scheme generates public accessor methods for protected elds and public worker methods for protected methods. Table 7.5 summarizes the visibility status of translated methods and elds. These methods are invoked by the forwarding methods generated in child classes (section 7.2.6.4 on the next page). Each forwarding method has the same visibility as the method with the same signature from the declared parent type. 7.2.6.4 Automatic Forwarding

Forwarding of method invocations. Forwarding of method invocations is trivial, given the extended method format and translation scheme from section 7.2.1 on page 136. In a consulting child class, the methods inherited from parent types are implemented as
public void aP arentM ethod(arg) { parent.$aP arentM ethod$(parent, arg); } public void aP arentM ethod(... receiver, arg) { parent.$aP arentM ethod$(parent, arg); } // bridge method

// worker method

In a delegating child class, forwarding methods are implemented as


public void aP arentM ethod(arg) { // bridge method parent.$aP arentM ethod$(this, arg); } public void aP arentM ethod(... receiver, T arg) { // worker method parent.$aP arentM ethod$(receiver, arg); }

7.2. BASIC DELEGATION SCHEME What Forwarded read access to public eld Forwarded write access to public eld Forwarded read access to protected eld Forwarded write access to protected eld Source code var this.var holder.var var this.var holder.var var this.var holder.var var = val this.var = val holder.var = val Target Java code

147

this.getparent()a.var

this.getparent()a .var = val

this.getparent()a.getvar()

this.getparent()a .setvar(val)

Table 7.6: Forwarded eld access


a If the accessed eld var is contained in the N -th declared ancestor type, then the getparent() access is repeated N times, before the specic accessor method is called. For instance, N = 2 means this.getparent().getparent().

Forwarding of eld access. Using the accessor methods of elds in declared ancestor types and the ubiquitous $getparent$() method (section 7.2.6 on page 141), access to elds of ancestor objects is implemented as shown in Table 7.6. Access to public elds is shown in row 1 and 2 and access to protected elds is shown in row 3 and 4. In both cases the $getparent$() method is invoked repeatedly, until the ancestor object that contains the desired eld is reached. The only distinction is that a public eld is accessed directly, whereas a protected eld can be accessed only via its public accessor method. Alternatively, one could invoke parent.$getvar$() knowing that the denition of $getvar$() either returns the local value (see Table 7.2.6.1 on page 143) or forwards the invocation to the next ancestor (see Table 7.7). However, this alternative would imply the risk of accidental overriding. For instance, a definition of the $getvar$() method in the parent would override one from the grandparent, although the respective eld from the parent must not override a eld with the same name from the grandparent. The specication of Lava and Java generally prohibits overriding of elds. The implementation using $getparent$() avoids this subtle change of the compiled programs semantics with respect to the source code semantics. Note that we are talking here about the translation of direct eld access expressions. If the programmer has written and used own accessor methods in the source code, they will be treated like any other method, including the ability to override them. Thus overriding of elds is possible, if intended by the programmer. Finally, it is worth mentioning that Java requires a cast prior to the eld access (omitted in Table 7.6 and 7.7). For instance, if the public eld var is contained in the declared parent class PC then the exact access code for reading it is ((P C)(this.$getparent$())).var

148 Lava source code denition T var; in ancestor class

CHAPTER 7. IMPLEMENTATION Target Java code T getvar(){ return this.parent.getparent()a .getvar() } void setvar(T val){ this.parent.getparent()a .setvar(val) }

Table 7.7: Accessor methods for elds dened in declared ancestor types
a If the accessed eld var is contained in the N -th declared ancestor type, then the getparent() access is repeated N 1 times, before the specic accessor method is called. For instance, N = 2 means this.parent.getparent().

This cast will always succeed. If the name of the immediate parent eld is known and visible then slightly more ecient code can be generated, where $getparent$() invocations are used only for accesses to ancestors from the grandparent onwards. The code of forwarding accessor methods is shown as an example in (Table 7.7 on the following page). The more general variant shown so far must be used in subclasses of a child class whose parent eld has private or package visibility. 7.2.6.5 Subtyping

In Lava, any child class is a subtype of the declared type of its parent eld. This eect is achieved in the translated code by adding implements declarations to the child class and eventually creating a suitable Java interface for the parent class. Declared parent interface. This is trivial if the declared parent type is itself an interface. Then we only have to add an implements part to the declaration of the child class for instance, in the running example for this section (left part of Listing 7.3 on page 143): class CI implements PI. Declared parent class. If the declared parent type is a class, PC, the procedure involves three steps: First, an interface P CI must be generated. It contains all the public methods generated for the translated parent class according to Table 7.5 and declares that it extends all the supertypes of the parent class. More precisely, if the parent class implements the interfaces I1 , ..., In and extends the class Super then PCI extends I1 , ..., In and SuperI, the interface generated for Super. Then a declaration implements P CI is added to the child class and to the parent class. Finally, the name of the declared parent class PC is replaced by the name of the generated interface PCI in all type declarations throughout the program, except in the signature of bridge methods.

7.2. BASIC DELEGATION SCHEME


class CI implements PI { final protected PI parent; ... } class CC implements $PCI$ { final protected $PCI$ parent; ... } class PC implements $PCI$ { ... } interface public public public public public public public } $PCI$ extends $Child$ { $TI$ $getv1$() ; $TI$ $getv2$() ; void $setv1$($TI$) ; void $setv2$($TI$) ; T m1() ; $TI$ m1(... receiver) ; $TI$ m2(... receiver) ;

149

Listing 7.4: Implementation of subtyping for declared parent interface versus declared parent class. Target code for source from Listing 7.3 on page 143. Applied to the example from Listing 7.3 these transformations produce the target code shown in Listing 7.4. The right-hand-side of the code assumes that the type T (from the source code) is a class. Therefore it has been replaced in the target code by the corresponding (generated) interface $TI$. Adapters. Note that the replacement of class names by the name of the corresponding generated interface is done throughout the program except in the signature of bridge methods, which must not change (compare the two underlined method signatures above). Therefore, bridge methods must check whether the result of a working method (of replaced type, e.g. $P CI$) is compatible with the expected result type of the bridge method (of original type, e.g. PC ). If the result is an instance of a child type, e.g. CC, then it is wrapped into an adapter object that provides the expected type, PC, before being passed to external code. So the general schema of bridge methods that return a result becomes the one shown in Listing 7.5. It applies only to genuine bridge methods, not to forwarding ones. A forwarding bridge method can rely on the type-correct (possibly wrapped) result of the executed genuine bridge method of an ancestor. The schema for implementing the extended subtyping relation and for constructing adapters is illustrated in Figure 7.1 on the following page. Each adapter class is a subclass of the class expected by the external code. It implements all methods from the interface $T I$ by simply forwarding to the wrapped object, which is an instance of $T I$. Adapters are discussed further in connection with possibly type-unsafe values of receiver in the next section, where a slightly more complex forwarding

150
LAVA

CHAPTER 7. IMPLEMENTATION

CC

PC

Java implementation 1 1

Object

<PCI>

<<implements>> CC PC

PC- Adapter

Objects at run-time

adapter

cc

pc

Figure 7.1: Adapters for type-safe passing of child objects to external code scheme, which covers both cases, will be introduced.

7.2.7

Summary of basic scheme

The full code generated for the example from Listing 7.3 on page 143 is shown in Listing 7.6 on the facing page and Listing 7.7 on page 152. Listing 7.6 shows the code generated for the child class CC. Listing 7.7 shows the code generated for the declared parent class PC and the generated interface $PCI$. Only the translation of the case in which the declared parent type is a class (right hand side of Listing 7.3) is shown. In the case of a declared parent interface, the extra interface $PCI$ would not be generated. Adapter classes are not shown, because the description of adapters is still

T msg(T1 arg) { $T I$ res = $msg$(this, arg); // call worker method if (res instanceof T ) return (T ) res; else return new $T A$(res); } // if result is of expected type // return result // else return wrapped result

Listing 7.5: General schema of bridge methods that return a result of type T . $T A$(res) creates an adapter object of type T.

7.2. BASIC DELEGATION SCHEME

151

class CC implements $PCI$ { CC($PCI$ parent) { $setmyParent(parent) }

// subtyping (7.2.6.5) // translated constructor

// parent field -----------------------------------------------------final protected $PCI$ myParent; // parent field (7.2.6.1)

public Child $getparent$() { // standard get method (7.2.6.1) return myParent; } public T $getmyParent$() { // normal get method (7.2.3) return myParent } public void $setmyParent$($PCI$ newParent) { // extended set method (7.2.6.1) if (newParent == null) throw new AssignmentOfNullException(); if (newParent == this) throw new CyclicForwardingException(); Child current = newParent; synchronized(Lava.ParentUpdate) { while (current.isChild()) { current = current.$getparent$(); if (current == this) throw new CyclicForwardingException(); } ; parent = newParent; };

// forwarding methods -----------------------------------------------public $TI$ $getv1$() { return this.myParent.getv1(); } public void $setv1$($TI$ val) { this.myParent.setv1(val); } public $TI$ $getv2$() { return this.myParent.getv2(); } public void $setv2$($TI$ val) { this.myParent.setv2(val); } public T m1() { myParent.$m1$(this); } public $TI$ m1(... receiver) { myParent.$m1$(receiver); } public T m2() { myParent.$m2$(this); } public $TI$ m2(... receiver) { myParent.$m2$(receiver); } // forwarding reading of v1 (7.2.6.4)

// forwarding writing of v1 (7.2.6.4)

// forwarding reading of v2 (7.2.6.4)

// forwarding wirting of v2 (7.2.6.4)

// forwarding bridge method m1 (7.2.6.4)

// forwarding worker method m1 (7.2.6.4)

// forwarding bridge method m2 (7.2.6.4)

// forwarding worker method m2 (7.2.6.4)

Listing 7.6: Full translated code of child class for source from right hand side of Listing 7.3 on page 143.

152

CHAPTER 7. IMPLEMENTATION

interface $PCI$ extends $Child$ { public public public public public public public } $TI$ $TI$ void void T $TI$ $TI$ $getv1$() ; $getv2$() ; $setv1$($TI$) ; $setv2$($TI$) ; m1() ; m1(... receiver) ; m2(... receiver) ;

// interface for class PC (7.2.6.5) // parent access interface (7.2.6.1)

class PC implements $PCI$ {

// subtyping (7.2.6.5) // // // // // // field v1 field v2 normal get normal get normal set normal set

public $TI$ v1; protected $TI$ v2; public $TI$ $getv1$() { return v1 ; } public $TI$ $getv2$() { return v2 ; } public void $setv1$($TI$ val) { v1 = val ; } public void $setv2$($TI$ val) { v2 = val ; } public T m1() { $TI$ res = $m1$(this); if (res instanceof T) { return (T) res; } else return new $TA$(res); } public $TI$ m1(... receiver) { this.v1 = ... receiver.m2(); return this.v1 } protected T m2() { $TI$ res = $m2$(this); if (res instanceof T) { return (T) res; } else return new $TA$(res); } public $TI$ m2(... receiver) { return this.v2 }

method method method method

(7.2.3) (7.2.3) (7.2.3) (7.2.3)

// bridge method m1 (7.2.2, 7.2.6.5)

// worker method m1 (7.2.1)

// bridge method m2 (7.2.2, 7.2.6.5)

// worker method m2 (7.2.1)

Listing 7.7: Full translated code of declared parent class for source from right hand side of Listing 7.3 on page 143.

7.3. TYPING OF RECEIVER

153

preliminary. Ellipses (...) indicate the omission of code that depends on the static type of receiver. Both aspects are discussed further in the next section.

7.3

Typing of receiver

Lava supports anticipated and unanticipated delegation. The implementation techniques discussed so far are common to both cases. The last open question is the type of the receiver parameter in compiled code. The answer is straightforward for anticipated delegation. In the case of unanticipated delegation, it requires the consideration of dierent alternatives with wide-ranging implications an compiled code size and eciency.

7.3.1

Anticipated delegation

Any anticipated parent type either is a delegatee type or has one unique delegatee supertype. All anticipated parent classes rooted in the same delegatee type, T , have T as the static type of receiver. This is precisely specied by the language denition (see section 6.7) and straightforward to implement. Note that the denition of the anticipated parent type approach not only prevents unsafe values to be passed into receiver. It also enables a simple and ecient implementation: because the type of receiver is the same in an anticipated parent class and in all its subclasses, the translated code has precise knowledge about the static type of receiver in every method without requiring overloaded method denitions to account for possibly dierent types of receiver in each subclass (compare this to the worst case exponential number of overloaded method denitions of the design pattern discussed in section 3.6 on page 40).

7.3.2

Unanticipated delegation

Our implementation must also support unanticipated static delegation to any type. In this case, we cannot make any assumptions about the type of receiver in translated code: A method executing on behalf of an object p of class P must be prepared to handle values of receiver that are instances of any supertype of P (because p may be used as a parent of objects with a delegatee eld whose declared type can be any supertype of P). The only way to express this in Java is to give receiver the static type Object, the most general supertype in Java. 7.3.2.1 Impact on forwarding methods

In forwarding methods, this loss of static type information has no negative impact forwarding methods do not need to access receiver, so they do not depend on its type. On the contrary, treating receiver as being of type Object allows forwarding methods to be kept simple: they do not need to include any checks whether the current value of receiver conforms to the static type of receiver in the method to be invoked on the parent object. Such checks can be deferred to the worker method that will be called after one or several forwarding method invocations.

154 7.3.2.2 Impact on worker methods

CHAPTER 7. IMPLEMENTATION

In worker methods, not knowing the type of receiver has a wide-ranging negative impact: Every access to receiver must be preceded by a parent cast operation, in order to determine the most specic safe ancestor object of the receiver that provides the required method (see 6.10 on page 130 and 7.3.3 on page 156). If receiver is assigned to another eld, passed as parameter of a message or returned as a result, then the corresponding eld, parameter or result must also be given type Object in the translated program. Fully untyped target code. The recursive spreading of type Object throughout the translated program would make any use of available static type information impossible. Ultimately, then, every single method invocation and eld access of the source program would have to be preceded by a parent cast in the translated program. More precisely, every invocation of a method m() in class C would be preceded by a parent cast to the supertype(s) of C from which m() originates3 . For instance, the following source code
class MyList extends LinkedList { public void add(MyList arg) { // add at the end MyList l = this; while (l.next != null) { l = l.next; } l.next = arg; } // ... other methods and fields }

will be transformed to
class MyList extends LinkedList implements $MyListI$ { public void $add$(Object receiver, Object arg) { Object l = receiver; while ( (<MyList> l).$getnext$() != null) { l = (<MyList> l).$getnext$(); } (<MyList> l).$setnext$(arg); } // ... other methods and fields }
signature originates in type T if T contains the signature and there is no supertype of T that contains the signature.
3A

7.3. TYPING OF RECEIVER

155

This certainly is a possible implementation option but not a particularly attractive one because the parent casts are performed also in places where the run-time value of an expression is safe with respect to the static type of that expression in the source program. Adapters revisited. Ideally, one would like to have a cheap way to avoid useless parent casts. A simple solution could be, again, the use of adapters: if the value of receiver is unsafe upon entry to a worker method it can be wrapped into an adapter object that is safe. More precisely, a source method
T msg(T1 arg) { // body accessing "this" or "receiver" }

contained in class C can be translated to:


T $msg$(Object receiver, T1 arg) { $CI$ self; // correctly typed local var ... if (receiver instanceof $CI$) self = receiver; // ... for safe receiver or else self = new $CA$(receiver); // ... for safely wrapped receiver // translated body accessing "self" instead of "receiver" }

This schema simplies the translation of the method body, which is not cluttered with possibly useless parent casts. Instead, the adapter performs the parent casts. Because adapters are generated only when the wrapped object is used in an unsafe context, the parent casts are performed only when necessary. Moreover, all static type information is preserved: after creation and initialisation of the local self variable the unknown (Object) type of receiver will not spread further through the executing method. Applied to MyList this approach yields the following translated version of the class:
class MyList extends LinkedList implements $MyListI$ { public void $add$(Object receiver, MyList arg) {

// Creation and initialisation of "self": $MyListI$ self; if (receiver instanceof $MyListI$) self = receiver; else self = new $MyListA$(receiver); // Translated body accessing "self" instead of "receiver": MyList l = self; while ( l.$getnext$() != null) { l = l.$getnext$(); } l.$setnext$(arg); }

156

CHAPTER 7. IMPLEMENTATION

// ... other methods and fields }

Construction of adapters. The general schema for constructing adapters is still the one introduced in section 7.2.6.5 and illustrated in Figure 7.1 on page 150: Each adapter class CA is a subclass of the class C and the adapter implements (via forwarding) all methods from the interface CI associated with the wrapped class. However, the forwarding process must additionally consider the risk that the wrapped object does not provide all methods from CI. In order to handle such cases, each forwarding method performs rst a parent cast of the wrapped object to the supertype of CI that is the origin of that method signature. Then the forwarding method invokes itself on the object obtained as the result of the parent cast. The adapter class generated in this way for MyList is:
public class $MyListA$ extends MyList { private final Object o; $MyListA$(Object o) { this.o = o; } public void $add$(Object receiver, MyList arg) { (<Collection> o).add(receiver, arg) } // ... other forwarding methods }

Note that the parent cast is to the Collection interface, which is the origin of the add() method in our example (assuming the core Java APIs as of JDK 1.3).

7.3.3

Parent cast

The function of the parent cast operation is to determine the most specic ancestor of an object that provides a required type (see 6.10 on page 130). So far we have used the parent cast operation also in translated code in order to simplify the presentation. Now we proceed to show how the parent cast is implemented. The class de.uni-bonn.lava.lang.Lava contains the following class method:
public static Child parentCast(Child current, Class targetType) { try { while (!targetType.isInstance4(current) ) { current = current.$getparent$(); } }
4 The

method isInstance(Object) is part of the standard JDK class Class

7.4. EVALUATION
catch (NullPointerException e) { throw new ClassCastException(); }; return(current); }

157

The translation of a parent cast expression then simply requires calling this method and casting the result to targetType: the source expression var = <T > expr is translated to var = (T ) de.uni-bonn.lava.lang.Lava.parentCast(expr, T )

7.4

Evaluation

This section evaluates the proposals made so far, identifying weaknesses in the treatment of unanticipated delegation. The next section shows how these weaknesses can be eliminated, yielding a portable yet ecient implementation of Lava. The translation scheme proposed in this chapter consists broadly of three distinct parts The basic scheme for implementing delegation (section 7.2) together with the simple typing of receiver that is sucient in the case of anticipated delegation (section 7.3.1). The fully untyped variant for implementing unanticipated delegation (section 7.3.2). The adapter variant for implementing unanticipated delegation (section 7.3.2). which are evaluated in this section.

7.4.1

Anticipated delegation is ecient

The rst part is an elaborated version of design patterns for implementing delegation and the generated code is as ecient as any manual implementation of these patterns can be. In most cases, it is even more ecient than manual implementations that achieve the same functionality: in our scheme, all translated code calls the hidden extended format methods directly, whereas bridge methods are called only by external code in manual implementations, bridge methods will be called by any client class, because manually propagating the changed interface to all clients is prohibitive in all but the simplest applications. So manual implementations that add the ability to be delegation parents to already existing classes will perform two method invocations for every method invocation contained in client code. There is nothing to optimize here, in our scheme. Eciency gains at this level are only possible by giving up some of the functionality provided by our implementation.

158

CHAPTER 7. IMPLEMENTATION

7.4.2

Unanticipated delegation is dicult

In contrast, each of the two variants for implementing unanticipated delegation has performance problems, which are largely complementary: The rst one needs a parent cast operation prior to every method invocation or variable access, needlessly slowing down code that is not involved in delegation interactions. The second one avoids additional run-time costs for cases where the value of receiver conforms to its static type: delegation-free programs and delegation to instances of the declared parent type falls into this category. However, the remaining cases are the typical cases in which delegation will be used: typically, declared parent types will be abstract (interfaces or abstract classes) and parent objects will be instances of their concrete subtypes hence the value of receiver will, in general, not conform to the static type of receiver. These cases can be slowed down signicantly by the use of adapters. Adapters are inecient for two reasons. First, every adapter must perform the same parent cast operations like the untyped implementation variant but further adds one object access and the invocation of a forwarding method to every method of an application object invoked via the adapter. Second, adapters tend to accumulate in front of the adapted objects. This is illustrated by the following code:
// Child class A and declared ancestor classes B and C ---- : class A { protected final mandatory delegatee B parentB; A(B b) { parentB=b; } } class B { protected final mandatory delegatee C parentC; B(C c) { parentC=c; } methodFromB() { ... } } class C { methodFromC() { ... } } // Subtypes of declared ancestor classes ------------------ : class SubB extends B { methodFromB() { methodFromC(); } } class SubB extends C { methodFromC() { ... } } // A possible client -------------------------------------- : class Test { A a = new A(new SubB(new subC())); a.methodFromB(); a.methodFromC();

7.5. OPTIMIZATION OF UNANTICIPATED DELEGATION


}

159

Note that methodFromB is overridden in SubB such that it calls methodFromC, and methodFromC is overridden in SubC. Therefore, the value of a, passed to the (implicit) receiver parameter of methodFromB will be unsafe (A is not a subtype of SubB). If adapters are used, then the object a will be wrapped into an ASubB adapter in the translated version of methodFromB. This adapter will be passed further to the (implicit) receiver parameter of methodFromC, where it will also be unsafe (ASubB resp. SubB are no subtypes of SubC). Therefore an ASubC adapter will be added in front of the rst one. In general, adapted objects can still be passed into context that expect other, incompatible types. This will invariably lead to accumulation of more and more adapter objects created by our translation scheme in front of the objects created by the translated application. Net eect. So far, following Stroustrups Dont pay for what you dont use maxim [Str94] in the implementation of unanticipated delegation seems to entail paying excessively for what is used. At least, our adapter-based approach to not slowing down non-delegating code makes delegation extremely costly, in the worst case.

7.5

Optimization of unanticipated delegation

This section develops a solution that combines the best of both approaches to unanticipated delegation while limiting their disadvantages. The solution is based on reduced use of adapters and elimination of redundant parent casts.

7.5.1

Reduced use of adapters

The main optimisation idea is to create adapters only in places that would otherwise lead to dissemination of unsafe values of receiver throughout the program: unsafe values of receiver are wrapped into an adapter just prior to being passed into an expression dierent from receiver (i.e. before receiver is assigned to another variable, passed as parameter or returned as result). Methods that do not perform any of these operations are called anonymous methods in [VB99] (because they do not create aliases of receiver). An analysis of packages from the JDK reported in [VB99] shows that anonymous methods make up 83 to 94% of the code. In spite of a signicant limitation of adapter use, this approach still guarantees that all expressions dierent from receiver have type-correct values at run-time. Therefore, all messages and variable accesses on expressions dierent from receiver can be translated as usual. Explicit parent casts must only be inserted before accesses to receiver.

160

CHAPTER 7. IMPLEMENTATION

7.5.2

Lazy, non-redundant adapter creation

Wrapping of unsafe values is done lazily, just prior to the rst instruction of a method where an alias to receiver is created. If there are multiple places in a method where aliases are created, a reference to the created wrapper is stored in a local variable and reused later (by accessing this variable instead of receiver). This schema avoids the (redundant) construction of multiple adapters within one method execution.

7.5.3

Lazy, non-redundant parent casts

Because the value of receiver is xed during the execution of a method, all parent casts <T > receiver within a method will produce the same result for a given type T . Therefore, all but the rst occurrence of such a cast can be eliminated and replaced by the access to a local variable that stores the result of the rst cast. This replacement avoids redundant parent casts. Like the creation of wrappers, the parent casts are performed lazily. The only exception are parent casts within loops, which are moved before the loop. Listing 7.8 on page 160 shows an example. The types I1 to In are assumed to be the supertypes of Cn that are the origin of the methods invoked on receiver .
class Cn implements ..., In { public void m(Object receiver, args) { // Temporary variables for parent cast results: I1 selfI1; ... In selfIn; // Message this.n() with n() originating from Ii: // first parent cast to Ii selfIi = <Ii> receiver; selfIi.n(receiver, ...); // Message this.f() with f() originating from Ii: // no repeated parent cast necessary selfIi.f(receiver, ...); // Message self.g() with g() originating from Ik: // the parent cast to Ik has been moved before // the loop that contains the first call site selfIk = <Ik> receiver; while (...) { selfIk.m(receiver, ...); }

} }

Listing 7.8: Example: lazy, non-redundant parent casts of receiver

7.5. OPTIMIZATION OF UNANTICIPATED DELEGATION

161

7.5.4

Non-redundant parent casts in adapters

Parent casts are performed also in the body of forwarding methods contained in adapters. Within each forwarding method of an adapter class $W rappedT ypeA$, a parent cast is performed to the supertype of W rappedT ype that is the origin of that method signature. Because each adapter instance is used only for one wrapped object, parent casts do not need to be performed repeatedly, at every method invocation. Instead, their result can be stored in the adapter and reused as the target of later invocations. The result of a parent cast to type T is stored in a eld of type T :
private final T instanceOfT ;

A forwarding method that previously performed just a parent cast to type T now rst checks whether the variable named instanceOfT has the value null, in which case it initializes the variable by performing the parent cast. After this the respective method is invoked on the value of the instanceOfT eld:
public methodOriginatingF romT (Object receiver) { if (null == instanceOfT ) instanceOfT = <T > wrappedObject; instanceOfT .methodOriginatingF romT (receiver); }

The adapter class generated in this way for class MyList (page 154 and 156) is:
public class $MyListA$ extends MyList { // wrapped object: private final Object o; // result of parent cast of o to "Collection": private final Collection instanceOfCollection; // ... results of parent casts to other types $MyListA$(Object o) { this.o = o; } public void $add$(Object receiver, MyList arg) { if (null == instanceOfCollection) instanceOfCollection = <Collection> o; instanceOfCollection.add(receiver, arg); } // ... other forwarding methods }

Note that the if test can succeed only at its rst execution, given that the instanceOfT elds are final. A clever dynamically optimizing run-time system might therefore eliminate the test after its rst execution and perform even additional optimizations, e.g. by inlining the target of the method lookup at the call site.

162

CHAPTER 7. IMPLEMENTATION

7.5.5

Customization

The optimizations of unanticipated delegation proposed so far limit the number of created adapters and the number of parent casts that are performed during execution of delegated messages: In anonymous methods, which make up 83% to 94% of the code of JDK packages [VB99], adapters are not created at all. In non-anonymous methods, adapters are created only when necessary, i.e. when the dynamic type of receiver is not a subtype of its static type and an alias of receiver that expects its static type is created. Each method executes just as many parent casts as the number of messages to receiver that originate from dierent supertypes of the class containing the method. For each method, this number is a compile-time constant, denoted parentCastN umbermethod . Only the last item creates a possible performance penalty that will manifest itself even if delegation is not used. If i is the cost of an instanceof test then the added costs per method are i parentCastN umbermethod . However, this inconvenience can be eliminated easily by adopting the weak customization schema proposed in [Kni94]. It creates an additional version of every method, which is customized for the case that receiver and holder are the same. This is always true when a method is not invoked via delegation. The customized version can be compiled like a normal Java method: it has the same signature as the original method and needs no parent casts or adapters at all. It can be seen as a specialized variant of our bridge methods. In order to take full advantage of customization, the translation scheme is modied: The customized version of a method is invoked by other customized versions and by explicit messages5 in non-customized versions. The non-customized version of a method is invoked only within forwarding methods, in the translation of explicit delegation expressions and in the translation of messages to super and receiver within non-customized versions. This modication guarantees that the translation implies no performance penalty for non-delegated messages. However, it is questionable whether weak customization pays o, given that the dynamic compilation techniques of [HCU91, Hl94b, Hl94a, Hl94b, HU95] o o o meanwhile have found their way into modern Java run-time systems. We expect that such systems are able to eliminate most of the delegation-specic costs of non-customized translated Lava code. This trade-o remains to be evaluated.

7.6

Chapter Summary

This chapter has presented an implementation scheme for Lava that provides the following benets in terms of compatibility, portability eciency:
5 Messages

to expressions dierent from receiver (in the translated code).

7.6. CHAPTER SUMMARY

163

It extends Java with type-safe static and dynamic delegation (and consultation) while maintaining full backward compatibility with standard Java. It is fully portable across any correct implementation of the Java Virtual Machine because it generates standard Java byte code. Dynamic delegation to anticipated parent types will be at least as ecient as any hand-coded implementation of delegation (following well-known design patterns) could be. Delegation to types whose use as parent types has not been anticipated is possible (unlike in hand-coded schemes) and adds only limited run-time costs. Unanticipated delegation can be further optimized to ensure that no performance penalty is incurred on non-delegated messages (7.5.5).

164

CHAPTER 7. IMPLEMENTATION

Chapter 8

Applications
The introduction of Darwin and Lava in the previous chapters has been very technical, with few, highly stylized examples. It has described the new concepts and the reasons why they had to be designed just like that but gave almost no hints as how to use these concepts. This chapter lls this gap. It identies generic application scenarios and illustrates each one with an example. The discussion of these examples also demonstrates the practical utility of delegation and consultation compared to traditional class-based modelling and suggests guidelines for the selection of the appropriate mechanism.

8.1

Language Support for Good Design

In Chapter 3 we have argued that design patterns are no good substitute for delegation: because behaviour evolution is typically simulated by a set of cooperating classes, these classes tend to be tightly dependent on each other in ways that are not grounded in the applications semantics but just in the technical details of the pattern (see for instance the comments in the implementation section of the state and strategy pattern in [GHJV95]). Such additional dependencies and assumptions built into the design often require consistent changes in a complete hierarchy of classes even for small changes in the application logic (see 3.6.3 on page 48). Thus the application of existing design patterns can impede reuse and complicate program maintenance, achieving the opposite of what is widely regarded as a main benet of object oriented programming. Note that these are no problems of the design patterns themselves, which are indeed an excellent way to condense, communicate and reuse design knowledge, but of the underlying object model, which provides no suitable means to implement the patterns. For instance, [GHJV95, page 309] hints that the use of dynamic inheritance would be a desirable, albeit unavailable, implementation option for the state pattern. The missing language support for design patterns eectively hinders the use of good design practices because many programmers tend to prefer a poor (fragile) design that has a simple implementation over good (stable) designs with a complex implementation. The simplicity of the rst implementation is often paid for with complex redesign and refactoring during maintenance. But the complexity of many patterns is not intrinsic, it can often be seen as resulting from poor language support. 165

166

CHAPTER 8. APPLICATIONS

It has been suggested that the mere existence of design patterns is an indication of the need for language extensions. However, the granularity of language support for design patterns is subject to debate. Whereas some authors insist that every pattern needs its specic language support, others point out that this would be overwhelming and argue that the common needs of many dierent patterns should be identied and supported by generic mechanisms. We think that delegation is such a generic mechanism. As shown in Chapter 3, it underlies two generic object evolution patterns, identity-changing and identity-preserving addition, deletion and update of object structure and behaviour. They capture all possible applications of delegation. Figure 8.1 recalls the typical structure of collaboration diagrams for the generic patterns. Identity-preserving evolution (Figure 8.1a) is modeled via dynamic delegation: clients always reference the same object, and are unaware of its change of delegation parents. Identitychanging evolution (Figure 8.1b) is modeled via static delegation: at dierent times clients reference dierent delegating wrapper / decorator / adapter objects that have the same parent; in this case clients control behaviour evolution and are aware of how it takes place. Each of the generic patterns (or meta-patterns) has concrete patterns as its instances, e.g. the state pattern, the strategy pattern and objects that dynamically take on dierent roles are instances of the identity-preserving evolution pattern; the decorator pattern, the wrapper pattern and objects that can be regarded from dierent perspectives (i.e. present partly dierent interfaces and state to dierent clients) are instances of the identity-changing evolution pattern. Each of the concrete patterns has concrete or generic applications (or both). For instance, dynamic component adaptation (discussed in section 8.3 on page 169) is a generic application of delegating wrappers. These relationships are illustrated in Figure 8.2. The next sections present relevant case studies: decorators taking advantage of unanticipated static delegation (section 8.2 on the facing page),
t1

id

evolving object increment object

t1 id t2

t1, t2

time client reference internal reference

id

t2

Legend

(a) Identity-preserving object evolution

(b) Identity-changing object evolution

Figure 8.1: Collaboration diagrams for generic object evolution patterns

8.2. THE DECORATOR PATTERN


Concrete applications Generic applications Concrete patterns Generic patterns Language support decorator ... ... ... ... ... ... ... ... ... ... ...

167

dynamic component adaptation wrapper perspective role

state

strategy

identity-changing object evolution

identity-preserving object evolution delegation

Figure 8.2: Delegation supports good design practices strategies implemented using dynamic delegation to instances of anticipated parent types (section 8.4 on page 176) and dynamic component adaptation via unanticipated delegation (section 8.3 on page 169). The rst two cases are adaptations of well-known patterns from [GHJV95] whereas the last one is an example of a novel, generic application of wrappers enabled by delegation. Because this can be considered a result in its own right, it will be treated in more detail than the two standard patterns.

8.2

The Decorator Pattern

This section illustrates the use of consultation and delegation for the implementation of two variations of the decorator design pattern [GHJV95]. It compares their dierent functionality and uses this example to illustrate general guidelines for the selection of the appropriate mechanism. The two decorator classes shown in Listing 8.1 on the next page and 8.2 on page 169 may use all methods of the Decorated type as if they where locally dened or inherited from a superclass - with the essential dierence that the superclass is not statically xed but can be any subtype of Decorated. It is determined at run-time as the class of the object assigned to the decorated attribute when the decorator is constructed. So, even static delegation still enables dynamic composition. The distinction between the SimpleDecorator and the OverridingDecorator is the use of consultation versus delegation. The former lets programmers add new methods. In SimpleDecorator, local denitions of methods from Decorated will have only a local eect. The before and after code that surrounds the invocation of parent methods in their body is executed exactly once for every methodFromDecorated() message sent to the decorator. In contrast, delegation lets programmers add new methods and override existing ones. The local implementations provided in OverridingDecorator are used also in the context of the decorated parent object. Thus the before and after code is executed for the initial methodFromDecorated() message sent to

168

CHAPTER 8. APPLICATIONS
public class SimpleDecorator { // consult the decorated object for pre-existing methods: protected final mandatory consultee Decorated decorated; // create decorator for given parent: public SimpleDecorator (Decorated d) { decorated = d; } // Added methods: public void before() { ... } public void after() { ... } // Decorated methods: public void methodFromDecorated() { ... before(); decorated.methodFromDecorated(); // simple message after(); }

Listing 8.1: The decorator pattern with consultation

the decorator as well as for every self.methodFromDecorated() message sent by the decorated object. So delegation should be used when one wants to modify behaviour inherited from the parent in a way that inuences the parent. For instance Listing 8.5 on page 173 and Figure 8.3 on page 174 show the use of a delegating decorator for injecting Euro compliant behaviour into an existing banking application. Consultation is appropriate if overriding is not necessary or even undesirable. If there is clearly no need for overriding, consultation should be used since it is generally more run-time ecient. A later change of the design is as cheap and easy as one can think of: turning a simple decorator into an overriding decorator just involves changing one keyword in the decorator class. It has no impact on clients or parents but it will inuence the behaviour of children and subclasses of the decorator. So the guiding principle in choosing the appropriate forwarding mechanism must be the intended behaviour of the type, not eciency considerations. Compared to the manual implementation of the intended behaviour consultation is helpful because it avoids the need to implement forwarding methods. Addition of a keyword to a variable suces. This is a pleasant but rather moderate advantage compared to the dierence between delegation and manual implementation of the same functionality: delegation also saves a signicant amount of work but additionally supports unanticipated extension. For instance, the designer and the implementors of the Decorated type do not need to be aware of its use as a parent type and do not have to hard-code any hooks to enable this use (unlike in the simulations of delegation discussed in section 3.6 on page 40). The implementors of overriding decorator classes can (re)use the Decorated type and the classes that implement it as is, without worrying whether this use was anticipated by the designers and implementors of the parent class / type.

8.3. DCA: DYNAMIC COMPONENT ADAPTATION


public class OverridingDecorator { // delegate non-redefined methods to the decorated object: protected final mandatory delegatee Decorated decorated; // create decorator for given parent: public SimpleDecorator (Decorated d) { decorated = d; } // Added methods: public void before() { ... } public void after() { ... } // Overridden methods: public void methodFromDecorated() { ... before(); decorated<-methodFromDecorated(); // explicit delegation after(); }

169

Listing 8.2: The decorator pattern with delegation This example was just meant to give a rst impression of delegation at work. There are countless other applications of the identity-changing object evolution pattern (Fig. 8.2 on the preceding page) reported in literature they will equally benet from delegation. Section 8.3 presents a novel application of wrappers that is specically enabled by unanticipated delegation.

8.3

DCA: Dynamic Component Adaptation

Component-oriented programming aims at the replacement of monolithic applications with sets of smaller software components. Its motivation is twofold. For software engineers, assembly of applications from existing components should increase reuse, thus allowing them to concentrate on value-added tasks and to produce high-quality software within a shorter time. For users, component oriented programming promises tailor-made functionality in spite of the use of ready-made components. In this context we distinguish application tailoring from component adaptation: Tailoring is the customization of a whole component-based application by adding new components, changing the ways components are connected (re-wiring of components), or replacing some components with others that are better suited for a specic task. Adaptation is the customization of individual components by adding new components that modify the behaviour of the existing ones. Known approaches to component adaptation can be classied according to their need for hooks in the application as either suitable for anticipated or unanticipated changes,

170

CHAPTER 8. APPLICATIONS

the time when adaptation is performed as either static, load-time or dynamic (run-time) their ability to adapt whole component types or individual component instances as either global or selective. Selective approaches can be further classied as either replacing or preserving, depending on whether they replace an existing component instance by its adapted version or let both be used simultaneously. the applied techniques as either code-modication-based, wrapper-based or meta-level-based. With respect to the above classication delegation enables an approach to unanticipated, dynamic1 , selective, wrapper-based, object preserving component adaptation. It advances wrapper-based techniques by providing a new basic component interaction primitive beyond simple message sending. The dynamic adaptation problem. The need for unanticipated, dynamic adaptation has been repeatedly pointed out in literature (e.g. in [MS97]) and its practical relevance has been impressively demonstrated by the recent transition from national European currencies to the Euro. As opposed to the year two thousand problem this change of requirements could not be foreseen when the currently deployed software systems were designed. So the software departments of European banks, insurances and other companies providing 24-hours services to their customers were suddenly faced with the problem to perform unanticipated changes to their systems, without discontinuing operation. When active components can neither be directly modied nor unloaded from a running system, one is faced with the problem to change their behavior solely by adding further components and transferring part of the existing components wiring to the new components. This section shows that delegation can help to solve this apparent contradiction.

8.3.1

State of the Art

Currently, the problem of component adaptation is mainly tackled from a programmers point of view with proposals that aim at easing reuse of existing components in the development of new applications. Most proposals in this category are based on code modication ([Bos98, HOT97, ACLN98, KH97, HK98]). The paper [KAZ98] proposes programming guidelines for the construction of components, which anticipate and ease the construction of wrappers. A second line of work concentrates on the problem of adapting running applications. Proposals in this category are based on wrappers (e.g. [PBJ98]) or metalevel architectures (e.g. [MS97]). In the following we review these techniques in the light of their suitability for dynamic component adaptation.
proposed approach can also be applied statically or at load-time. However, its dynamic use is the most benecial one with respect to component adaptation
1 The

8.3. DCA: DYNAMIC COMPONENT ADAPTATION

171

Metalevel Architectures. Metalevel architectures enable extensive manipulation of a system at run-time. In principle, every object level approach can be mimicked at the metaobject level. The price for this generality is that meta level architectures still suer from two main limitations: they defeat static type systems and are notoriously inecient. One application of meta level architectures to the problem of dynamic component adaptation is described in [MS97]. It supports only anticipated adaptation because it relies on special design guidelines, which have to be obeyed in order to construct adaptable systems.

Code Modication. Code modication uses two inputs, a class to be modied and a specication of the modication. The result is a modied version of the initial code which is used instead of the old version. Examples in this category are Jan Boschs superimposition technique ([Bos98]), the class composition proposal of Harrison and Ossher ([HOT97]) and the recent work of Keller and Hlzle on binary component adaptation ([KH97, HK98]). Superimposition is a o language construct within a special object model, LayOM. LayOM programs are translated to C++ and Java, making the modied source code available for further use. Class composition as proposed by Harrison and Ossher is also applied at compile-time and requires source-code availability. Binary component adaptation can be seen as a more general form of class composition, which can adapt binary components when they are loaded. The basic technique behind binary component adaptation can be generalized to other approaches. In particular, it can be used to produce a more dynamic (load-time) version of subject oriented programming ([HO93, HOT98]), with similar component adaptation properties. Code modication is applicable at compile-time or load-time but has only limited applicability at run-time. Its essence is the replacement of an existing class by a new version of that class. This is very dicult in a running system, where instances of the class to be replaced already exist and are being used. This is the well-known yet still generally unsolved problem of schema evolution in database systems, transferred to the run-time environment of an objectoriented language. Even if the schema evolution problem could be tackled in some ad hoc way, dynamic class replacement would be too coarse grained for many applications, globally aecting all existing instances of a component, even if selective adaptation was intended.

Component instance replacement. In the extreme case when each component has only one instance, neither the schema evolution problem, nor the granularity problem would arise. Thus a component and its sole instance could be replaced by a new version. However, component instance replacement has its own shortcomings. First of all, the component to be replaced might not be prepared to hand all its relevant private data over to the new version, if adaptation was not anticipated. Secondly, the application might require joint use of the old and the new component. For instance, for the Euro transition it is required that prices in the national currency be printed in addition to prices in Euro during the rst two years.

172

CHAPTER 8. APPLICATIONS

Wrappers. When active components can neither be directly modied nor unloaded from a running system, we are faced with the problem to change their behavior solely by adding more components. This leads us to the use of wrappers ([GHJV95]). A wrapper is interposed between a component and each of its clients that should perceive some new behaviour. For each operation visible to the client the wrapper either provides an own implementation or forwards corresponding messages to the wrapped component. In the sequel a wrapper will also be called a child and the wrapped component will be called its parent.

8.3.2

Wrapper-Based Euro Transition.

The wrapper approach enables unanticipated, dynamic, selective adaptation, joint use of dierent versions of a component and easy modeling of components that present dierent interfaces to dierent clients. Traditional wrappers. However, wrappers in traditional class-based objectoriented systems fall short of achieving the desired adaptation functionality. The usefulness of the wrapper approach is limited by the underlying object model, which provides message sending as the only component interaction primitive. Message sending limits the range of component interaction and component adaptation that can be achieved [HOT97, Szy98]. This is due to the so called self-problem [Lie86] that is inherent in message passing. In [HOT97] Harrison and Ossher rephrased the self problem in component-based terminology: Robust solutions [...] require that when components of composite objects invoke operations [...], the operations need to be applied to the composite object, rather than to the component object alone. The problem is illustrated by the (stylized) traditional wrapper-based modeling of the Euro transition scenario shown in Listing 8.3 on page 172 and 8.4 on the next page. The class DM (for the German currency) represents a component that existed before the decision to adopt the Euro was taken. It provides two methods: amount() computes the current value of an investment (deposit, stock, assurance), using some private, non-shared data structures, foo() does something else but calls self.amount() in its implementation.

public class DM{ // ...

private data ...

int amount() { return ... } } void foo() { ... self.amount() ... }

Listing 8.3: The Euro scenario: The component to be adapted The class DMtoEURO represents the wrapper. It encapsulates a reference to a DM instance and also provides two methods:

8.3. DCA: DYNAMIC COMPONENT ADAPTATION


public class DMtoEURO{ // the wrapped component: DM parent; // constructor: DMtoEURO(DM p) { parent = p} // redefined method: int amount() { return parent.amount() / 1.96} // forwarding method: void foo() { parent.foo() }

173

Listing 8.4: The Euro scenario: Traditional wrapper-based adaptation fails amount() calls amount() on its wrapped DM instance and divides the result by the xed DM-to-Euro exchange rate, foo() calls foo() on its wrapped DM instance. When a foo() message is sent to the DMtoEuro wrapper, the same message is sent to its DM parent and in the course of its evaluation, the self.amount() message will be sent to the parent. Thus the adapted denition of amount() from the child will be ignored, and a wrong result (in DM instead of Euro) will be produced in the end. Delegating wrappers. Listing 8.5 shows the delegation-based variant of the DMtoEURO class. Now a foo message to a DMtoEURO instance will produce correct results because the subsequent self.amount() message sent by its DM parent (see Listing 8.3) will correctly be addressed to the DMtoEURO wrapper object thus using the adapted denition of amount().
public class DMtoEURO{ // delegate to the object referred to by parent: mandatory delegatee DM parent ; // constructor: DMtoEURO(DM p) { parent = p } // redefined method: int amount() { return parent<-amount() / 1.96 }

Listing 8.5: The Euro scenario: Delegation-based adaptation succeeds Figure 8.3 shows two dierent clients that use the old DM component and its adapted DMtoEuro version simultaneously. The stock overview component calculates in Euro, complying with the new regulations, whereas the internal accounting component continues to use DM as long as possible because calculations in DM are more precise.

174
internal accounting

CHAPTER 8. APPLICATIONS
: DM delegation stock value overview : DMtoEuro

Figure 8.3: Simultaneous use of the original DM component and of its adapted DMtoEuro version by dierent clients

5.th

6.th

Orig.

2. Increment

4. Increment

1st

3rd
Component Delegation

1. Increment

3. Increment

2nd

4th

Original Component

5th

6th

a) User view

b) System view

Figure 8.4: Additive and disjunctive composition

8.3.3

Additive and disjunctive composition

Unlike other approaches, which irrecoverably destroy the old version of a component, delegation enables two types of component modications. Additive modications are the product of a series of modications, each applied to the result of a previous one. Disjunctive modications are applied independently to the same original component. Additive modications are enabled by the recursive nature of delegation. They meet the requirement that the result of compositions / adaptations should itself be composable / adaptable ([Bos98, Szy98]). In the user view (Figure 8.4a) additive composition is depicted by stacking components one on top of the other. The system view in Figure 8.4b shows the implementation by building chains of delegating components. E.g. the rst and second increment of the original component together form an additive modication. Disjunctive extensions are enabled by the fact that each extension is encapsulated in a separate component instance that can be addressed and reused independently. Disjunctive extensions are most useful in modeling components that need to present themselves dierently to dierent clients. In the user view (Figure 8.4a), disjunctive extension are visualized sitting on top of the jointly extended component. For instance component 5 and 6 represent dierent extensions of component 4, which itself is part of a disjunctive extension branch of the original component. At the implementation level (Figure 8.4b) disjunctive extensions delegate to the same parent component.

8.3. DCA: DYNAMIC COMPONENT ADAPTATION


Incoming Incoming Orig.

175

a)

b)
1st

Orig.

2nd

3rd

Figure 8.5: Component wiring before extension (a) and after extension (b)

8.3.4

Dynamic Component Re-Wiring

As already illustrated in Figure 8.3 on the next page, the adaptation only takes eect for clients that use the wrapper instead of the wrapped component. Therefore, dynamic component adaptation requires dynamic component re-wiring, i.e. rerouting of all input connections of a component to its most specialized increments. In the JavaBeans model input connections correspond to the registration of a component as an event listener of other components. The complete schema for dynamic component adaptation, including component re-wiring, is illustrated in Figure 8.5. Part a) shows the implementation view of the original component conguration. Part b) shows the re-wired conguration after addition of three increment components, one of which represents a disjunctive modication. A component architecture that supports dynamic re-wiring must provide a run-time component directory, including information about the increment of relationship between components and the ability to ask every component to abandon all its input connections in favor of one or more other components. This must happen as an atomic operation in order to guarantee that the system is not left in an inconsistent state. These requirements are not met by current component architectures, although dynamic component re-wiring is an essential infrastructure which would benet also simple forwarding based dynamic composition techniques, not just delegation. For instance, the Extensible Runtime Containment and Services Protocol of the Glasgow model for JavaBeans provides no run-time component directory; every BeanContext functions as a directory of nested components but there is no directory of top-level BeanContexts, a JavaBean is only required to maintain a list of registered Listener objects but no list of objects to which it listens itself; thus it only knows about its outgoing connections but not about incoming connections, there is no atomic operation for handing a beans incoming connections over to another bean. Dynamic re-wiring infrastructures have already been proposed and implemented by other researchers, e.g. [PBJ98]. The approach proposed in [PBJ98] solves the atomic input transfer problem by adding a proxy to every component

176

CHAPTER 8. APPLICATIONS

and enforcing that no direct references to the component itself exist outside the proxy. The proxy can thus decide when a component is idle and may therefore be replaced by another one. This approach could, in principle, also be used to determine when a component may safely hand over its input connections to a delegating increment component. Unfortunately, it cannot be easily transferred to the JavaBeans architecture because nothing can prevent a JavaBean from handing out a reference to itself to other components, thus subverting the alias protection provided by the proxy.

8.4

The Strategy Pattern

This section illustrates the use of dynamic delegation to instances of anticipated parent types for the implementation of the strategy design pattern2 [GHJV95]. A class of text formatting objects (Formatting) that may use and dynamically switch between dierent line breaking strategies (type LineBreaking) can be written as shown in Listing 8.6 on page 177. The Formatting class may use all methods of the LineBreaking type as if they where locally dened or inherited from a superclass - with the essential dierence that it may dynamically switch to a dierent set of method implementations simply by assigning an object of a dierent LineBreaking subtype to the variable lb. Like in the case of inheritance, the Formatting class can ne tune the inherited behaviour via overriding. For instance, in the above example it is assumed that the LineBreaking type species that the line breaking algorithm calls the method getStretchability() to determine by how many pixels individual text elements can be stretched. Then providing a specialised version of this method (as shown above) might be all that is needed to adapt the inherited behaviour to the delegators needs. Note how delegation supports a simple and stable interface between the context class Formatting and the dierent strategy classes. The same method signature compose(Coord[] in) is implemented in all subclasses of LineBreaking. A complex composition scheme like the one of class TexLineBreaking can get the additional input that it needs via messages to receiver, whereas simple ones just use the input passed as parameter. Without delegation (see [GHJV95]), one would either have to extend the method signature to include parameters that would be useful just to some subclasses of LineBreaking (turning the addition of subclasses that need more input into a maintenance nightmare) or one would have to simulate delegation manually (with all the related shortcomings discussed in section 3.6). Note also that switching strategies is an act of non-monotonic object evolution: an instance3 of class Formatting that has its parent eld updated with an instance of class TexLineBreaking conceptually acquires new behaviour (the composeTex() method) and abandons it again when updated later with an instance of SimpleLineBreaking.
example is the one from [GHJV95] up to class names: The class Formatting corresponds to the class Compositor from the original example and the class LineBreaking corresponds to the class Composition. 3 More precisely, the split object represented by this instance acquires and abandons new behaviour.
2 The

8.4. THE STRATEGY PATTERN


public class Formatting { // delegate line breaking requests to the value of lb protected mandatory delegatee @LineBreaking lb; // create object with default strategy public Formatting () { lb = new SimpleLineBreaking(); } // switch strategy public setLBStrategy (@LineBreaking lb) { lb = lb; } // Overrides method from parent type LineBreaking. protected Coord[] getStretchability() { ... } ... } public abstract delegatee class LineBreaking { abstract public int compose(Coord[]); protected Coord[] getStretchability() { ...} protected Coord[] getShrinkability() { ...}

177

public class SimpleLineBreaking extends LineBreaking { // Overriden method from LineBreaking public void compose(Coord[] in) { // equidistant spacing }

public class TexLineBreaking extends LineBreaking { // Overriden method from LineBreaking public void compose(Coord[] in) { Coord[] stretch = receiver.getStretchability(); Coord[] shrink = receiver.getShrinkability(); holder.composeTex(in, stretch, shrink); } // Local auxiliary method for TeX strategy public void composeTex(Coord[] in, stretch, shrink) {... }

Listing 8.6: The strategy pattern in Lava

178

CHAPTER 8. APPLICATIONS

8.5

Chapter Summary

This chapter has presented applications of type-safe static and dynamic delegation to the implementation of well-known design patterns and to the problem of dynamic component adaptation. The examples shown demonstrate how Darwin and Lava can express dynamic update, addition and deletion of consistent chunks of state and behaviour. These object evolution operations can be anticipated or unanticipated and identity-preserving or identity-changing. Taken together they can express any conceptual object evolution required by a statically typed application: monotonic object evolution, i.e. pure specialisation of objects by property update and property addition (see 8.4), non-monotonic object evolution, i.e. specialisation plus the ability to remove again added properties (see 8.4), cumulative and disjunctive evolution, i.e. evolution operations can be applied either to the result of a previous operation, accumulating their eects, or they can be applied to the original object, resulting in distinct yet overlapping views of the same base object (see 8.3.3). Only removal of properties of an object that have not been added dynamically is prohibited. This remains the domain of dynamically typed prototypebased languages.

Part IV

Related Work and Conclusions

179

Chapter 9

Related Work
This chapter compares Darwin and Lava to other work on object evolution and delegation in the eld of design patterns, type theory and programming language design, and extra-language approaches (e.g. aspect-oriented programming and subject-oriented programming) In the following discussion, Darwin will be equated with Lava.

9.1

Design Patterns

Compared to design patterns simulating delegation (section 3.6 on page 40), Lava automates all the work that the programmer had to do previously and presents none of the limitations of simulations, except for the need to anticipate the potential use of a class as a parent. However, compared to simulated delegation, Lava limits the need for anticipation and minimizes its impact: Simulations of delegation require anticipation for dynamic and static delegation. In Lava, anticipation is required only for dynamic delegation, whereas static delegation may be fully unanticipated and requires no preparation of parent classes, whatsoever. In Lava, turning a class into an anticipated parent class requires the addition of the delegatee keyword to the class declaration and replacement of the keyword this by receiver or holder in the class and its subclasses. Such a replacement is also required in all simulation approaches. In Lava, however, this replacement is enforced by the compiler, preventing subtle semantic errors that could result from not performing the replacement consistently The more general simulation approach passing of simulated self as a parameter additionally changes method signatures, requiring extensive subsequent changes of client code. In order to avoid changing clients, programmers have to add bridge methods (see 7.2.2 on page 137) to the 181

182

CHAPTER 9. RELATED WORK modied class. So, the costs of invoking methods of the modied class are doubled because every invocation is preceeded by the invocation of a bridge method. This ineciency is avoided by the Lava compiler, which automatically propagates signature changes throughout the compiled code and generates bridge methods just for use by external programs.

We may conclude that manually coded anticipated delegation is less ecient than the use of Lava, either in terms of coding and maintenance costs or in terms of run-time costs. In addition, unanticipated delegation enables novel applications (e.g. the dynamic component adaptation approach described in section 8.3) and contributes even more to the reduction of coding and maintenance costs.

9.2

Dynamically Typed Languages

Highly dynamic and exible object models that can express object evolution are traditionally the domain of dynamically typed languages. This section shortly reviews what can be achieved and what is lost by giving up static typing. Self The primary reference in this eld is the Self system [US87, CUCH91a, CUCH91b, SU95], which can be seen as an ideal with respect to exibility. It has certainly been the the main source of inspiration for this work. Due to dynamic typing Self oers extreme exibility, including possible removal of properties (slots) at any time, which is impossible in Lava. Self also oers support for type inference [APS93, Age95] but it depends on the closed world assumption, i.e. the full program must be available to the inference process. Any change in the program requires to re-execute the type inference process. Composition lters The composition lters approach of Sina [ABV92, ABSB94, AWB+ 94] allows the method dispatch to be controlled by dynamically evaluated conditions. This includes the ability to express (untyped) dynamic inheritance. Smalltalk-80 The Smalltalk-80 system [GR83] provides the low-level become: primitive that can be used to exchange the identities of two objects. There is no system support for enforcing any kind of stuctural or behavioural consistency after such a change. Reection Using reection is another standard technique to manipulate a systems behaviour in extreme ways at run-time, if static typing and performance are no concerns. Comparison Being able to remove slots from an object, to let all references to an object suddently point to an arbitrarily dierent object or to change the operational semantics of a system at run-time are extreme forms of exibility that are outside the scope of Lava. However, we think that Darwin and Lava oer a good mix of exibility and safety, not provided by any other system. Its target users are programmers who are working today in Beta, C++, Eiel, Java or other statically typed class-based languages. For this community, which

9.3. STATICALLY TYPED LANGUAGES

183

probably has the biggest share in professional software production, the exibility of Lava might already appear extreme.

9.3

Statically Typed Languages

The past few years have seen quite active research towards more dynamic typed object models, that can be broadly classied into two main areas: typed prototype-based systems and typed mixin-languages.

9.3.1

Statically Typed Prototype-based Systems

A detailed review of statically typed prototype-based systems is contained in section 2.2.5. Here, we just summarize its main aspects and conclusions. 9.3.1.1 No Subtyping

Cecil and Omega Apparently, the rst typed prototype-based language have been Omega [Bla91, Bla94] and Cecil [Cha92, Cha93a, Cha93b, CL94]. Omega prohibits both delegation and individual changes of objects [Bla94, p. 104]. Cecil restricts delegation to be static and delegation parents to be statically known. Its concept of predicate classes [Cha93b] lets an object dynamically inherit from others, depending on its state. This can only be type-checked if programmers manually prove some disjointness and completeness properties. Even then, dynamic inheritance would be rejected if it had any inuence on object interfaces. Thus, as a price for static type safety, Omega eliminated any form of individual object evolution whereas Cecil limited it, as noted in [AC96, p. 44] to the point that there is not much dierence [... to] class-based languages. A Theory of Objects The rst type systems for a prototype-based language that included method update were developed by Abadi and Cardelli in their Theory of Objects [AC96]. They require the new property value to be statically known [AC96, p. 44]. This is equivalent to delegation to statically known objects (as in Cecil). 9.3.1.2 Mutually Exclusive Subtyping and Method Addition

In their Notes on typed object-oriented programming [FM94, p. 876] Fisher and Mitchell write: no subtyping is possible [if] method addition is a legal operation on objects [because] objects with extra methods cannot be used in some contexts where an object with fewer methods may. As an example, a colored point object cannot be used in a context that will add color, but a point object can. They conclude that method addition and update, on one hand, and subtyping, on the other hand, are mutually exclusive and present a system that gives dierent uses of objects dierent types [FM95]. Their object types allow either

184

CHAPTER 9. RELATED WORK

extension and update without subtyping or subtyping without extension and update. So programmers have to restrict how an object can be used via a certain variable. In a delegation-based environment these restrictions would correspond to require parent elds to hold no subtypes of their declared type. This is still much the same restriction as in Cecil. 9.3.1.3 Restricted Subtyping or Restricted Method Addition

An approach that allows a limited coexistence of both concepts was proposed by Bono and Liquori. In [BL95] it was suggested to jointly enable subtyping and extension but to restrict subtyping so that it forgets only methods that are not referred to by the bodies of the methods which are visible in the supertype. Thus new methods can be added without fear that they would ever be used in contexts expecting forgotten methods. A complementary limited coexistence approach was suggested by Liquori in [Liq96, Liq97]. Instead of restricting subtyping, it remembers the types of forgotten methods and restricts method addition to add only methods whose type is consistent with the forgotten ones. A very similar idea is presented in [Rm98]. It is worked out at the level of e elds instead of objects, yielding a more ne grained and uniform system. Cum grano salis these approaches correspond to a delegation-based system in which either methods overridden in a subtype of a parent type never call additional local methods, or updates of parent elds are rejected if they would assign an object whose overriding methods call local methods. Neither restriction is present in Darwin and Lava. 9.3.1.4 Subtyping and Frozen Method Lookup

A more general approach was proposed by Riecke and Stone in [RS98]. Their system combines unrestricted width subtyping and unrestricted method addition. However, this has been achieved by modifying the operational semantics in a way that makes it inconsistent with the intuitive semantics of a program. In particular, when a method is added to an object, the body of the method is manipulated so that all messages sent therein to self use the current methods of the object but none of its future extensions (static binding). The inconsistent treatment of messages to self (which are bound statically) and of other messages (which are bound dynamically) leads to semantic inconsistency: the same message, sent to the same object may produce dierent results depending on whether it has been sent via the self pseudovariable or via another expression. A detailed discussion of the freezing proposal of Riecke and Stone and of its delegation-based counterpart can be found in section 2.2.5.5 and 5.3. 9.3.1.5 Conclusions

We may thus conclude that, in conjunction with subtyping, there still is no published system that allows semantically sound, unrestricted use of object

9.3. STATICALLY TYPED LANGUAGES

185

extension, on one hand, or delegation, on the other. The reviewed approaches fall into three categories: Category A prohibits either subtyping or behaviour modication or both, Category B allows joint use of subtyping and behaviour modication (in the form of method addition and update) but signicantly restricts one of them, Category C allows unrestricted joint use of subtyping and behaviour modication (in the form of method addition and update) at the price of an operational semantics that is inconsistent with the intended semantics of a program. It is worth noting that almost all of the above proposals deal with method addition and update. In particular, there is no proposal that combines subtyping and delegation and also no proposal that could express non-monotonic behaviour evolution.

9.3.2

Statically Typed Mixins

Initial work on mixins [Pae93, BC90, BL92] was aimed to provide an alternative to multiple inheritance that developed into an alternative to inheritance per se. Agora Mixin methods supported in Agora [SCD+ 93, SdM95] are a statically typed variation of this idea, where mixins are embedded into methods of a class and can only be used to extend its instances. Thus a class must forsee every possible extension, which is quite unrealisitic in a large scale development process (as also noted in [Ern98]). MixedJava MixedJava [FKF98] supports statically typed mixins that can be used to compose classes on the y, whithout the need for a xed inheritance hierarchy. As a means to avoid accidental overriding of unrelated methods from dynamically composed mixins MixedJava independently developed the same semantic compatibility criterion that is used also in Lava (see section 4.12 and [Kni94]). Gbeta An essentially mixin-based approach is pursued in gbeta [Ern98, Ern99a, Ern99b]. It goes beyond static, class-level mixins (a la MixedJava) in that it combines static typing and dynamic object specialisation. Its object specialisation operation can be used to turn an object into an object that has at least a given set of operations and state. Still, Gbeta shares the limitations of all the typed approaches reviewed so far: it cannot model non-monotonic object evolution, i.e. scenarios in which objects can also abandon dynamically acquired properties. Its strong integration with Beta-specic language features (virtual types, block structure, and extension using inner instead of overriding of messages to self) makes a more detailed comparison of Gbeta to other approaches dicult, at least. There is, however, a noteworthy relationship to delegation: Gbetas blocks can be regarded as statically delegating objects that are connected to their parent objects (i.e. outer blocks) by the origin pointer.

186

CHAPTER 9. RELATED WORK

Mixins versus delegation. The last observation on Gbeta hints at the essential relationship of delegation in Darwin to the dierent variants of mixins: any statically typed mixin program can be translated into an equivalent program based on single, static delegation. Currently, we have a translation scheme of mixins to delegation that seemes to meet this claim, but no formal proof. A formalisation is subject of ongoing work. The main diculty is to nd a common framework for such dierent systems like MixedJava, Gbeta and Lava. Assuming that the above claim is correct and given the obvious fact that mixin approaches cannot express non-monotonic object evolution (by their very nature they just add but never remove anything from a type or an object) we conjecture that delegation properly subsumes mixins.

9.4

Extra-language Approaches

It has repeatedly been demonstrated how delegation can be used to improve separation of concerns [Par72] in large software systems. For instance, it allows to express various points of view [CS90], roles [Kni96b], conceptual states, strategies [GHJV95] or versions [NRE92] of an object in distinct program entities. As such delegation achieves similar eects to approaches like aspect oriented programming (AOP) [KLM+97], subject oriented programming (SOP) [HO93, OKK+96, CHOT99], multi-dimensional separation of concerns (MDSOC) [TOHS99], or adaptive plug and play components (APPC) [ML98]. It has even been shown in [SU96, HOT97] how subject compositon can be achieved by delegation. Apart from this similarity, however, comparing delegation to these approaches is dicult, because their main objectives as well as the ways to achieve them are fundamentally dierent. Lava is a fully integrated language model with a well dened type-system and operational semantics. Its aim is to make highly dynamic applications, including object behaviour evolution, easier to model, program and maintain, reducing the gap between conceptual design and implementation. AOP, SOP, MDSOC, APPCs and related approaches are designed as extralanguage tools whose main aim is to ease evolution of software. Expressing evolution of object behaviour at run-time is not a main concern here, although some aspects can be expressed by some of these approaches. As pointed out in [Ern98] the extra-language approach always involves a transition which must be dealt with, in type-checking and error-reporting and even in compilation and optimisation. We believe that a full language integration is a natural goal to strive for.

9.5

Chapter Summary

In the nal analysis, Darwin is the rst object model that makes static and dynamic delegation compatible with the standard notion of (width) subtyping, integrates delegation into a class-based language,

9.5. CHAPTER SUMMARY

187

allows non-monotonic evolution of object structure and behaviour to be expressed in statically typed languages. It provides immediate language support for a large variety of widely-used design patterns, making them much easier to implement and maintain. Thus it generally fosters good design and coding practices by eliminating their main inconvenience. In addition, unanticipated delegation enables unanticipated reuse (e.g. dynamic component adaptation) contributing even more to the reduction of coding and maintenance costs that characterises delegation compared to standard design patterns. Last but not least, delegation is easy to use because it is just the condensed essence of many design patterns. Anyone who is familiar with common patterns (state, strategy, decorator, wrapper, adapter, chain of responsibility, etc.) will immediately know how to use delegation.

188

CHAPTER 9. RELATED WORK

Chapter 10

Conclusions
In this thesis we have shown how statically typed class-based languages can benet from the power and exibility of object-based, dynamic inheritance (delegation), previously known only in the context of dynamically typed, prototypebased systems. We have presented a general object model, Darwin, and its implementation as an extension of Java, called Lava. Our proposal enhances traditional statically typed, class-based object models by the ability to express non-monotonic evolution of objects at run-time, i.e. the dynamic addition of consistent groups of variables and methods and possible subsequent removal of such groups of acquired properties. In chapter 3 we have stated the requirements for a dynamic object evolution mechanism (3.3) and have analysed prototype-based languages (3.4) and classbased languages (3.4.1) in the light of these requirements. We have shown how object evolution has to be hard-coded into class-based programs, essentially using various design patterns that try to mimic delegation. The review of these patterns demonstrated their limitations in terms of functionality and usability. In particular, we pointed out that the application of these patterns can impede reuse (of existing code and designs) and complicate program maintenance, achieving the opposite of what is widely regarded as a main benet of object oriented programming. Looking for a suitable candidate mechanism for an extension of class-based languages we have reviewed the two built-in mechanisms of prototype-based languages for expressing unanticipated behaviour evolution: object structure modication (3.4.1) and delegation (3.4.2). The review showed that these mechanisms signicantly dier in their support for modularity. Object-structure modication works at the level of individual properties. Due to this extreme granularity and its missing support for sharing, object structure modication is verbose, error-prone, and incompatible with modular programming and conceptual modelling. Delegation avoids these problems. It allows programmers to break down a conceptual entity into an object that represents its permanent properties and into dierent objects that represent groups of properties that can be added, removed, or mutually exchanged. Thus the problem of managing dynamic object evolution can be separated from the problem of providing chunks of consistent behaviour that may be interchanged. As a consequence of this separation of concerns, the resulting behaviour change code is trivially simple and easy to maintain. This stands in sharp contrast to the use of object 189

190

CHAPTER 10. CONCLUSIONS

property modication. For these reasons, we concluded that delegation is the preferred behaviour evolution mechanism to be integrated into a unied model. The description of the integrated model starts in chapter 4, which introduces the basic aspects of Darwin. The main focus of the chapter is the discussion of safety problems that can arise from such an extension: the risk of creating incomplete objects, which do not contain methods for all the signatures from their expected compound type and the risk of passing complete objects into contexts where they are statically unsafe, i.e. where their expected compound type is not a subtype of the expressions static type. A solution for the problem of incomplete objects has been presented, which consists of the rule that parent attributes are mandatory (rule 4.2 on page 66) and two alternative options for preventing cyclic incomplete objects (section 4.8.3 on page 69). Regarding the second problem it has been shown that for static delegation it has no negative eect. Static delegation ensures that the concrete compound type of an object will be safe in any context where a reference to the object can be passed. So, every message that is statically safe is dynamically safe. This conceptual insight has been materialized into an operational semantics for unanticipated, static single delegation that ensures type-safe execution of delegation without the need to restrict the language in any way. In particular, there is no need to limit the values of parent attributes the standard width subtyping relation can be used throughout the program. It has also been demonstrated that the dynamic composition of independent extensions of the same base type, enabled by unanticipated delegation, entails the risk that components which have not been designed to work together might interact in undesirable ways. In particular, the risk of undesired overriding of semantically unrelated methods has been discussed, and a semantic compatibility criterion based on declared subtyping has been presented. It smoothly integrates with the operational semantics of delegation, providing the guarantee that dynamic composition of split objects from instances of independently created classes will be type-safe and will not lead to accidental overriding of incompatible methods. In Chapter 5 dierent approaches to type-safe dynamic delegation with subtyping have been presented and evaluated: pure specialisation (section 5.2), use of frozen states (section 5.3), waiting for future states (section 5.4), and invariant split self (section 5.5). It has been shown that the applicability of pure specialisation is extremely restricted whereas frozen states and future states are semantically unsound. Invariant split self is the only approach that enforces type-safety of dynamic delegation with subtyping, does not exhibit undesired side-eects and is widely applicable.

191 Static delegation can be fully unanticipated, i.e. any type can be used as a declared parent type. Dynamic delegation using the split self approach must be anticipated. The parent type must declare that it may be used as a target of dynamic delegation by adding one keyword to the type declaration. This declaration enables the compiler to ensure that all instances of this type will not send unsafe messages to their potential children. On the side of the child type a suitable type annotation of the delegation attribute ensures that only objects that implement the parent type directly (rather than via delegation) can be used as parents. This still leaves room for the use of non-delegating subtypes (e.g. those derived by inheritance). This approach to dynamic delegation provides enough exibility to be able to model widely-used object evolution scenarios like those condensed in the state and strategy pattern. Static delegation to arbitrary parent types (unanticipated delegation) additionally enables novel applications of the wrapper or decorator pattern, e.g. dynamic adaptation of components (Chapter 8). Chapter 7 has shown how the ideas of Darwin can be incorporated into the design of Java, yielding our experimental language Lava. In chapter 7 an implementation scheme for Lava has been presented that extends Java with type-safe static and dynamic delegation while maintaining full backward compatibility with standard Java, is fully portable across any correct implementation of the Java Virtual Machine because it generates standard Java byte code, implements dynamic delegation to anticipated parent types at least as eciently as any hand-coded implementation of delegation (following wellknown design patterns) could do, enables delegation to types whose use as parent types has not been anticipated (unlike in hand-coded schemes). In the latter case, our implementation scheme adds only limited run-time costs to delegated messages (non-delegated messages remain unaected). The comparison of Darwin and Lava to related work (in Chapter 9) has shown that our proposal is the rst one that makes static and dynamic delegation compatible with the standard notion of (width) subtyping, integrates delegation into a class-based language, allows non-monotonic evolution of object structure and behaviour to be expressed in statically typed languages.

192

CHAPTER 10. CONCLUSIONS

Bibliography
[ABB+ 89] Giuseppe Attardi, Cinzia Bonini, Maria Rosario Boscotrecase, Tito Flagella, and Mauro Gaspari. Metalevel programming in CLOS. In Cook [Coo89], pages 243256. Ole Agesen, Lars Bak, Craig Chambers, Bay-Wei Chang, Urs Hlzle, John Maloney, Randall B Smith, David Ungar, and Mario o Wolczko. The SELF 3.0 Programmers Reference Manual. Computer Systems Laboratory, Stanford University, 1993.

[ABC+ 93]

[ABGO93] A. Albano, R. Bergamini, G. Ghelli, and R. Orsini. An object data model with roles. In Rakesh Agrawal, Sean Baker, and David Bell, editors, Very large data bases, VLDB 93: proceedings of the 19th International Conference on Very Large Data Bases, August 2427, 1993, Dublin, Ireland, pages 3951, Palo Alto, Calif., USA, 1993. Morgan Kaufmann Publishers. [ABSB94] Mehmet Aksit, Jan Bosch, William van der Sterren, and Lodewijk Bergmans. Real-time specication inheritance anomalies and realtime lters. In Tokoro and Pareschi [TP94], pages 386407. Mehmet Aksit, Lodewijk Bergmans, and Sinan Vural. An objectoriented language-database integration model: The compositionlters approach. In Madsen [Mad92], pages 372395. Mart Abadi and Luca Cardelli. A Theory of Objects. Springer, n 1996. P.S.C. Alencar, D. D. Cowan, C.J.P. Lucena, and L.C.M. Nova. A model for gluing components. In Weck et al. [WBS98], pages 101108. ISBN 952-12-0284-X. Ken Arnold and James Gosling. The Java Programming Language. Addison Wesley, 1996. Ole Agesen. Concrete Type Inference: Delivering Object-Oriented Applications. Ph.d. thesis, Stanford University, 1995. Mehmet Aksit and Satoshi Matsuoka, editors. ECOOP 97 Object-Oriented Programming 11th European Conference, Jyvskyl, Finland, volume 1241 of LNCS. Springer, 1997. a a 193

[ABV92]

[AC96]

[ACLN98]

[AG96]

[Age95]

[AM97]

194 [Ame91]

BIBLIOGRAPHY P America. A behavioural approach to subtyping in object-oriented programming languages. In Maurizio Lenzerini, Daniele Nardi, and Maria Simi, editors, Inheritance Hierarchies in Knowledge Representation and Programming Languages, pages 173190. Wiley, 1991. Ole Agesen, Jens Palsberg, and Michael I. Schwartzbach. Type inference of SELF: Analysis of objects with dynamic and multiple inheritance. In Nierstrasz [Nie93], pages 247267.

[APS93]

[AWB+ 94] Mehmet Aksit, Ken Wakita, Jan Bosch, Lodewijk Bergmans, and Akinori Yonezawa. Abstracting object interactions using composition lters. In O. Nierstrasz R. Guerraoui and M. Riveill, editors, Proceedings of the ECOOP93 Workshop on Object-Based Distributed Programming, number 791 in LNCS, pages 152184. Springer, 1994. [BC90] Gilad Bracha and William Cook. Mixin-based inheritance. Proceedings OOPSLA/ECOOP90, ACM SIGPLAN Notices, 25(10):303 311, 1990. John Boyland and Giuseppe Castagna. Type-safe compilation of covariant specialization: A practical case. In Cointe [Coi96], pages 325. Kim B. Bruce, Luca Cardelli, Giuseppe Castagna, The Hopkins Objects Group, Gary T. Leavens, and Benjamin Pierce. On binary methods. Theory and Practice of Object Systems, 1(3):217238, 1996. Elisa Bertino and Giovanna Guerrini. Objects with multiple most specic classes. In Oltho [Olt95], pages 102126. K. Bowen and R. Kowalski. Amalgamating language and metalanguage in logic programming. In Keith L. Clark and S.. Trnlund, A a editors, Logic Programming, pages 153172. Academic Press, London, 1982. Sren Brandt and Jrgen Lindskov Knudsen. Generalising the beta type system. In Cointe [Coi96], pages 421448. Daniel G. Bobrow, K. Kahn, M. Stek, and Gregor Kiczales. Common LOOPS. Technical report, Xerox PARC, 1985. Gilad Bracha and Gary Lindstrom. Modularity meets inheritance. In Proceedings of IEEE Computer Society International Conference on Computer Languages, pages 282290. IEEE Computer Society, 1992. Viviana Bono and Luigi Liquori. A subtyping for the FisherHonsell-Mitchell lambda calculus of objects. LNCS, 993:1730, 1995.

[BC96]

[BCC+ 96]

[BG95] [BK82]

[BK96] [BKSK85] [BL92]

[BL95]

BIBLIOGRAPHY [Bla91]

195

Gnther Blaschek. Type-safe object-oriented programming with u prototypes the concepts of Omega. Structured Programming, 12:217225, 1991. Gnther Blaschek. Object-Oriented Programming with Prototypes. u Springer, 1994. Kwang June Byeon and Dennis McLeod. Towards the unication of views and versions for object databases. In Shojiro Nishio and Akinori Yonezawa, editors, Object Technologies for Advanced Software, First JSSST International Symposium, volume 742 of LNCS, pages 220236. Springer, 1993. Jan Bosch. Superimposition a component adaptation technique, 1998. http://www.ide.hk-r.se/~bosch/articles.html.

[Bla94] [BM93]

[Bos98]

[BOSW98] G. Bracha, Martin Odersky, D. Stoutamire, and P. Wadler. Making the future safe for the past: Adding genericity to the Java programming language. Proceedings of OOPSLA98, ACM SIGPLAN Notices, 33, 1998. [Box98] [Bri89] Don Box. Essential COM. Addison Wesley, 1998. Jean-Pierre Briot. Actalk: A testbed for classifying and designing actor languages in the Smalltalk-80 environment. In Cook [Coo89], pages 109129. Grady Booch, James Rumbaugh, and Ivar Jacobson. Unied Method for Object-Oriented Development Version 1.0. Rational Software Corporation, 1997. Kim B. Bruce. Typing in object-oriented languages: Achieving expressibility and safety. Technical report, Williams College, 1996. Boris Bokowski and Jan Vitek. Conned Types. In IWAOOS99 [IWA99]. Online proceedings at http://cuiwww.unige.ch/~ecoopws/iwaoos/papers/, selected papers to appear in Software Practice and Experience. Luca Cardelli. A semantics of multiple inheritance. Information and Computation, 76:138164, 1988. Peter S. Canning, William Cook, Walter L. Hill, and Walter G. Oltho. Interfaces for strongly-typed object-oriented programming. Proceedings OOPSLA 89, ACM SIGPLAN Notices, 24(10):457 468, 1989. Laurence Cable and Graham Hamilton. A draft Proposal to Dene an Extensible Runtime Containment and Services Protocol for JavaBeans, August 1997. http://java.sun.com. Craig Chambers. Object-oriented multi-methods in Cecil. In Madsen [Mad92], pages 3356.

[BRJ97]

[Bru96] [BV99]

[Car88] [CCHO89]

[CH97]

[Cha92]

196 [Cha93a]

BIBLIOGRAPHY Craig Chambers. The Cecil language: Specication and rationale. Technical report tr 930305, Department of Computer Science and Engineering, University of Washington, March 1993. Craig Chambers. Predicate classes. In Nierstrasz [Nie93], pages 268296.

[Cha93b]

[CHOT99] Siobhan Clarke, William Harrison, Harold Ossher, and Peri Tarr. Subject-oriented design: Towards improved alignment of requirements, design and code. In Proceedings OOPSLA 99, pages 325 339, 1999. [Civ93] Franco Civello. Roles for composite objects in object-oriented analysis and design. Proceedings OOPSLA 93, ACM SIGPLAN Notices, 28(10):376393, 1993. Craig Chambers and Gary T Leavens. Type-checking and modules for multi-methods. In Proceedings OOPSLA 94 , ACM SIGPLAN Notices, volume 29. ACM Press, 1994. Pierre Cointe. Metaclasses are rst class: the ObjVlisp model. Proceedings OOPSLA 87, ACM SIGPLAN Notices, 22(12):156 167, 1987. Pierre Cointe, editor. ECOOP 96 Object-Oriented Programming 10th European Conference, Linz, Austria, volume 1098 of LNCS. Springer, 1996. S. Cook, editor. Proceedings ECOOP89, Nottingham, 1989. Cambridge University Press. James O. Coplien. Advanced C++: Programming Styles and Idioms. Addison-Wesley, 1992. Pascal Costanza. Lava: Delegation in einer streng typisierten Programmiersprache Sprachdesign und Compiler. Diploma thesis, CS Dept. III, University of Bonn, Germany, January 1998. http://javalab.cs.uni-bonn.de/research/darwin/. Michael Caruso and Edward Sciore. The VISION object-oriented database management system. In Franois Bancilhon and Peter c Buneman, editors, Advances in Database Programming Languages, Frontier Series, pages 147164. ACM Press, 1990. James O. Coplien and Douglas Schmidt. Pattern Languages of Program Design. Addison-Wesley, 1995.

[CL94]

[Coi87]

[Coi96]

[Coo89] [Cop92] [Cos98]

[CS90]

[CS95]

[CUCH91a] Craig Chambers, David Ungar, Bay-Wei Chang, and Urs Hlzle. o Organizing programs without classes. In Lisp and Symbolic Computation, volume 4, pages 3756. Kluwer Academic Publishers, 1991. [CUCH91b] Craig Chambers, David Ungar, Bay-Wei Chang, and Urs Hlzle. o Parents are shared parts of objects: Inheritance and encapsulation in SELF. In Lisp and Symbolic Computation: An International Journal, volume 4, pages 2136. Kluwer Academic Publishers, 1991.

BIBLIOGRAPHY [CW85]

197

Luca Cardelli and Peter Wegner. On understanding types, data abstraction, and polymorphism. ACM Computing Surveys, 17(4):471522, 1985. Christophe Dony, Jacques Malenfant, and Pierre Cointe. Prototype-based languages: From a new taxonomy to constructive proposals and their validation. Proceedings OOPSLA 92, ACM SIGPLAN Notices, 27(10):201217, 1992. Scott Danforth and Chris Tomlinson. Type theories and objectoriented programming. ACM Computing Surveys, 20(1):2972, 1988. Erik Ernst. Dynamic inheritance and static analysis can be reconciled. pages 5778, 1998. Eric Ernst. Dynamic inheritance in a statically typed language. Nordic Journal of Computing, 6(1):7292, 1999. Erik Ernst. Propogating class and method composition. In Rachid Guerraoui, editor, ECOOP 99 Object-Oriented Programming 13th European Conference, Lisbon, Portugal, volume 1628 of LNCS, pages 6791. Springer, 1999. D.H Fishman, J Annevelink, E.C Chow, T Connors, J. W Davis, W Hasan, C.G Hoch, W Kent, S Leichner, P Lyngbaek, B Mahbod, M.A Neimat, T Risch, M.C Shan, and W.K Wilkinson. Overview of the Iris DBMS. In Won Kim and Frederick H. Lochovsky, editors, Object-Oriented Concepts, Databases and Applications, pages 219250. ACM Press and Addison-Wesley, Reading, Massachussets, 1989. D.H. Fishman, David Beech, H.P. Cate, E.C. Chow, T. Connors, J.W. Davis, Nigel Derrett, C.G. Hoch, William Kent, P. Lyngbaek, B. Mahbod, M.A. Neimat, T.A. Ryan, and M.C. Shan. Iris: An object-oriented database management system. ACM Transactions on Oce Information Systems, 5(1):4869, 1987. Donald G. Firesmith and Edward M. Eykholt. Dictionary of Object Technology. SIGS Reference Library Series. SIGS Books, Inc., New York, 1995. Matthew Flatt, Shriram Krishnamurthi, and Matthias Felleisen. Classes and mixins. The 25th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, January 1921, 1998 San Diego, California, USA, Proceedings, 1998. Kathleen Fisher and Jonn C. Mitchell. Notes on typed objectoriented programming. In Proceedings Theoretical Aspects of Computer Software (TACS 94), volume 789 of LNCS, pages 844885. Springer, 1994.

[DMC92]

[DT88]

[Ern98]

[Ern99a]

[Ern99b]

[FAC+ 89]

[FBC+ 87]

[FE95]

[FKF98]

[FM94]

198 [FM95]

BIBLIOGRAPHY Kathleen Fisher and John C. Mitchell. A delegation-based object calculus with subtyping. In Proceedings of 10th International Conference on Fundamentals of Computation Theory (FCT 95), volume 965 of LNCS, pages 4261. Springer, 1995. Martin Fowler. Analysis Patterns, Reusable Object Models. Object Technology Series. Addison-Wesley, 1997. Martin Fowler. Refactoring, Improving the Design of Existing Code. Object Technology Series. Addison-Wesley, 1999. Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns Elements of Reusable Object-Oriented Software. Addison Wesley, Reading, MA, 1995. James Gosling, Bill Joy, and Guy Steele. The Java Language Specication. Addison Wesley, 1996. Andreas Gawecki and Florian Matthes. Integrating subtyping, matching and type quantication: A practical perspective. In Cointe [Coi96], pages 2647. G Ghelli and R Orsini. Types and subtypes as partial equivalence relations. In Maurizio Lenzerini, Daniele Nardi, and Maria Simi, editors, Inheritance Hierarchies in Knowledge Representation and Programming Languages, pages 191210. Wiley, 1991. Adele Goldberg and David Robson. Smalltalk 80: the Language and its Implementation. Addison-Wesley, Reading, Mass., 1983. Adele Goldberg and David Robson. Smalltalk-80: The Language. Addison-Wessley, Reading, MA, 1989. G. Gottlob, M. Schre, and B. Rck. Extending object-oriented o systems with roles. In ACM Transactions on Information Systems, volume 14, pages 268196. ACM press, 1996. Franz J. Hauck. Class-based inheritance is not a basic concept. Technical Report TR-146-93, University of Erlangen-Nrnberg, u Computer Science Department, IMMD IV, July 1993. Franz J. Hauck. Inheritance modeled with explicit bindings: An approach to typed inheritance. Proceedings OOPSLA 93, ACM SIGPLAN Notices, 28(10):231239, 1993. Franz J. Hauck. Typisierte Vererbung modelliert durch Aggregation. Technical report, Dep. of CS, University of ErlangenNrnberg, Germany, 1993. u Urs Hlzle, Craig Chambers, and David Ungar. o Optimizing dynamically-typed object-oriented languages with polymorphic inline caches. In Pierre America, editor, ECOOP91 Object-Oriented Programming 5th European Conference, Geneva, Switzerland, volume 512 of LNCS, pages 2138. Springer, 1991.

[Fow97] [Fow99] [GHJV95]

[GJS96] [GM96]

[GO91]

[GR83] [GR89] [GSR96]

[Hau93a]

[Hau93b]

[Hau93c]

[HCU91]

BIBLIOGRAPHY [HK98]

199

Urs Hlzle and Ralf Keller. Binary Component Adaptation. In Eric o Yul, editor, ECOOP 98 Object-Oriented Programming 12th European Conference, Brussels, Belgium, volume 1445 of LNCS, pages 307329. Springer, 1998. J. Hogg, D. Lea, A. Wills, D. de Champeaux, and Richard Holt. Report on ECOOP91 workshop W3: The Geneva convention on the treatment of object aliasing. OOPS Messenger, 3(2):1116, 1992. William Harrison and Harold Ossher. Subject-oriented programming (a critique of pure objects). Proceedings OOPSLA 93, ACM SIGPLAN Notices, 28(10):411428, 1993. John Hogg. Islands: Aliasing protection in object-oriented languages. Proceedings of OOPSLA 91, ACM SIGPLAN Notices, 26(11):271285, 1991. Urs Hlzle. Adaptive Optimization for SELF: Reconciling High o Performance With Exploratory Programming. Phd thesis, Stanford University, 1994. Urs Hlzle. A third generation SELF implementation: Reconciling o responsiveness with performance. OOPSLA 94 Proceeding, ACM SIGPLAN Notices, 29(10):229243, 1994. William Harrison, Harold Ossher, and Peter Tarr. Using delegation for software and subject composition. Research Report RC 20946 (922722), IBM Research Division, T.J. Watson Research Center, August 1997. William Harrison, Harold Ossher, and Peter Tarr. Load-time subject-oriented programming, June 1998. Personal communication. Urs Hlzle and David Ungar. Do object-oriented languages need o special hardware support? In Oltho [Olt95], pages 283302. D. Heiler and S. Zdonik. Object views: Extending the vision. In Proceedings of the Sixth International Conference on Data Engineering, pages 8693. IEEE Computer Society Press, Los Alamitos, CA, 1990. Intercontinental Workshop on Aliasing in Object-Oriented Systems, part of the ECOOP99, 1999. Online proceedings at http://cuiwww.unige.ch/~ecoopws/iwaoos/papers/, selected papers to appear in Software Practice and Experience. JavaSoft. The Glasgow model, 1997. http://www.javasoft.com. Ivar Jacobson, Grady Booch, and James Rumbaugh. Unied Software Development Process. Object Technology Series. AddisonWesley, 1999.

[HLW+92]

[HO93]

[Hog91]

[Hl94a] o

[Hl94b] o

[HOT97]

[HOT98]

[HU95] [HZ90]

[IWA99]

[Jav97] [JBR99]

200 [JCJO92]

BIBLIOGRAPHY Ivar Jacobson, Magnus Christerson, Patrik Jonsson, and Gunnar Overgaard. Object-Oriented Software Engineering A Use Case Driven Approach. Addison-Wesley/ACM Press, Reading, Mass., 1992. S. N. Kamin. Inheritance in Smalltalk-80: A denotational denition. In Proceedings of the 15th Annual ACM Symposium on Principles of Programming Languages, pages 8087, 1988. Randy H Katz. Towards a unied framework for version modeling in engineering databases. ACM Computing Surveys, 22(4):375395, 1990. Blent Kcuk, M. Nedim Alpdemir, and Richard N. Zobel. Cusu u tomizable adapters for black-box components. In Weck et al. [WBS98], pages 5360. ISBN 952-12-0284-X. Setrag N. Khoshaan and George P. Copeland. Object identity. Proceedings OOPSLA 86, ACM SIGPLAN Notices, 21(11):406 416, 1986. Ralph Keller and Urs Hlzle. Supporting the Integration and Evoo lution of Components Through Binary Component Adaptation. Technical Report TRCS97-15, University of California at Santa Barbara, September 1997.

[Kam88]

[Kat90]

[KAZ98]

[KC86]

[KH97]

[KLM+97] Gregor Kiczales, John Lamping, Anurag Mendhekar, Chris Maeda, Cristina Lopes, Jean-Marc Loingtier, and John Irwin. Aspectoriented programming. In Aksit and Matsuoka [AM97], pages 220 242. [KLRSR95] Gerti Kappel, P Lang, S Rausch-Schott, and W Retschitzegger. Workow management based on objects, rules, and roles. Data Engineering, 1(1):1118, 1995. [Kni94] Gnter Kniesel. Implementation of dynamic delegation in strongly u typed inheritance-based systems. Technical Report IAI-TR-94-3, CS Dept. III, University of Bonn, Germany, October 1994. Gnter Kniesel. Encapsulation = Visibility + Accessibility. Techniu cal report IAI-TR-96-12, ISSN 0944-8535, CS Dept. III, University of Bonn, Germany, December 1996. Gnter Kniesel. Objects Dont migrate! Perspectives on Obu jects with Roles. Technical report IAI-TR-96-11, ISSN 09448535, CS Dept. III, University of Bonn, Germany, November 1996. http://javalab.cs.uni-bonn.de/research/darwin/. Gnter Kniesel. u Delegation for Java API or Language Extension? Technical report IAI-TR-98-4, ISSN 09448535, CS Dept. III, University of Bonn, Germany, May 1998. http://javalab.cs.uni-bonn.de/research/darwin/.

[Kni96a]

[Kni96b]

[Kni98]

BIBLIOGRAPHY [KR94]

201

S. N. Kamin and U. S. Reddy. Two semantic models of objectoriented languages. In C. A. Gunter and J. C. Mitchell, editor, Theoretical Aspects of Object-Oriented Programming, pages 463 495. MIT Press, 1994. Gregor Kiczales, Jim des Rivi`res, and Daniel G. Bobrow. The Art e of the Metaobject Protocol. MIT Press, Cambridge, MA, 1991. Gnter Kniesel, Mechthild Rohen, and A.B. Cremers. A Manu agement System for Distributed Knowledge Base Applications. In W. Brauer and D. Hernndez, editors, Verteilte Knstliche Intellia u genz und Kooperatives Arbeiten (Distributed Articial Intelligence and Cooperative Work), pages 6576. Springer-Verlag, 1991. Gnter Kniesel and Dirk Theisen. u Flexible Aliasing with Protection. In IWAOOS99 [IWA99]. Online proceedings at http://cuiwww.unige.ch/~ecoopws/iwaoos/papers/, selected papers to appear in Software Practice and Experience. Gnter Kniesel and Dirk Theisen. JAC Acess right based enu capsulation for Java. Software Practice and Experience, 2000. To appear. W.R LaLonde. Why examplars are better than classes. Technical report scs-tr-93, School of Computer Science, Carleton University, May 1986. Henry Lieberman. Using prototypical objects to implement shared behavior in object oriented systems. Proceedings OOPSLA 86, ACM SIGPLAN Notices, 21(11):214223, 1986. Luigi Liquori. Bounded polymorphism for extensible objects. Technical report CS-2496, Dipartimento di Informatica, Universit` di a Torino, Italy, 1996. Luigi Liquori. An extended theory of primitive objects: First order system. In Aksit and Matsuoka [AM97], pages 146169. H Lieberman, L.A Stein, and D Ungar. Of Types and Prototypes: The Treaty of Orlando. ACM SIGPLAN Notices, pages 4344, 1988. Wilf R. LaLonde, Dave A. Thomas, and John R. Pugh. An exemplar based Smalltalk. Proceedings OOPSLA 86, ACM SIGPLAN Notices, 21(11):322330, 1986. Tim Lindholm and Frank Yellin. The Java Virtual Machine Specication. Addison-Wesley, 1997. Ole Lehrmann Madsen, editor. ECOOP92 Object-Oriented Programming 6th European Conference, Utrecht, The Netherlands, volume 615 of LNCS. Springer, 1992.

[KRB91] [KRC91]

[KT99]

[KT00]

[LaL86]

[Lie86]

[Liq96]

[Liq97] [LSU88]

[LTP86]

[LY97] [Mad92]

202 [Mae87]

BIBLIOGRAPHY Pattie Maes. Concepts and experiments in computational reection. Proceedings OOPSLA 87, ACM SIGPLAN Notices, 22(12):147155, 1987. Jacques Malenfant. Conception et implantation dun langage de programmation integrant trois paradigmes: la programmation logique, la programmation par objets et la programmation rpartie. e Phd thesis (in french), Facult des arts et des sciences, Universit e e de Montral, C.P. 6128, Succursale A, Montral, P.Q., H3C 3J7, e e Canada, 1990. Jacques Malenfant. Class versioning in a reexive object-oriented model: Semantic issues and implementation. Research report RXFLITP 9102, Institut Blaise Pascal, 4, Place Jussieu, F-75252 Paris Cedex 05, Jan 1991. Philippe Mulet and Pierre Cointe. Denition of a reective kernel for a prototype-based language. In Shojiro Nishio and Akinori Yonezawa, editors, Object Technologies for Advanced Software, First JSSST International Symposium, volume 742 of LNCS, pages 128144. Springer, 1993. Jr. Mercado, Antonio. Hybrid: Implementing classes with prototypes. Masters thesis, technical report CS-88-M19, Brown University, Department of Computer Science,, July 1988. Bertrand Meyer. Object-oriented Software Construction. Prentice Hall, 1988. Bertrand Meyer. Eiel: The Language. Prentice Hall, 1992. Scott Meyers. Eective C++. Addison-Wesley, 1992. Scott Meyers. More Eective C++. Addison-Wesley, 1996. Sun Microsystems. Java HotSpot Technology, http://java.sun.com/products/hotspot/. 1999.

[Mal90]

[Mal91]

[MC93]

[Mer88]

[Mey88] [Mey92a] [Mey92b] [Mey96] [Mic99a] [Mic99b] [ML98]

Sun Microsystems. Method Dispatch Dierences, 1999. http://java.sun.com/products/hotspot/1.0/dispatch.html. Mira Mezini and Karl Lieberherr. Adaptive plug-and-play components for evolutionary software development. ACM SIGPLAN Notices, 33(10):97116, October 1998. Simon R. Monk and Ian Sommerville. A model for versioning of classes in object-oriented databases. In P.M.D. Gray and R.J. Lucas, editors, 10th British National Conference on Databases, pages 4258. Springer, Aberdeen, 1992. Kai-Uwe Mtzel and Peter Schnorf. Dynamic component adapa tation. Technical report 976-1, Union Bank of Swizerland, June 1997.

[MS92]

[MS97]

BIBLIOGRAPHY [Nie93]

203

Oscar Nierstrasz, editor. ECOOP93 Object-Oriented Programming 7th European Conference, Kaiserslautern, Germany, volume 707 of LNCS. Springer, 1993. Oscar Nierstrasz. Regular types for active objects. In O. Nierstrasz and D. Tsichritzis, editors, Object-Oriented Software Composition, pages 99121. Prentice Hall, 1995. G.T. Nguyen, D. Rieu, and J. Escamilla. An object model for engineering design. In Madsen [Mad92], pages 233251. Hideaki Okamura and Yutaka Ishikawa. Object location control using meta-level programming. In Tokoro and Pareschi [TP94], pages 299319. Harold Ossher, Matthew Kaplan, Alexander Katz, William Harrison, and Vincent Kruskal. Specifying subject-oriented composition. Theory and Practice of Object Systems, 2(3):179292, 1996. Walter Oltho, editor. ECOOP95 Object-Oriented Programming 9th European Conference, arhus, Denmark, volume 952 A of LNCS. Springer, 1995. Andreas Paepcke. PCLOS: Stress testing CLOS experiencing the metaobject protocol. Proceedings OOPSLA/ECOOP90, ACM SIGPLAN Notices, 25(10):194211, 1990. A Paepcke. Object-Oriented Programming: the CLOS Perspective. MIT press, Cambridge, MA, 1993. M.P Papazoglou. Roles: A methodology for representing multifaceted objects. In Proceedings of the International Conference on Database and Expert Systems Applications, pages 712. Springer, 1991. D. L. Parnas. On the criteria to be used in decomposing systems into modules. Communications of the ACM, 15(12):10531058, 1972. F. Plasil, D. Balek, and R. Janecek. SOFA/DCUP: Architecture for Component Trading and Dynamic Updating. In ICCDS 98, Annapolis, Maryland, USA, 1998. IEEE CS Press. Barbara Pernici. Objects with roles. Technical report, Centre Universitaire dInformatique, University of Geneva, July 1989. Barbara Pernici. Class design and meta-design object management. Technical report, Centre Universitaire dInformatique, University of Geneva, July 1990. Barbara Pernici. Objects with roles. In Proceedings ACM-IEEE Conference of Oce Information Systems (COIS). ACM Press, Boston, Mass., 1990.

[Nie95]

[NRE92] [OI94]

[OKK+96]

[Olt95]

[Pae90]

[Pae93] [Pap91]

[Par72]

[PBJ98]

[Per89] [Per90a]

[Per90b]

204 [PS92] [PS94] [PT94]

BIBLIOGRAPHY Jens Palsberg and Michael I. Schwartzbach. Three discussions on object-oriented typing. ACM OOPS Messenger, 3(2):3138, 1992. Jens Palsberg and Michael I. Schwartzbach. Object-Oriented Type Systems. John Wiley, 1994. Benjamin C. Pierce and David N. Turner. Simple type-theoretic foundations for object-oriented programming. Journal of Functional Programming, 4(2):207247, 1994. Frank Puntigam. Types for active objects based on trace semantics. In Elie Najm et al., editor, Proceedings of the Workshop on Formal Methods for Open Object-based Distributed Systems (FMOODS96), Paris, France, March 1996. IFIP WG 6.1, Chapman & Hall. James Rumbaugh, Michael Blaha, William Premerlani, Frederick Eddy, and William Lorensen. Object-Oriented Modeling and Design. Prentice Hall, New Jersey, 1991. U. S. Reddy. Objects as closures: Abstract semantics of objectoriented languages. In Proceedings of the 1988 ACM Conference on Lisp and Functional Programming, pages 289297, 1988. Didier Rmy. From classes to objects via subtyping. In Chris Hane kin, editor, Programming Languages and Systems ESOP98, 7th European Symposium on Programming, Held as Part of the European Joint Conferences on the Theory and Practice of Software, ETAPS98, Lisbon, Portugal, March 28 April 4, 1998, Proceedings, volume 1381 of LNCS, pages 200220. Springer, 1998. J Richardson and P Schwarz. Aspects: Extending objects to support multiple, independent roles. In J. Cliord and R. King, editors, Proceedings of the ACM SIGMOD International Conference on Management of Data, pages 298307. ACM, 1991. J. G. Riecke and C. Stone. Privacy via subsumption. Electronic Proceedings of FOOL 98 Fifth International Workshop on Foundations of Object-Oriented Languages Held in conjunction with POPL 98, 1998. Patrick Steyaert, Wim Codenie, Theo DHondt, Koen De Hondt, Carine Lucas, and Marc Van Limberghen. Nested mixin-methods in agora. In Nierstrasz [Nie93], pages 197219. Matthias Schickel. Lava: Konzeptionierung und Implementierung von Delegationsmechanismen in der Java Laufzeitumgebung. Diploma thesis, CS Dept. III, University of Bonn, Germany, December 1997. http://javalab.cs.uni-bonn.de/research/darwin/. Manfred Schneider. CETUS links on object-orientation: Objectoriented patterns, January 1998. http://www.cetus-links.org/.

[Pun96]

[RBP+ 91]

[Red88]

[Rm98] e

[RS91]

[RS98]

[SCD+ 93]

[Sch97]

[Sch98]

BIBLIOGRAPHY [Sci89] [SdM95]

205

Edward Sciore. Object specialization. ACM Transactions on Information Systems, 7(2):103122, April 1989. Patrick Steyaert and Wolfgang de Meuter. A marriage of class- and object-based inheritance without unwanted children. In Oltho [Olt95], pages 127144. Sergiu S. Simmel and Ivan Godard. The Kala Basket: A Semantic Primitive Unifying Object Transactions, Access Control, Versions, and Congurations. Proceedings OOPSLA 91, ACM SIGPLAN Notices, 26(11):230246, 1991. David L Shang. Covariant deep subtyping reconsidered. ACM SIGPLAN Notices, 30(5):2128, 1995. Andre Sielski. Vergleichende Implementierung eines Rollenkonzeptes in Verschiedenen Zielsprachen. Diploma thesis, CS Dept. III, University of Bonn, Germany, December 1998. http://javalab.cs.uni-bonn.de/research/darwin/. Randall B. Smith, Mark Lentczner, Walter R. Smith, Antero Taivalssari, and David Unvar. Prototype-based languages: Object lessons from class-free programming panel summary. ACM SIGPLAN Notices, Proceedings OOPSLA 94, 29(10):102112, 1994. Lynn Andrea Stein, Henry Lieberman, and David Ungar. A shared view of sharing: The treaty of orlando. In Won Kim and Frederick H. Lochovsky, editors, Object-Oriented Concepts, Databases and Applications, pages 3148. ACM Press and Addison-Wesley, Reading, Massachussets, 1989. Walter R. Smith. Using a prototype-based language for user interface: The Newton projects experience. ACM SIGPLAN Notices, Proceedings OOPSLA 95, 30(10):6172, 1995. Alan Snyder. Encapsulation and inheritance in object-oriented programming languages. Proceedings OOPSLA 86, ACM SIGPLAN Notices, 21(11):3845, 1986. Lynn Andrea Stein. Delegation is inheritance. Proceedings OOPSLA 87, ACM SIGPLAN Notices, 22(12):138146, 1987. Lynn Andrea Stein. A unied methodology for object-oriented programming. In Maurizio Lenzerini, Daniele Nardi, and Maria Simi, editors, Inheritance Hierarchies in Knowledge Representation and Programming Languages, pages 211222. Wiley, 1991. Bjarne Stroustrup. Multiple inheritance for c++. In Proceedings of European Unix Users Group Conference, Helsinki, May 1987. 1987. Bjarne Stroustrup. The Design and Evolution of C++. AddisonWesley, 1994.

[SG91]

[Sha95] [Sie98]

[SLS+ 94]

[SLU89]

[Smi95]

[Sny86]

[Ste87] [Ste91]

[Str87]

[Str94]

206 [SU95] [SU96]

BIBLIOGRAPHY Randall B. Smith and David Ungar. Programming as an experience: The inspiration for self. In Oltho [Olt95], pages 303330. Randall B. Smith and David Ungar. A simple and unifying approach to subjective objects. Theory and Practice of Object Sytems, 2(3):161178, 1996. Clemens Szyperski. Independently extensible systems software engineering potential and challenges. In Proceedings of the 19th Australasian Computer Science Conference, Melbourne, Australia. Computer Science Association, 1996. Clemens Szyperski. Component Software Beyond Object-Oriented Programming. Addison-Wesley, 1998. Antero Taivalsaari. Delegation versus concatenation or cloning is inheritance too. In OOPS Messenger, volume 6, pages 2049. ACM Press, 1995. Antero Taivalsaari. On the notion of inheritance. ACM Computing Surveys, 28(3):438479, 1996. Dirk Theisen. Enhancing Encapsulation in OOP A Practical Approach. Diploma thesis, CS Dept. III, University of Bonn, Germany, July 1999. http://javalab.cs.uni-bonn.de/research/darwin/. G Talens, C Oussalah, and M.F Colinas. Versions of simple and composite objects. In Proceedings of the 19th VLDB Conference, Dublin, Ireland, pages 6272. Morgan Kaufmann Publishers, Palo Alto, 1993. P. Tarr, H. Ossher, W. Harrison, and S. M. Sutton. N degrees of separation: Multi-dimensional separation of concerns. In Proc. International Conference on Software Engineering (ICSE 99) (To appear), 1999. Mario Tokoro and Remo Pareschi, editors. ECOOP94 ObjectOriented Programming 8th European Conference, Bologna, Italy, volume 821 of LNCS. Springer, 1994. OMG Unied Modeling Language Specication. http://www.omg.org/, 1999. OMG Inc.,

[Szy96]

[Szy98] [Tai95]

[Tai96] [The99]

[TOC93]

[TOHS99]

[TP94]

[UML99] [US87]

David Ungar and Randall B. Smith. SELF: The power of simplicity. Proceedings OOPSLA 87, ACM SIGPLAN Notices, 22(12):227 242, 1987. Jan Vitek and Boris Bokowski. Conned types. ACM Sigplan Notices, 34(10):8296, November 15 1999. Amndio Vaz Velho and Rogrio Carapua. a e c From entityrelationship models to role-attribute models. In B. Thalheim

[VB99] [VC93]

BIBLIOGRAPHY

207

R.A. Elmasri, V. Kouramajian, editor, Entity-Relationship Approach ER 93, 12th International Conference on the EntityRelationship Approach, Arlington, Texas, December 1993, volume 823 of LNCS, pages 257270. Springer, 1993. [VCK95] John Vlissides, James O. Coplien, and Norman L. Kerth. Patterns Languages of Program Design 2. Addison Wesley, Reading, MA, 1995. Woolf and Bobby. Null object. In R. Martin, F. Buschmann, and D. Riehle, editors, Pattern Languages of Program Design 3. Addison-Wesley, Reading, Mass, 1998. Wolgang Weck, Jan Bosch, and Clemens Szyperski, editors. Proceedings of Third International Workshop on Component-Oriented Programming (WCOP 98), volume 10 of TUCS General Publications, Turku, Finland, 1998. Turku Center for Computer Science. ISBN 952-12-0284-X. Roel Wieringa and Wiebren de Jonge. The identication of objects and roles. Technical report, Faculty of Mathematics and Computer Science, Vrije Universiteit, De Boelelaan 1081a, 1081 HV, Amsterdam, Netherlands, 1991. Roel Wieringa and Wiebren de Jonge. Object identiers, keys, and surrogates: Object identiers revisited. Theory and Practice of Object Systems, 1(2):101114, 1995. Roel Wieringa, Wiebren de Jonge, and Paul Spruit. Roles and dynamic subclasses: A modal logic approach. In Tokoro and Pareschi [TP94], pages 3259. Kenishi Yamazaki, Yoshiji Amagai, Masaharu Yoshida, and Ikuo Takeuchi. TAO: an object orientation kernel. In Shojiro Nishio and Akinori Yonezawa, editors, Object Technologies for Advanced Software, First JSSST International Symposium, volume 742 of LNCS, pages 6176. Springer, 1993. Yasuhiko Yokote. Kernel structuring for object-oriented operating systems: The apertos approach,. In Shojiro Nishio and Akinori Yonezawa, editors, Object Technologies for Advanced Software, First JSSST International Symposium, volume 742 of LNCS, pages 145162. Springer, 1993. Stanley B. Zdonik. Version management in an object-oriented database. In R. Conradi, T.M. Didriksen, and D.H. Wanvik, editors, Advanced Programming Environments, Proc of IFIP WG2.4 International Workshop,Trondheim, Norway, number 244 in LNCS, pages 405422. Springer, 1986.

[WB98]

[WBS98]

[WdJ91]

[WdJ95]

[WJS94]

[YAYT93]

[Yok93]

[Zdo86]

Das könnte Ihnen auch gefallen