23

Click here to load reader

On the relationship between classes, objects, and data abstraction

  • Upload
    john-c

  • View
    217

  • Download
    1

Embed Size (px)

Citation preview

Page 1: On the relationship between classes, objects, and data abstraction

On the Relationship Between Classes, Objects,and Data Abstraction

Kathleen FisherAT&T Labs—Research, 180 Park Ave., Florham Park, NJ 07932, USA. E-mail: [email protected]

John C. MitchellComputer Science Department, Stanford University, Stanford, CA 94305, USA. E-mail: [email protected]

While most object-oriented programming is done inclass-based languages, the trend in theoretical studyhas been to develop formal systems that are object-based, without classes and often without explicit inher-itance mechanisms. This paper studies the correspon-dence between class constructs of the form found inC++, Eiffel, and Java and object primitives of the formused in recent type-theoretic studies. One insight isthat classes require both an extensible aggregate, toserve as the basis for inheritance, and a non-extensibleform of object to support subtyping; typed object cal-culi without extensible objects or extensible records donot seem adequate for conventional class-based pro-gramming. We develop our analysis by comparing threeapproaches to class-based programming, the first us-ing records of object components called “premethods”and the latter two using an extensible form of objectcalled a “prototype.” While the first approach is sim-plest, using fewer primitive operations on objects, itdoes not seem to accurately provide several featuresof conventional class-based languages. In the latter twoapproaches, we give more comprehensive treatmentsof classes by combining prototypes with standard ab-straction mechanisms. All three treatments of classesare based on typed translations into provably sound ob-ject calculi. c© 1998 John Wiley & Sons, Inc.

1. Introduction

There are several forms of object-oriented program-ming languages, including class-based languages suchas C++ [20, 47], Eiffel [35], Java [6], Simula [7], andSmalltalk [26]; prototype-based languages such as Self[48] and Obliq [12]; and multi-method-based approachessuch as CommonLisp [46] and Cecil [15]. This paperis concerned with the study of class-based languagesand their relationship to prototype-based languages. Inparticular, we study ways to translate class-based ob-ject declarations, inheritance, and subtyping into lan-guages that do not have classes. Since class-based en-

Received February 4, 1997; revised August 19, 1997; accepted Septem-ber 3, 1997

c© 1998 John Wiley & Sons, Inc.

capsulation appears to be a separable concept that canbe provided by a form of traditional data abstraction, ourtranslations lead to a study of the relationships betweenthree language constructs: classes, prototype-based ob-ject primitives, and traditional data abstraction of theform found in languages such as CLU [34, 32], Ada [49],and ML [36].

Our study begins with an overview of essential prop-erties of class constructs as they appear in conventionaltyped, class-based object-oriented languages. Focusingon core class features shared by C++ [20, 47], Eiffel [35],Java [6], and Simula [7], we develop criteria for compar-ing and evaluating different approaches to class-basedprogramming. We then explain three such approachesusing three translations of a skeletal class construct intomore basic object calculi. The translations differ in twoways: the degree to which they respect important proper-ties of classes and the complexity of the object operationsused in the target calculus. Since these encodings aremeant to provide some insight into the degree to whichconventional object-oriented programming could be car-ried out in a statically-typed language without classes,we compare them using our design criteria for class con-structs. We believe that the differences between the threetranslations provide useful insight into the suitability ofobject-based languages for carrying out traditional class-based programming.

To the best of our knowledge, the translations in thispaper provide the first type soundness proofs for stan-dard forms of class constructs as a consequence of thetype soundness of their target object calculi. The sound-ness proof for the first target calculus appears in [4]; theanalysis of extensible object calculi is developed in fullin [21]. For brevity and to present the major design is-sues as clearly as possible, we do not repeat the technicaldetails in this paper. The interested reader may consultour previous publications [22, 23] for discussions of typesoundness proofs for extensible object calculi and [24]

THEORY AND PRACTICE OF OBJECT SYSTEMS, Vol. 4(1), 3–25 1998 CCC 1074–3227/98/010003–23

Page 2: On the relationship between classes, objects, and data abstraction

for a general discussion of type systems, object calculi,and object-oriented programming. An alternate view ofmany of these issues may be found in the book by Abadiand Cardelli [4].

The remainder of this paper is structured as follows.In the next section, we discuss the use of classes in tradi-tional object-oriented programming, developing criteriafor evaluating the properties of various class constructs.Section 3 gives the syntax of a sample class constructand two examples to be used in comparing different classmechanisms. In Section 4, we describe our notation anddiscuss the background material necessary to understandthe class encodings. Sections 5 to 7 describe and analyzethree translations of the sample class syntax into specificformal calculi, each inducing a different set of restric-tions on the use of objects and inheritance. Sections 8,9, and 10 describe related work, outline future directions,and conclude.

2. Classes

In contemporary class-based object-oriented lan-guages such as Eiffel, Java, and C++, classes are themain tool for program organization. Since program orga-nization is a complex task, classes are designed to servea number of purposes. While purists from various lan-guage camps may differ on the value of certain aspects ofeach language, we believe it is important to understandthe essential properties of core class concepts common toaccepted object-oriented languages. Although Smalltalkor Beta enthusiasts may regard portions of C++ as awk-ward or ugly, or conversely, we believe that the rise inpopularity of class-based object-oriented languages overthe past three decades is ample evidence that these lan-guages provide something of use to a large number ofprogrammers and software designers. In the interest ofpromoting sound scientific discourse on the differencebetween various object-oriented programming languagesand program development styles, it seems important totry to distill the main features of these object systemsand understand their underlying language principles andproperties. In carrying out the study reported in this pa-per and elsewhere, it is not our objective to promote anyspecific programming language or approach. Our goal isto analyze certain phenomena that are common to Eiffel,Java, C++, and related languages using the frameworkof type-theoretic analysis [38].

As a starting point, we first examine core aspects ofclass-based programming shared by these languages. Thegoal of this section is to explain our assumptions andestablish a working terminology. In brief, a class decla-ration provides a mechanism for creating objects that allshare a common implementation. Inheritance, which en-ables the reuse of object implementations, is structuredalong class lines, with derived classes incorporating someor all of the implementation of their base classes. Ad-

ditionally, classes provide access control mechanisms,which allow programmers to hide implementation de-tails. A class declaration also defines a type, namely, thetype that all objects created from it will share. Finally,in contemporary languages, class declarations explicitlydetermine the subtyping relation between object types.In the remainder of this section, we review each of theseissues in turn.

2.1. Defining Objects

A class declaration provides a mechanism for creatingobjects. Typically, all objects created from a single classhave the same representation, with the values of somedata fields differing from object to object. For exam-ple, the following class declaration, written in a Java-likepseudo-code, defines a family of Point objects, created(or instantiated) by calling the constructor function ofthe class. For simplicity, we illustrate the main ideas us-ing one-dimensional points.

class Point {x : int

setX : int→PointgetX : intmove : int→Point

constructor newPoint : int→Point}

Each object constructed from this class has an x com-ponent, representing the location of the point. The setX

and getX components write and read the x coordinate,respectively, while move changes the location by a givenoffset. For simplicity, we omit the implementations ofthese components. We will see the need for setX andgetX when we discuss access controls in Section 2.3.

For efficiency, components that are the same for allobjects created from a given class are usually shared. InSmalltalk for example, data, called instance variables,are stored within each object, while functions, calledmethods, are shared by all objects created from the sameclass. The distinction between components allocated perobject and per class does not always fall between dataand functions, but this division is a common one. Inthe Point example, the x component would be an in-stance variable in most languages, set to different valuesin different Point objects, while the setX, getX, andmove methods would be shared among all instances ofthe class.

Programmers create instances of the Point class (i.e.,Point objects) by calling the Point class constructorfunction newPoint, here distinguished by the keywordconstructor. We again omit the implementing code. Con-structor definition is an important part of class designsince each object must be initialized in accordance withany intended object invariants. Since objects are createdby calling constructor functions, we must be able to call

4 THEORY AND PRACTICE OF OBJECT SYSTEMS–1998

Page 3: On the relationship between classes, objects, and data abstraction

the constructors of a class before any objects have beencreated. Hence constructor functions are not componentsof the objects they create.

2.2. Defining Related Objects via Inheritance

Inheritance allows a new family of objects to bedefined as an incremental modification to another. Forexample, we may use inheritance to define the classColorPoint from Point as follows:class ColorPoint : Point {

c : colorturnRed : ColorPoint

getC : colorconstructor

newColorPoint : int→ color→ColorPoint}The ColorPoint class inherits the Point x, setX,getX, and move methods and adds three new ones: c,turnRed, and getC. The Point class is said to be abase, parent or super class and ColorPoint a derived,child or sub class. In addition to adding components,derived classes may also redefine (or override) existingmethods. For example, the ColorPoint class may re-define the Point move method so that ColorPointobjects change color whenever they are moved. The con-structor function newColorPoint is responsible forcreating fully initialized ColorPoint objects. In mostlanguages, the derived class constructor will need to callthe base class constructor to initialize any private dataassociated with base class objects.

As this example illustrates, inheritance is a code-reusemechanism. If we ignore typing considerations, then inprinciple, for every object or class of objects defined viainheritance, there is a functionally equivalent definitionthat does not use inheritance, obtained by expanding thedefinition so that the inherited code is duplicated. Theimportance of inheritance is that it saves the effort of du-plicating (or reading duplicated) code, and that when oneclass is implemented by inheriting from another, changesto the parent are automatically reflected in its descen-dants. Moreover, while it is operationally natural to thinkof inheritance as an abbreviation mechanism that couldbe eliminated by syntactic expansion of programs, thisview of inheritance does not accurately reflect the use ofclasses in program development. In particular, separateclasses are meant to be developed separately, with de-signers of one class oblivious (to some degree, at least) ofthe details involved in implementing another. Therefore,inheritance has a significant impact on program mainte-nance and modification.

A convenient feature of class-based inheritance isthat classes are essentially static structures. Specifi-cally, class-based inheritance may be implemented asa compile-time operation, greatly simplifying the staticchecking involved in compiling class declarations. In par-

ticular, the correctness of a derived class declaration de-pends on both the presence and absence of certain com-ponents in the corresponding base class. This informa-tion is available at compile time if the base class declara-tion is known. If we want to statically check delegation, arun-time form of inheritance based on individual objectsinstead of classes, then it is necessary to include bothpresence and absence of components as part of the statictype system. This complicates subtyping, as discussed in[22, 24, 23], for example.

2.3. Access Restrictions

Classes typically provide access controls, which areused to restrict the visibility of various object compo-nents. Some common visibility levels are private, for useonly within the class implementation; protected, for useonly by derived classes; and public, for use by anyone.As an example, we may add the annotations private, pro-tected, and public to the Point class above to obtain theclass

class Point {private x : intprotected setX : int→Pointpublic getX : int

move : int→Pointconstructor newPoint : int→Point}

This modified class permits the x component to be usedonly within the Point class declaration, i.e., within thecode for setX, getX, move, and newPoint. We maysimilarly annotate the ColorPoint class to make its c

component private. The protected Point method setX

is visible only to the implementation of the Point classand all classes derived from it, e.g., the ColorPoint

class defined above.Visibility levels help insure that separate blocks of

code have no hidden dependencies and hence may be sep-arately maintained. For example, if the designers of thePoint class decide to change its underlying representa-tion (say to a polar representation), they are free to mod-ify private components without fear of breaking eitherclient code or derived classes. Similarly, they may changeprotected components without worrying about breakingclient code; however, in this case, they may have to fixderived classes. Throughout this paper, we will use theterm “client code” for code restricted to public-level ac-cess and “derived classes” for code granted protected-level access.

There is a subtle interaction between visibility andconstructors, which we will explain by example. Con-sider the ColorPoint constructor newColorPoint.To create an initialized ColorPoint object, new

ColorPoint must initialize the inherited portion ofthe object, in particular, the x component. However, theColorPoint class has no access to private components

THEORY AND PRACTICE OF OBJECT SYSTEMS–1998 5

Page 4: On the relationship between classes, objects, and data abstraction

inherited from the parent class. Hence there must be away for a base class to provide initialization to derivedclasses. Typically, each derived-class constructor invokesa parent class constructor for this purpose.

2.4. The Types of Objects

In most statically typed, class-based languages, a classdeclaration defines a type. Each class name is regardedas a type name, and all objects created by constructors ofthe class are given this type. In general, types in differ-ent programming languages may convey different kindsof information about the values that have each type. Forexample, a type may completely determine the size of anobject in memory in some languages but not in others. Inall typed object-oriented languages, the type of an objectgives the public interface of the object, consisting of thenames of public operations (methods or member func-tions) and their types (the types of method argumentsand return values). If the implementations of objects ofa given type may differ arbitrarily, then the type of anobject may only convey a public interface. However, incurrent class-based languages, it is possible for a type(associated with a class) to also guarantee certain imple-mentation properties. For example, given a C++ objecttype, the order of function pointers in the virtual func-tion table can be determined. This inference is possiblebecause the objects of this type must all be created byeither the class of the same name or some derived class,and derived classes are implemented so that this corre-spondence between virtual function tables is maintained.Although there are varying amounts of implementationinformation that might be conveyed as part of the typeof an object (depending on the design and, possibly, im-plementation of the programming language), we find ituseful to distinguish between object types that are meantto give the interface only and object types that may guar-antee additional implementation properties.

Although this expository technique has certain draw-backs, we adopt an informal but suggestive terminol-ogy that distinguishes between two forms of object type:interface types and implementation types. As the namesuggests, interface types constrain only the interface ofobjects, specifying the set of messages each object mustunderstand and the types of these messages. This formof object type has been extensively studied in the theo-retical literature, e.g., [5, 9, 22, 42] and the earlier pa-pers appearing in [27], but it is not used in current class-based languages. In contrast, implementation types are aform of abstract type. In fact, one of the main points ofthe present paper is that the types associated with classnames in conventional object-oriented languages are ac-tually the traditional, accepted notion of abstract datatype, generalized in a natural way to allow subtyping.(General discussion of abstract data types may be foundin [33], for example.) An implementation type may guar-

antee both a public interface and a portion of the im-plementation of an object. Although interface types of-fer increased flexibility, existing languages such as C++,Java, and Eiffel ([20, 6, 35]) use implementation typessince the type of an object gives some information aboutthe ways that the object could have been created.

The distinctions we hope to clarify using the termi-nology of interface and implementation types also relyon separating the concept of a class as a way of definingthe implementation of objects from the type name asso-ciated with a class. In hopes of distinguishing betweenthe implementation part of a class and the type definedby a class, we will write class names in teletype font(ClassName) and object types in italics (TypeName).

We discuss interface and implementation types inmore detail in the remainder of this section. One goalof the paper is to show how “interface types” and “im-plementation types” may be consistently combined in asingle language and type system. This combination maybe useful in practical program development. However,our initial objective is simply to show in precise termshow the object types commonly used in prior theoreticalanalyses differ from the form of object types that arisein conventional class-based languages.

2.4.1. Interface Types. An interface type specifies alist of operations, generally as method names and returntypes. If an object is declared to implement all of theoperations with the appropriate types, it is considered anelement of an interface type. For example, objects instan-tiated from the Point class have the following interfacetype, written using a notation defined in Section 4.2.

PointInterdef= obj u 〈〈getX : int move : int→ u〉〉

An expression with this type is guaranteed to denote anobject that has at least getX and move methods. Fur-thermore, when the getX message is sent, the result isan integer. Additionally, sending the move message withan integer parameter results in an object with the typeof the receiver, i.e., PointInter. Since the return type ofthese methods is Point, we must use some form of re-cursive type for the Point class interface type. To thatend, we have replaced the Point return type of the move

method with a type variable u, which is bound by thekeyword obj to the type PointInter. We omit the x andsetX methods from the interface type because they arenot public components and are therefore not visible toclients of the class. The constructor function newPoint

does not appear in the interface because constructors arenot part of the objects they create.

6 THEORY AND PRACTICE OF OBJECT SYSTEMS–1998

Page 5: On the relationship between classes, objects, and data abstraction

We may write the interface type for the access-controlled version of the ColorPoint class as follows:

ColorPointInterdef=

obj u 〈〈 getX : int move : int→ u,turnRed : u, getC : color〉〉

Because interface types specify only the names andtypes of operations, objects with the same interface mayhave significantly different internal representations. Inparticular, objects from many different classes could allbe given the same interface type. For example, objectsinstantiated from any class with public getX and movemethods of the appropriate types will semantically havetype PointInter. There is no need to inherit from thePoint class.

A significant advantage of interface types is the flexi-bility they provide. For example, in a programming lan-guage with interface types, we could define a single typeof matrix object and then represent dense matrices withone form of object and sparse matrices with another.Both matrix representations support the same public in-terface and therefore may be given the same type. Thisallows the two kinds of matrices to be used interchange-ably in any program. A benefit is that library functionsmay be written using a matrix interface type, withoutconcern for how matrices might be implemented in anyprogram using the library.

2.4.2. Implementation Types. Informally, an imple-mentation type is a form of object type that guaran-tees both a specific public interface and some aspectsof the implementation of objects, in a fashion similarto abstract datatypes. For example, ColorPointImp, theimplementation type associated with the ColorPointclass, imposes two requirements: if an object has typeColorPointImp, then it must (i) support the public inter-face ColorPointInter and (ii) have the private and pro-tected components defined in the ColorPoint class.Thus ColorPointImp objects must have private x and ccomponents, possibly with x occurring before c in therepresentation if the language requires it. This imple-mentation constraint is not reflected in the public inter-face.

There are several reasons why a language designer (oruser) may choose to associate implementation types withclasses. These include optimizing component lookup,guaranteeing behavioral similarity, and allowing class-level protection mechanisms. We discuss these issues inmore detail in the following paragraphs.

Optimizing Lookup. This issue may be explained byexample. If we know that all Point objects inherit a spe-cific representation of x and y coordinates, then a pro-gram may be optimized to take advantage of this staticguarantee. The usual implementations of C++, for exam-

ple, use type information to statically calculate the offsetof member fields relative to the starting address of theobject. A similar calculation is used to find the offsetof methods in the virtual function table at compile time[20, Section10.7c]. Such optimizations are not possiblein an untyped language such as Smalltalk [26] and wouldnot be possible in a typed language where objects of asingle type could have arbitrarily dissimilar implementa-tions. In particular, the Java interface mechanism allowsus to specify that several classes that do not have similarimplementations all satisfy a common interface. Whenall that is known about an object (say, passed as an ac-tual parameter to a function) is an interface it supports,then basic facts about the implementation of the objectare not known at compile time. This is reflected in thefact that different Java bytecodes are used for accessingmethods when the class or interface are known.

Behavioral Guarantees. A more methodological rea-son that programmers may be interested in implemen-tation types is that they convey greater behavioral guar-antees. For example, since the only non-trivial way tocreate a new object with a given implementation type isto call the constructor function for the associated class,we can be sure that an object with a given implementa-tion type has been properly initialized and satisfies theexpected invariants for its class.

Class-Level Protection. A more subtle reason to usetypes that restrict the implementations of objects has todo with the implementation of binary operations. In anobject-oriented context, a binary operation on type A isrealized as a method that requires another A object asa parameter. In a language where all objects of type Ashare some common representation, it is possible for anA method to safely access part of the private internalrepresentation of another A object. A simple examplearises with Set objects that have only a membership testand a union operation in their public interfaces. Con-sidering interface types only, some objects of type Setmight be represented internally using bit vectors, whileothers might use linked lists. In this case, there is notype-safe way to implement union, since no single op-eration will access both a bit vector and a linked listcorrectly. With only interface types, it is necessary toextend the public interface of both kinds of sets withsome sort of “elementsAsList” method to make thisoperation possible. In contrast, if the type of an objectconveys implementation information, then a less flexi-ble but type-safe implementation of set union is possiblewithout polluting the public interfaces. In this case, allSet objects would have one representation and a unionoperation could be implemented by taking advantage ofthis uniformity. See [10] for a detailed discussion of bi-nary methods.

THEORY AND PRACTICE OF OBJECT SYSTEMS–1998 7

Page 6: On the relationship between classes, objects, and data abstraction

2.4.3. Interface vs. Implementation Types. In sum-mary, interface types and implementation types havecomplementary strengths:

Interface Implementation

Flexibility + -Efficiency - +Behavioral Guarantees - +Binary Methods - +

Current languages use implementation types, essentiallyidentifying classes and their associated object types, butinterface types have been widely studied in the theoreti-cal literature [5, 9, 22, 42, 27]. It is possible to associateboth an interface and an implementation type with eachclass, since, as we will see in the next section, an im-plementation type is semantically a subtype of the inter-face type obtained by “forgetting” the implementationconstraints. This use of subtyping allows both forms ofobject type to be used to advantage in a single language.To some extent, the Java class and interface mechanismsprovide this combination of features, although since itis necessary to specifically declare membership in eachJava interface, Java interfaces are actually a form of ab-stract data type in which the implementation constraint isvacuous, not a pure interface type of the form we discussin this paper.

2.5. Defining a Subtyping Hierarchy

Like inheritance, subtyping is a code reuse mecha-nism. Instead of supporting the reuse of object defini-tions, however, subtyping supports the reuse of clientcode. The basic principle associated with subtyping issubstitutivity: if A is a subtype of B, then any expressionof type A may be used without type error in any contextthat requires an expression of type B. To see the impor-tance of this principle, suppose we have written a largebody of code to manipulate B objects. If A is a subtypeof B, then it follows from the basic substitutivity princi-ple of subtyping that the same code may also be used tomanipulate A objects (at least without type errors). Thesignificance of subtyping is that it permits uniform op-erations over various types of data that share some basicstructure. Subtyping therefore makes it possible to haveheterogeneous data structures containing objects that be-long to different subtypes of some common base type.We write “A < : B” to indicate that A is a subtype of B.

It is important to keep in mind that subtyping is arelation on types, not classes. This distinction is eas-ily understood in languages where objects are either un-typed (in which case subtyping is an external notion thatcan be used to describe the differences between inter-faces of objects) or where objects are created individu-ally, without using classes. However, in class-based lan-

guages, as we have already noted, it is typical to as-sociate a type with each class. This may either be aninterface type or the form of abstract type we call animplementation type. The standard subtype relation oninterface types is a form of structural subtyping, whilethe standard form of subtyping on implementation typesrequires some further correspondence between hiddenimplementation constraints. The natural way to guaran-tee subtyping between implementation types, without vi-olating principles of data abstraction by exposing hiddencomponents of objects, is to make the subtyping hierar-chy a subset of the inheritance hierarchy, i.e., if

ColorPointImp < : PointImp

then ColorPoint must inherit from Point. See [25,21] for more details. Thus with implementation types,the class construct plays a substantial role in definingthe subtyping hierarchy.

Since the implementation subtyping and inheritancehierarchies are closely related, providing one mechanism(class hierarchies) to declare both the subtyping and in-heritance relations can be a significant conceptual sim-plification. This simplification does not mean, however,that the two hierarchies are necessarily the same. Forexample, C++ private inheritance allows two classes tobe related in the inheritance hierarchy without inducingan associated subtype relationship. As mentioned brieflyearlier, Java interfaces allow implementation types tohave a common supertype without sharing implemen-tations.

With interface types, the subtype relation may be in-ferred by comparing the conditions imposed by each in-terface. For example,

ColorPointInter < : PointInter

may be inferred simply by looking at the structure ofthese two types. While classes are fundamental to sub-typing between implementation types, interface types al-low subtyping and inheritance to be unrelated.

There is a third language design possibility, arisingbecause implementation types are subtypes of interfacetypes [21]. In this scenario, each class has an associatedimplementation type that is a subtype of the interfacetype obtained by “forgetting” the extra implementationconstraints. For example,

PointImp < : PointInter

The class hierarchy is responsible for determiningsubtyping relationships between implementation types,while subtype inference rules are used to infer relation-ships between interface types. Hence in this case, theclass hierarchy defines the “bottom” portion of the sub-typing hierarchy, while structural subtyping rules definethe “top.”

8 THEORY AND PRACTICE OF OBJECT SYSTEMS–1998

Page 7: On the relationship between classes, objects, and data abstraction

2.6. Evaluation Criteria

Sections 2.1 through 2.5 suggest some of the waysthat classes are used to structure code by providing reuse(subtyping and inheritance) and encapsulation (access re-strictions and initialization code) mechanisms. Based onthe discussion of the preceding sections, we believe thata class mechanism should provide at least the featuresoutlined in the following paragraphs. Where appropri-ate, we annotate these paragraphs with the sections thatcontain relevant discussion.

Coherent, Extensible Collection [Sections 2.1, 2.2]. Aclass defines an extensible collection of object compo-nents. These components may include methods, data, lo-cal constants, specifications of communication protocols,etc. In current languages, these components must be co-herent, in the sense that if a class is well-formed, all ob-jects instantiated from the class will themselves be well-formed. For example, if one of the methods of a classrequires an integer f method, then the class must con-tain an integer f method or some form of indication thatan integer method must be defined before objects can becreated. Extensibility is the basis for inheritance, allow-ing derived classes to reuse object components from theirparents, possibly adding additional coherent componentsand replacing others. Inheritance is statically checked,insuring that the components of a derived classes areconsistent. While it would be possible, in principle, tocheck consistency at object creation time, there are soft-ware engineering arguments against this practice.

Enforced Access Restrictions [Section 2.3]. A classconstruct should allow programmers to specify a levelof visibility for each of the components defined in aclass. These specifications should be enforced by thelanguage. For example, if a programmer specifies thata given method is private, the type system should in-sure that only the implementation of the given class mayaccess the method. The purpose of this restriction is toguarantee that the implementor of a class may change itsprivate implementation without introducing errors intoclients or derived classes. For this data abstraction prin-ciple to hold, access controls must apply uniformly toboth the current class and all derived classes: if a methodis private in a given class, it cannot become public oroverridden in some derived class.

Guaranteed Initialization [Sections 2.1 and 2.2].Classes provide code to initialize the components of ob-jects when they are created. This practice is essential forestablishing invariants. For example, in a class definingdoubly-linked lists, an empty list might consist of a listnode with links set appropriately. There are several waysthat initialization interacts with inheritance and encapsu-

lation. Part of class-based encapsulation is that a derivedclass cannot access private components of a parent class.Therefore, a derived class must invoke code supplied bythe parent class to initialize private components definedin the parent class. A derived class should not (and indeedcannot) be responsible for initializing and establishinginvariants for inherited components.

Explicit Type Hierarchy [Sections 2.4 and 2.5]. Inconventional class-based languages, the subtype relationon the types defined by classes is explicitly declared. Ifthe type of objects defined by class A is to be recognizedas a subtype of the type of objects defined by class B,then this relationship must be declared explicitly in theprogram. In C++, subtyping arises only by inheritance.In Java, there is a possibility to use both inheritance (Javaextends) and explicit declaration of subtyping betweenclasses and interfaces (Java implements). In the ter-minology of Section 2.4, classes define implementationtypes. Since there are advantages to both interface andimplementation types, it might be beneficial in the fu-ture to define languages with both forms of type thatfurther consider implementation types to be subtypes ofthe corresponding interface types. A language with sucha type structure would allow programmers to use imple-mentation types where the extra information is useful(such as in the types of binary methods) and interfacetypes where more generality is required (such as in theargument types of library functions).

Automatic Propagation of Base Class Changes.Classes are intended to support program extension, mod-ification, and maintenance. In particular, incrementalchanges to a base class are automatically propagated toall derived classes. If any type errors are introduced inderived classes because of such changes, these errors arereported at compile or link time, according to the lan-guage. For example, if a programmer adds a new methodto a parent class, that method should then be automati-cally added to all derived classes. If the new method con-flicts with a method defined in a descendent, the conflictshould be reported when the revised program is com-piled, not when objects are instantiated from the derivedclass at run time.

3. Class Construct

In this brief section, we give the syntax of the classconstruct we will translate into formal calculi in Sec-tions 5 to 7.class 〈class-name〉 { : 〈superclass 〉 }

constructor 〈name〉 : 〈type〉 → 〈class-name〉;private {〈name〉 : 〈type〉; }∗public {〈name〉 : 〈type〉; }∗

end;

THEORY AND PRACTICE OF OBJECT SYSTEMS–1998 9

Page 8: On the relationship between classes, objects, and data abstraction

This declaration of class class-name may specify aparent class superclass, gives a constructor function re-turning instances of the class with the type class-name(using the pun between class names and object types),and lists public and private methods. For the sake of sim-plicity, we leave the implementing code implicit, specify-ing an implementation in each object calculus, and omitthe protected level of visibility. We also restrict our at-tention to the special case of exactly one constructor perclass.

3.1. Point and ColorPoint Examples

To simplify the exposition in the following sections,we will use the familiar example of points and colorpoints, slightly streamlined from the versions describedin Section 2. Using the syntax from Section 3 theseclasses may be written as follows:

class Point

constructor newPoint : int→Point;private x : int;public setX : int→Point;

getX : int;end;

class ColorPoint : Point

constructor newCPoint : int→ color→ColorPoint;private c : color;public setC : color→ColorPoint;

getC : color;end;

Intuitively, the set methods are used to assign to privatelocation or color data while the get methods read theseprivate values. The constructor functions newPoint andnewCPoint return new Point and ColorPoint ob-jects, respectively, initialized to store the values passedto them as parameters.

4. Preliminaries: Objects, Object Types,and Existential Types

Before proceeding to the first translation, we introducesome background material, including basic object-calculiterminology and notation, the object types we will use,and a form of existential type that will be useful in hidingprivate components.

4.1. Objects, Fields, and Methods

Many of the basic object operations that we will needmay be presented using Cardelli’s Obliq [12], a sim-ple dynamically-typed language for distributed program-ming that has been used for a variety of purposes, includ-ing graphics and web “oblets.” Obliq has a pure objectsystem which is not based on classes. Although we referto Obliq as an example of a “real" programming languagebased on pure objects without classes, we will describeits operations using a simplified lambda-calculus-style

notation that has fewer syntactic distinctions than Obliqbut retains the same essential expressiveness.

An Obliq object may be written in the form

〈m1 = e1, . . . , mk = ek〉where each ei defines a component of the object. Forclarity, Obliq distinguishes between two forms of com-ponents, data fields and methods. The distinction is notbased on the types of components, since functions areconsidered a particular kind of data. Instead, the distinc-tion is based on how the component is accessed. Specif-ically, simple selection, as in records, is used for fields,while method invocation is used for methods. We willuse a dot notation (a.x) to denote field selection and anarrow (a⇐m) for message sending. The distinction inhow these components are accessed may be illustratedusing the object

adef= 〈x = 6, setX = λ (self, n) self.x := n〉

We have the value of a.x is 6 simply by extracting thevalue of x, and

a⇐setX(8) =〈x = 8, setX = λ (self, n) self.x := n〉

by a function call that involves passing the receiver ob-ject a to the setX method as the first actual parameter.The important point is that method invocation is consid-ered an atomic operation; if a component is a method,it is not possible to extract the method and apply it (asa function) to any object other than the object to whichit belongs. An object with only fields may be called arecord.

Several formal calculi that provide Obliq operationsand more have been developed [5, 2, 3, 37, 22, 23]. As afoundational point, it is possible to eliminate the distinc-tion between fields and methods since any field may berepresented as a method which does not use the selfargument. For example, we could replace x = 6 byx = λ (self) 6. The only differences are that methodinvocation is less efficient than field selection in practiceand that objects of a given class typically have differentdata but the same methods. This second distinction al-lows objects to share method code and method lookuptables.

For syntactic simplicity, we will often assume everyobject component is a method and write objects in theform

〈m1 = e1, . . . , mk = ek〉where each expression e1, . . . , ek is assumed to be a func-tion that expects its host object as its first actual parame-ter when invoked. We use the following equality to eval-uate message sending:

〈m1 = e1, . . . , mk = ek〉⇐mi =ei〈m1 = e1, . . . , mk = ek〉

A useful operation on objects is to override a method,creating a new object with a different value at that

10 THEORY AND PRACTICE OF OBJECT SYSTEMS–1998

Page 9: On the relationship between classes, objects, and data abstraction

method. Using the symbol ← for override, we have

〈m1 = e1, . . . , mi = ei, . . . , mk = ek〉←mi = e′i =〈m1 = e1, . . . , mi = e′i , . . . , mk = ek〉

These object operations are shared by the calculi definedin [5, 2, 3, 37, 22, 23] and subsequent variants. Beforediscussing the related operation of object extension, itwill be useful to introduce object types.

4.2. Object Types

We will use the type expression

obj u 〈〈m1 : τ1, . . . , mk : τk〉〉to type objects that have methods m1, . . . , mk with as-sociated types τ1, . . . , τk. In this expression, the boundtype variable u may appear in the type of a method, inwhich case it refers to the type of the host object. For ex-ample, if method setX is invoked on an object of typeobj u 〈〈x : int, setX : int→ u〉〉, the result is a func-tion which, given an integer, returns an object of typeobj u 〈〈x : int, setX : int→ u〉〉. When u does not ap-pear in the types τ1, . . . , τk, we will use the shorthand

obj 〈〈m1 : τ1, . . . , mk : τk〉〉As described elsewhere [22, 23, 5, 2], obj types are es-sentially a specialized form of recursive type, with “fold-ing” combined with object formation and “unfolding”combined with method invocation.

4.3. Extensible Objects

Extensible objects support the operation of object ex-tension in addition to method invocation and methodoverride. Object extension, which we denote by the sym-bol ←+, adds new components to objects. Although itseems appealing to work in a calculus with a single formof object that is both overridable and extensible, there isunfortunately a fundamental conflict between these op-erations and subtyping, a crucial aspect of object types[22, 5]. In particular, we lose width subtyping with ex-tension and depth subtyping with override, as illustratedin the following table.

Object Inheritance Operations Sound Subtyping

none width and depthoverride width onlyextension depth only

override and extension none

Width subtyping implies that longer object types (oneswith more methods) are subtypes of narrower ones.Depth subtyping is the orthogonal notion; it specifiesthat object types with specialized components are sub-types of object types with more general ones. For exam-ple, assuming that ColorPoint < : Point, then obj 〈〈x :

ColorPoint〉〉 is a depth subtype of obj 〈〈x : Point〉〉.Throughout this paper, we will use the symbol < :w tospecify subtyping relationships in which only width sub-typing has been used.

Because of the conflict between subtyping and object-based inheritance, two different forms of object type areuseful in constructing classes: (i), an extensible form foruse within class declarations to support inheritance and(ii), a non-extensible form that provides rich subtypingfor use as the objects instantiated from classes. Ideally,there would be an easy way to obtain the non-extensibleform from the extensible one. This conversion processshould occur whenever programmers ask for instancesof the class. We shall refer to extensible objects as pro-totypes and non-extensible ones as proper objects.

Type checking prototypes is significantly more chal-lenging than type checking proper objects. Intuitively,the difficulties arise from two sources. The first is thatwe need to insure that each method body works correctlyfor all objects, extensible and otherwise, that it may even-tually live in. Since we may extend prototypes, we mustshow that a candidate method has the necessary type forall possible extensions of its original host. To expressthis form of polymorphism, we need to be able to sayformally “all future extensions” of a prototype. To thatend, we introduce a version of Wand’s rows [50] into ourtype system. Intuitively, a row is a list of methods andassociated types; it has the form 〈〈m1 : τ1, . . . , mk : τk〉〉.Row variables range over row expressions. Such vari-ables, when implicitly universally quantified, stand forany list of method-type pairs. When we bound such arow variable with a “supertype” listing the methods of aprototype, we obtain (a representation of) the collectionof all possible future extensions of that prototype.

The second source of difficulty arises from the need tohave a type operation to track object extension and to in-sure that a given method name is not used in a prototypebefore we add a new method with that name. This guar-antee insures that the new method body cannot violateany typing assumptions the old prototype may have madefor methods of that name. To track object extension, wehave equipped our type system with a type operation toextend a given row with a new method-type pair. We usethe notation 〈〈R |m : τ〉〉 to denote the row that resultsfrom extending row R with method m of type τ. We thenadd a kinding system to insure that rows do not containmultiple occurrences of the same method name. Intu-itively, this kinding system works by tracking methodabsences. Each kind consists of a set of method names,M. If row R has kind M, then R is guaranteed not tocontain any methods listed in kind M. To add methodm to row R, we need to deduce via kinding rules thatrow R has kind M and m ∈M. Recent work reveals thata kinding system that tracks potential method presenceswill also suffice [8].

THEORY AND PRACTICE OF OBJECT SYSTEMS–1998 11

Page 10: On the relationship between classes, objects, and data abstraction

Since we will use both object extension and methodoverride for inheritance, we lump the inheritance op-erations of method override and object extension to-gether in this paper. We will use types of the formpro 〈〈m1 : τ1, . . . , mk : τk〉〉 to type the extensible, over-ridable objects, reserving types of the form obj 〈〈m1 :τ1, . . . , mk : τk〉〉 for proper objects. It turns out that wemay obtain proper objects from these extensible ones viasubsumption [23]:

pro 〈〈m1 :τ1, . . . , mk : τk〉〉 < : obj 〈〈m1 : τ1, . . . , mk : τk〉〉

4.4. Comparing Object Calculi

One insight from the analysis in this paper isthat the Abadi/Cardelli calculi, which include onlynon-extensible objects, and the Fisher/Mitchell calculi,which also allow object extension, are incomparable.The Fisher/Mitchell calculi were originally formulatedwith method specialization in mind, which has led totyping rules for object formation that enforce a highdegree of polymorphism. This polymorphism is neces-sary to make each method that is added to a proto-type suitable for inclusion in any future extension ofthat prototype. However, it precludes the formation ofcertain objects that would behave sensibly only if notextended. The record-of-premethod class objects in Sec-tion 5 are an instance of this phenomenon. In its currentform, the Fisher/Mitchell calculi cannot type these ob-jects. Explicit universal quantification over rows mustbe added to remedy this deficiency. On the other hand,the Abadi/Cardelli family of calculi do not provide anyform of extensible object. This lack makes it difficult tosupport class-based programming, for the reasons enu-merated in Section 5. A key technical issue for furtherexploration is to devise a system with two forms of ob-jects: an extensible form (similar to our prototypes) forrepresenting class implementations and a second formthat supports method override and width subtyping (likethe Abadi/Cardelli objects) to behave as proper objects,with a natural form of conversion between the first andthe second forms. We do not see any fundamental im-pediment to completing a study of this additional partof the design space. Recent work by Luigi Liquori [31]addresses this issue.

4.5. Existential Types and Data Abstraction

Since we will use existential types in several places,we review a general form of datatype declaration basedon existential types. For further information on data ab-straction, the reader is referred to [34, 39, 40, 44, 45]; theconnection between existential types and data abstractionis elaborated in [39, 38].

The declaration form

Abstype t with x1 : σ1, . . . , xk : σk

is 〈τ, M1, . . . , Mk〉 in N

declares an abstract type t with “operations” x1, . . . , xk

and implementation 〈τ, M1, . . . , Mk〉. The scope of thisdeclaration is N. For example, the expression

Abstype stream with

s : stream,first : stream→ nat,rest : stream→ stream

is

〈τ, M1, M2, M3〉in

N

declares an abstract datatype stream with distinguishedelement s : stream and functions first and rest for oper-ating on streams. Within the scope N of the declaration,the stream s and operations first and rest may be usedto compute natural numbers or other results. However,the type of N may not involve stream, since this typeis local to the expression. In computational terms, theelements of the abstract type stream are represented byvalues of the type τ given in the implementation. Oper-ations s, first, and rest are implemented by expressionsM1, M2, and M3, respectively. Since the value of s mustbe a stream, the expression M1 must have type τ, thetype used to represent streams. Similarly, we must haveM2 : τ→ nat and M3 : τ→τ. Using Cartesian prod-ucts, we may put any abstract datatype declaration inthe form Abstype t with x : σ is 〈τ, M〉 in N.For example, the stream declaration may be put in thisform by combining the three operations s, first, and restinto a single operation of type stream× (stream→ nat)×(stream→ stream). We may recover s, first, and rest usingprojection functions.

Some useful flexibility is gained by considering ab-stract datatype declarations and implementations sepa-rately. An implementation for the abstract type streamconsists of a type τ, used to represent streams, and ex-pressions for the specified stream s and the stream op-erations first and rest. To describe implementations ofstreams in general, we might say that in any implemen-tation, “there exists a type t with operations of typest, t→ nat, and t→ t.” This description would give justenough information about an arbitrary implementationto determine that an Abstype declaration makes sense,without giving any information about how streams arerepresented. Thus, this construct fits the general goals ofdata abstraction, as discussed in [40], for example.

We may add abstract datatype implementations andAbstype declarations to a type system by encodingthem using existential types of the form ∃t.σ. Intuitively,each element of an existential type ∃t.σ consists of atype τ and an element of [τ/t]σ, where [τ/t]σ denotesthe type obtained by substituting τ for t in σ. Using prod-ucts to combine s, first, and rest, an implementation of

12 THEORY AND PRACTICE OF OBJECT SYSTEMS–1998

Page 11: On the relationship between classes, objects, and data abstraction

stream would have type ∃t.[t × (t→ nat) × (t→ t)], forexample.

We will write an implementation of an abstract typein the form 〈t = τ, M : σ〉, where t = τ binds t in theremainder of the expression. The bound type variable tand the type expression σ serve to disambiguate the typeof the expression. The type of a well-formed expression〈t = τ, M : σ〉 is ∃t.σ.

The Abstype declaration form allows us to bindnames to the type and value components of a datatypeimplementation. More specifically, if expression e1 hastype ∃t.τ and under the assumption that x has type τ theexpression e2 has type σ, then

Abstype t with x : τ is e1 in e2 : σ

Informally, this rule binds type variable t and ordinaryvariable x to the type and value part of the imple-mentation e1, respectively, with scope e2. A technical-sounding condition is that in Abstype t with x :τ is e1 in e2 : σ, the type variable t cannot oc-cur free in the type σ of the entire expression. This isactually a straightforward scoping condition: the boundvariable t should not be allowed to escape from its scope.

4.5.1. Existential Types and Objects. Existential typesare useful in the context of modeling objects becausethey can hide private components [42, 41]. To develop afeel for this idea, we briefly explain how point objectsmay be modeled as elements of existential type. The typeof an object with private x and public getX and setX

components could be written as follows

Pointdef=

∃Rep recordstate : Rep;methods : record

getX : Rep→ int;setX : Rep→ int→ unit;

end;end;

Each object has an internal state component and arecord of methods that operate on that state. The typeof the internal state is hidden by the existential quanti-fier, so external functions cannot operate on it. The typesof an object’s methods are written in terms of the typeof the hidden state.

Now that we have defined the operations and typesthat we need, we may proceed with the first class encod-ing, translating the class construct we saw in Section 3into a simple object calculus.

5. Record-of-Premethods Model

In Cook’s important early work on the foundationsof object-oriented languages [16, 17], each class is rep-

resented by a function, called an object generator. Thefixed point of a generator is a record that represents anobject, with recursion used to resolve references to self.The reason for object generators is that inheritance can-not work directly on objects that encapsulate (or hide)the dependence of one method on another. However, in-heritance may be formulated as an ordinary operation onmethod bodies if we explicitly treat methods as functionsof self [37].

In the context of object calculi, it seems natural todefine inheritance using premethods, functions that arewritten with the intent of becoming object methods, butwhich are not yet installed in any object. Like Cook’sgenerators, premethods are functions that explicitly de-pend on the “object itself,” typically assumed to be thefirst parameter to the function. Following this idea, Abadiand Cardelli have proposed encoding classes in a pureobject system using records of premethods [5]; theseideas are also used in [43]. In this approach, a class isan object that contains a record of premethods and aconstructor function used to package these premethodsinto objects. We illustrate some of the advantages anddisadvantages of the record-of-premethods approach byexample.

5.1. Example Classes

Figure 1 shows the six declarations that result fromtranslating the classes given in Section 3 into a simpleobject calculus along the lines outlined above. The firstthree declarations give (i) the object type associated withthe Point class:

obj 〈〈x : int ref, getX : int, setX : int→ unit〉〉,(ii) the record of Point premethods PointImpl, and(iii) the Point constructor newPoint. For the sakeof simplicity, we assume an imperative semantics, withsetX changing the value of an assignable x field. Thisassumption allows us to use unit as the return typeof setX, side-stepping some difficulties associated withmethod specialization. In brief, method specialization oc-curs when the types of methods are “improved” duringinheritance to reflect their new, more specialized hostobject. For example, if the type of setX were Point inPoint objects and ColorPoint in ColorPoint objects,we would say the type of setX was specialized dur-ing inheritance. Although the record-of-premethods ap-proach can support method specialization, significantlymore complex types are required to do so [4]. (The rea-son the x component of the PointImpl record does nottake Point as an argument is because it is a field, not amethod.)

The next three declarations give the correspondingrepresentation for the ColorPoint class. Note that theimplementation CPointImpl of ColorPoint is de-fined from the implementation PointImpl of Point.

THEORY AND PRACTICE OF OBJECT SYSTEMS–1998 13

Page 12: On the relationship between classes, objects, and data abstraction

type Point = obj 〈〈x : int ref, getX : int, setX : int→ unit〉〉;val PointImpl : record

x : int ref;getX : Point→ int;setX : Point→ int→ unit;

end;

= record

x = ref 0;getX = λ(self) !(self.x);setX = λ(self) λ(newX) self.x := newX;

end;

fun newPoint : int → Point

= λ(xi)let s = 〈 x = PointImpl.x,getX = PointImpl.getX,setX = PointImpl.setX 〉

in

s⇐setX xi;s

end

type ColorPoint = obj 〈〈 x : int ref, getX : int, setX : int→ unit,c : color ref, getC : color, setC : color→ unit〉〉

val CPointImpl : recordx : int ref;

getX : ColorPoint→ int;setX : ColorPoint→ int→ unit;

c : color ref;getC : ColorPoint→ color;setC : ColorPoint→ color→ unit;

end;

= record

x = PointImpl.x;getX = PointImpl.getX;setX = PointImpl.setX;

c = ref red;getC = λ(self) !(self.c);setC = λ(self) λ(newC) self.c := newC;

end

fun newCPoint : int→ color → ColorPoint

= λ(xi)λ(ci)let s = 〈 x = CPointImpl.x,getX = CPointImpl.getX,setX = CPointImpl.setX 〉

c = CPointImpl.c 〉getC = CPointImpl.getC,setC = CPointImpl.setC 〉

in

s⇐setX xi;s⇐setC ci;s

end

FIG. 1. Type and constructor declarations for Point and ColorPoint classes.

14 THEORY AND PRACTICE OF OBJECT SYSTEMS–1998

Page 13: On the relationship between classes, objects, and data abstraction

This reuse is type correct because ColorPoint < : Point,and therefore, by the contravariance of function types,we have Point→τ < : ColorPoint→τ for each type τ.However, this dependency appears to be the extent towhich inheritance can be realized. It is not possible toreuse the Point constructor, newPoint, because ob-jects are not extensible in this model. Thus, we cannotuse a Point object to create a ColorPoint object (ex-cept trivially).

5.2. Adding Protection

In the basic record-of-premethods encoding shown inFigure 1, the x and c fields of a point or color point ob-ject are not private; they are just as accessible as the pub-lic methods. There are three ways of incorporating pri-vate fields into the record-of-premethods approach, eachwith advantages and disadvantages. The first approachuses subtyping to restrict the return type of construc-tor functions to reflect only public methods. The seconduses a variation of the existential-type object model. Thethird uses closures to create private fields whenever aconstructor is called.

5.2.1. Protection via Constructor Return Type. Inthis modification of the basic encoding, illustrated inFigure 2, there are two object types associated with thePoint class. Type Point lists the public components,while PointPriv lists the public and the private compo-nents. The premethods assume the self object has theprivate as well as the public components, but the con-structor function exposes only the public methods sinceits return type is Point instead of PointPriv. This changeof type is allowed because PointPriv is a subtype of Point(in symbols, PointPriv < : Point).

Although this use of subtyping hides the x compo-nent from clients who invoke the newPoint constructorfunction, it fails to hide the x premethod in the recordused by derived classes. Consequently, if the Point

class designers wish to change their private representa-tion, they must worry about breaking client code that hascome to rely on a supposedly private part of the class. Asecond encapsulation problem stems from the fact thatthe ColorPoint class must explicitly extract the pri-vate components of PointImpl to inherit them. In sum-mary, this modification using constructor subtyping pro-vides private methods and fields, but derived classes mustbe aware of the private components of parent classes.

5.2.2. A Refinement. Martin Abadi has suggested a re-finement to the record-of-premethods approach that usesa form of existential type to hide the types of privatecomponents [1]. (A similar idea appears in [43].) Figure 3

illustrates the idea using the declarations associated withthe Point class.

In this refinement, the x premethod is still containedin the PointImpl record, but the type of x is hiddenby an existential binding of its type. Since the otherpremethods of the Point class are within the scope ofthis existential binding, they can use the x premethod ina non-trivial way. However, other functions cannot per-form any operations on x that are specific to the type ofx. The main differences between this encoding and thesubtyping approach illustrated in Figure 2 include:

• parameterizing PointPriv by the representation typefor x,

• giving PointImpl existential type to hide the privaterepresentation for x,

• changing the argument type for each of the premeth-ods to be PointPriv(R), the private type instantiated tothe hidden representation type R,

• implementing PointImpl as an element of existen-tial type and binding the representation type variableR to the selected representation type, int ref, and

• moving the code for the constructor function intoPointImpl so that it will be within the scope ofthe hidden representation type.

This use of existential types hides the private x com-ponent from general use: since the x premethod has anunknown type, clients cannot access or manipulate it inany non-trivial way. However, to use existential types, itis necessary to complicate the object calculus with exis-tential types.

A remaining problem is that derived classes must ex-plicitly name the private components they wish to inherit.For private fields, there is at least a partial solution tothis naming problem [1]. For example, each class couldhave a special component named “private fields”that stores a record of all the private fields. Instead ofnaming each of the fields individually, then, a derivedclass can simply refer to private fields. At the priceof this one name being globally known, we can insurethat the actual private names do not leak out.

In this variant of the record-of-premethods model,class designers can guarantee that objects are initial-ized properly. Intuitively, this guarantee is possible be-cause the only way to build a Point object is to callthe newPoint function, and the Point class design-ers can insure that they properly initialize all of theirancestor classes. Unfortunately, this process works onlyfor the specific class where a constructor is defined; theinitialization guarantees are not preserved under inheri-tance. The problem arises from the fact that inheritanceis based on records of premethods instead of construc-tor functions. Because inherited private components haveunknown type, derived classes cannot initialize them di-rectly. Since constructor functions return non-extensibleobjects, they cannot be used for this initialization either,except trivially. Instead, parent classes must supply sep-

THEORY AND PRACTICE OF OBJECT SYSTEMS–1998 15

Page 14: On the relationship between classes, objects, and data abstraction

type Point = obj 〈〈getX : int, setX : int→ unit〉〉;type PointPriv = obj 〈〈x : int ref, getX : int, setX : int→ unit〉〉;

val PointImpl : recordx : int ref;

getX : PointPriv→ int;setX : PointPriv→ int → unit;

end;

= record

x = ref 0;getX = λ(self) !(self.x);setX = λ(self) λ(newX) self.x := newX;

end;

fun newPoint : int → Point

= λ(xi)let s = 〈 x = PointImpl.x,getX = PointImpl.getX,setX = PointImpl.setX 〉

in

s⇐setX xi;s

end

FIG. 2. Declarations for the Point class with modifications to support private components via restricting the return type of Point’s constructorfunction.

arate initialization functions for this purpose. However,nothing requires derived classes to invoke the suppliedinitialization code. Hence the designers of parent classescannot assume that derived classes will always properlyinitialize the components they inherit. If a class (or oneof its parents) does not initialize its inherited compo-nents properly, then the objects instantiated from thatclass may not be properly initialized either.

A further weakness of this technique, pointed outto us by an anonymous referee, is that under call-by-name semantics, this approach to protection can be sub-verted. Briefly, if we define an identity function Id :PointPriv(R)→PointPriv(R), then the fixed point Y(Id)has type PointPriv(R). If the premethod getX is appliedto this value but does not depend on it, getX will returnits supposedly hidden data. For example, if the getX

premethod has the form λ(self)3 and it is applied toY(Id), it will return its “private” data 3. Under call-by-value semantics, the term Y(Id) will diverge, foiling thisparticular attack. However, there may be related prob-lems using the call-by-value fixed-point operator.

5.2.3. Protection via Closures. Figure 4 shows a ver-sion of the Point class, modified to incorporate closure-style private fields. The necessary changes to the Point

class in Figure 1 include:

• removing the x field from type Point to indicate thatx is not publicly available,

• making PointImpl into a function that allocatesstorage for private field x before creating the recordof premethods,

• rewriting the remaining premethods to reference thelet-allocated variable x,

• modifying the constructor function newPoint tofirst call PointImpl with xi to create a record ofpremethods meth referencing private field x, and

• installing the premethods in meth into the final ob-ject.

This approach succeeds in hiding the private compo-nents of parent classes and in insuring that these compo-nents are properly initialized by all derived classes. Onedrawback is that it supports only private fields; privatemethods cannot be installed into objects without violat-ing encapsulation. A further drawback is that becauseeach ancestor class of an object potentially requires aclosure, it seems difficult to implement these objects ef-ficiently, i.e., in such a way that they may share methodsuites.

Since this approach is the most successful at mod-eling the private level of visibility, we will adopt thisversion as the “record-of-pre-methods” class interpreta-tion for the purposes of the analysis in the next section.Most of the analysis is applicable to all three protectionschemes.

5.3. Evaluation

The primary advantage of the record-of-pre-methods encoding of classes is that it does not require acomplicated form of object. All that is needed is a way offorming an object from a list of component definitions.However, looking back at the list of desiderata in Sec-

16 THEORY AND PRACTICE OF OBJECT SYSTEMS–1998

Page 15: On the relationship between classes, objects, and data abstraction

type Point = obj 〈〈getX : int, setX : int→ unit〉〉;type PointPriv(R) = obj 〈〈x : R, getX : int, setX : int→ unit〉〉;

val PointImpl : ∃R recordx : int ref;

getX : PointPriv(R)→ int;setX : PointPriv(R)→ int→ unit;newP : int→Point;end;

= 〈 R = int ref ,record

x = ref 0;getX = λ(self) !(self.x);setX = λ(self) λ(newX) self.x := newX;newP = λ(xi)let s = 〈 x = PointImpl.x,

getX = PointImpl.getX,setX = PointImpl.setX 〉

in

s⇐setX xi;s

end

end〉;fun newPoint : int → Point

= λ(xi)PointImpl.newP xi

FIG. 3. Declarations for the Point class with modifications to support private components via restricting the return type of Point’s constructorfunction and hiding the types of private components via data abstraction.

tion 2.6, it is clear that this approach has some seriousdrawbacks. We discuss each of the criteria in order.

Coherent, Extensible Collection. The combination ofa record of premethods and a constructor function maybe thought of as a coherent, extensible collection. Be-cause premethods are simply fields in a record, nothingrequires that they be coherent until a constructor func-tion is supplied. Since the constructor function installsthe premethods into an object, however, the fact that agiven constructor is typable implies that the premethodsit uses are coherent. Notice, however, that nothing re-quires a given constructor to mention all of the premeth-ods in a given premethod record.

Guaranteed Initialization. In the record-of-premeth-ods model, the code to initialize private instance vari-ables is guaranteed to run if any of the associatedpremethods is installed into an object. However, con-structor functions may not be usefully reused in derivedclasses. A consequence is that if a class designer puts ad-ditional initialization code into a class constructor, thereis no guarantee that this code will be executed for all de-rived classes. (In fact, it is guaranteed not to be.) Thereare several program-development scenarios where thislack would be a serious problem. For example, addi-

tional initialization code is useful if class designers wishto use methods of an object to perform initialization.These methods are not available until an object that con-tains them has been constructed, and hence they can-not be used in the premethod function. Another scenariothat arises is that class designers may wish to performsome kind of bookkeeping whenever objects are instan-tiated from a class or its descendants. Such bookkeep-ing is common in database applications, for example. Toachieve it, programmers need a place to put code that willexecute whenever an object is instantiated. With the pro-gram structure associated with the record-of-premethodsapproach, however, there is no appropriate place: nobase class constructor function will be called for derivedclasses, and a premethod function may be called withoutcreating an object.

Enforced Access Restrictions. The record-of-premeth-ods approach succeeds in hiding private instance vari-ables, with little additional language complexity. It can-not handle private methods, unfortunately, as there is noway to install them into objects at instantiation time.There is a slightly awkward work-around that can beused if needed, however. Specifically, private functionsthat expect a “self” parameter may be allocated and ex-plicitly passed the self object at run-time to mimic pri-vate methods.

THEORY AND PRACTICE OF OBJECT SYSTEMS–1998 17

Page 16: On the relationship between classes, objects, and data abstraction

type Point = obj 〈〈getX : int, setX : int→ unit〉〉;val PointImpl : int → record

getX : Point→ int;setX : Point→ int→ unit;

end;

= λ(xi) let x = ref xi in

record

getX = λ(self) !x;setX = λ(self) λ(newX) x := newX;

end;end;

fun newPoint : int → Point

= λ(xi) let meth = PointImpl xi in

〈getX = meth.getX,setX = meth.setX 〉

FIG. 4. Declarations for the Point class with modifications to support closure-style private fields.

Explicit Type Hierarchy. In many existing class-basedlanguages, it is possible to restrict the subtypes of anobject type to classes that inherit all or part of the classimplementation. As we discussed in Section 2.4.2, thisrestriction may be useful for optimizing operations onobjects, allowing access to argument objects in binarymethods, and guaranteeing semantic consistency beyondtype considerations (some discussion of these points ap-pears in [30]). A special case of this capability is theability to define final classes, as recognized in workon Rapide [30] and incorporated (presumably indepen-dently) as a language feature in Java. This ability is lack-ing in the record-of-premethods approach since any ob-ject whose type is a structural subtype of another type τcan be used as an object of type τ.

Automatic Propagation of Base Class Changes. Be-cause derived class constructors must explicitly namethe methods that they wish to inherit, the record-of-premethods approach does not automatically propagatebase class method changes. In particular, if a derivedclass D is defined from a base class B in Java or relatedlanguages, then adding a method to B will result in anadditional method of D, and similarly for every otherclass derived from B (and there may be many). With theapproach shown here, derived class constructors mustbe explicitly rewritten each time base classes change.Since object-oriented programs are typically quite largeand maintenance may be distributed across many people,the person who maintains a base class may fail to in-form those maintaining its derived classes of its change,causing unpredictable errors. There is no mechanism inthe language to detect such errors automatically. Anotherway of stating this point is to say that in this approach,

the translation from classes to object operations is notlocal — the definition of the constructor of a class de-pends on all of the definitions of classes from whichcomponents are inherited.

While reasonable people may disagree about the po-tential for records of premethods in large-scale object-oriented system design, it seems clear to us that this ap-proach involves a significant loss of language support infavor of very simple object primitives. While we read-ily admit that simplicity is a virtue, it seems that severalimportant and desirable features are lost.

6. Classes = Prototypes + Abstraction

Extensible objects provide a rich alternative to gener-ators or premethods. They obviate the need for premeth-ods, since collections of methods that are already in-stalled in objects may be extended. Because of this fact,we may impose static constraints on the ways in whichone method may be combined with others. For example,if an object contains two mutually recursive methods,then we cannot replace one with another of a differenttype. In contrast, in the record-of-premethods approach,it is possible to form a record of premethods withouta “covering” constructor that checks to be sure that allof the premethods are coherent. A second advantage ofextensible objects is that class constructors and initializa-tion code can be inherited, i.e., reused in derived classes.For example, to create ColorPoint objects, we may in-voke the Point class constructor and add color methodsto the resulting extensible object. This process guaran-tees that the Point class has the opportunity to initial-ize properly any inherited components. It also guaranteesthat the designers of the Point class have the opportu-nity to update any bookkeeping information they maybe keeping about instantiations of Point objects. An-

18 THEORY AND PRACTICE OF OBJECT SYSTEMS–1998

Page 17: On the relationship between classes, objects, and data abstraction

other advantage arises with private (or protected) meth-ods. In the extensible-object formulation, methods al-ways remain within an object, even when it is extended.These hidden methods exist in all future extensions, butthey can only be accessed by methods that were definedbefore the method became hidden. Furthermore, theseprivate methods need not be explicitly manipulated byderived class constructors to insure that they are treatedproperly.

In this section and the next, we show how to obtainclasses by combining extensible objects and a form ofdata abstraction. As a first step, we introduce a refinedform of existential type to provide the necessary dataabstraction. We then combine the extensible objects fromSection 4.3 with this abstraction mechanism to producethe desired class construct.

6.1. Data Abstraction

To provide the encapsulation needed for classes, weintroduce a fairly standard form of bounded existentialquantification [14, 39], adapted to rows instead of types.Bounded existential types (rows) are similar to the ex-istential types we described in Section 4.5, except thatthey provide a supertype (superrow) bound for the hid-den type (row), revealing partial information about it. Inmore detail, a declaration of the form

Abstype p < :w P :: M with Ops is Impl in Client

introduces a new row name, p, with upper bound P andkind M. When we use this construct to model classes, pwill play the role of a class name. Intuitively, the upperbound P lists the methods from p that clients will be ableto use. When we encode our running Point example, Pwill have the form 〈〈getX : int, setX : int→ unit〉〉,indicating that Point objects have public getX andsetX methods. Kind M lists the method names thatderived classes may add to p. In our example, M willbe {c, getC, setC}. The functions listed in Ops are theonly operations that can access the private representationof p. Typically, class constructors and friend functionsappear in this list. Impl gives the actual implementationof the abstraction. When this construct is used, parts ofImpl are bound to p and Ops in Client, allowing Clientto use Impl as specified by P, M, and the type annota-tions in Ops.

Typically, a class implementation specifies an objectlayout and a set of method bodies (code for the methodsof its objects). In our approach, object layouts are givenby row expressions and method bodies as parts of con-structor functions. Formally, the implementation of eachclass (Impl, above) is given by an expression of the form

{|p < :w P :: M = Ppriv, ConsImp|}As above, the row variable p may be thought of as thename of the class implemented by this expression. Row

P lists the class’s publicly supported methods and theirtypes. Kind M describes how the class may be extendedvia inheritance. Thus P and M essentially form the in-terface of the class implementation construct. The re-maining components, Ppriv and ConsImp, give the ac-tual implementation of the class. Ppriv describes the “lay-out” of the class’s instances by listing the names of allof the methods defined within the class and specifyingtheir types. The constructor function ConsImp providesthe method bodies and initializes instantiated objects asnecessary.

Following general principles of data abstraction,method bodies may rely on aspects of their host’s rep-resentation that are hidden from other parts of the pro-gram. This fact is reflected in the above construct in thatthe constructor function ConsImp is within the scopeof the private interface Ppriv, and hence it may use pri-vate methods omitted from P. Our framework allows anynumber of constructor functions or other “non-virtual”operations to be provided in the ConsImp slot. How-ever, for simplicity, we discuss only the special case ofone constructor per class.

6.2. Example Classes

Using extensible objects and the abstract datatypemechanism from Section 6.1, we may encode our ex-ample class hierarchy as shown in Figure 5. As in theprevious section, we work with an imperative versionof the underlying object calculus to avoid complicationsarising from method specialization. The class interpreta-tion presented in the next section provides enough ma-chinery to allow us to address the method specializationproblem directly; it does not require an imperative se-mantics. However, as this additional machinery is some-what complex, we first present the basic ideas behindthe “Classes = Prototypes + Data Abstraction” modelwithout support for method specialization. Recent workestablishes that this target calculus is type sound [8]. Themost relevant published soundness proof for an impera-tive version of the underlying object calculus appears in[18], which also includes support for concurrency.

The two abstract type declarations in Figure 5 definethe Point and ColorPoint classes, in that order. ThePoint Abstype expression introduces row variable p,which formally serves as the “name” of the Point class.Row p is bounded by row Ppub, Point’s public inter-face. This row, defined in Figure 5, lists the methodsavailable to clients and descendants of the Point class,namely getX and setX. The kind {c, getC, setC} in-dicates that descendant classes, e.g., ColorPoint, mayadd methods with these names.

The with clause of the declaration specifies that theconstructor for the Point class, named newPoint, willreturn expressions with pro type, specifically with typepro p. The constructor returns a pro type so that de-

THEORY AND PRACTICE OF OBJECT SYSTEMS–1998 19

Page 18: On the relationship between classes, objects, and data abstraction

Abstype p < :w Ppub :: {c, getC, setC}with newPoint : int→pro p

is {|p < :w Ppub :: {c, getC, setC} = Ppriv, Impp|}in

Abstype cp < :w CPpub ::∅with newCPoint : int→ color→pro cp

is {|cp < :w CPpub ::∅ = CPpriv, Impcp|}in

let newPoint : int→ obj p = newPoint

let newCPoint : int→ color→ obj cp = newCPoint

in 〈Program〉where

Ppubdef= 〈〈getX : int, setX : int→ unit〉〉

Pprivdef= 〈〈x : int ref, getX : int, setX : int→ unit〉〉

CPpubdef= 〈〈p |getC : color, setC : color→ unit〉〉

CPprivdef= 〈〈p |c : color ref, getC : color, setC : color→ unit〉〉

andImpp

def= λ(xi)〈 x = ref xi;

getX = λ(self) !(self.x);setX = λ(self) λ(newX) self.x := newX〉;

Impcpdef= λ(xi)λ(ci)

〈newPoint xi←+ c = ref ci;←+ getC = λ(self) !(self.c);←+ setC = λ(self) λ(newC)

self.c := newC〉;

FIG. 5. Nested abstract datatypes for Point and ColorPoint classes.

scendant classes may define their objects via extensionfrom the Point class constructor. The row variable pin this return type signals that objects returned by theconstructor are defined in the Point class. We call suchtypes partially abstract after [14] because a portion oftheir structure is hidden.

The is clause of the abstraction describes the imple-mentation of the Point class. Within this implemen-tation, the row Ppriv describes the full representationof Point class objects by listing all of the methodsdefined in the class and giving their associated types.The expression Impp, defined towards the bottom ofFigure 5, gives the implementation of the constructornewPoint. The type system insures that Impp’s typeis pro p with p replaced by Point’s private interface,Ppriv. The Abstype construct binds row variable p toPpriv and variable newPoint to Impp within the scopeof its in clause.

The structure of the ColorPoint Abstype decla-ration is similar. There are several aspects of this dec-laration that are worth mentioning however. In partic-ular, notice that the ColorPoint constructor, imple-

mented via the Impcp function defined at the bottom ofFigure 5, invokes newPoint to inherit Point’s imple-mentation and initialization code. Notice also that theColorPoint class publicly names the Point class asits parent by listing p in its public interface CPpub. With-out thus naming its parent, the ColorPoint object type(obj cp) would not be a subtype of Point’s object type(obj p) because the only way to be a subtype of a par-tially abstract type is to extend that type. The row vari-able p in CPpub signals that ColorPoint objects wereformed via extension from Point prototypes, and hencetheir partially abstract types may be related via subtyp-ing. This fact is the theoretical counterpart to the ob-servation we made in Section 2 that the only way forimplementation types to be related via subtyping is forthem to be related via inheritance.

Finally, the two let declarations in Figure 5 give con-structors for the two classes that return proper objects,instead of prototypes. Technically, this shadowing is typecorrect because pro types are subtypes of obj types [23].

20 THEORY AND PRACTICE OF OBJECT SYSTEMS–1998

Page 19: On the relationship between classes, objects, and data abstraction

6.3. Evaluation

The example in this section illustrates that a principledway to think about class-based object-oriented languagesis as the combination of two orthogonal components: (i),an object system that supports inheritance and messagesending and (ii), an encapsulation mechanism that pro-vides hiding. Referring to the class-evaluation checklistin Section 2.6, we can see that this approach successfullyaddresses each of the points listed there.

Coherent, Extensible Collection. The type checkingof our object calculus insures that the methods de-fined in each class are compatible with each other: themethods are typed at class-creation time, not at object-instantiation time.

Guaranteed Initialization. The data abstraction mech-anism provided by this framework allows each class tocontrol the initialization of its objects by insuring that theonly way to get an object from a particular class is tocall its constructor. Hence class-writers can be sure thatany invariants necessary for their objects will hold forall non-trivial expressions with the object type definedin their class. For example, the only way to get a non-trivial expression with type obj p is to call the construc-tor for the Point class, insuring that such an expressionhas been properly initialized. This property holds withrespect to derived classes as well, since the only wayfor the ColorPoint class to inherit from Point is toinvoke the Point class constructor, again guaranteeingthat Point’s invariants will hold.

Enforced Access Restrictions. The mechanism pre-sented here also allows programmers to explicitly statewhich methods are public and which are private. Privatemethods are guaranteed to be used only within the classimplementation itself; hence they may be changed withgreat flexibility.

Explicit Type Hierarchy. An additional strength ofthis model is that it provides explicit control over thebottom portion of the subtyping hierarchy, as discussedin Section 2.5. In particular, object types that includeexistentially bound row variables correspond to imple-mentation types; the row variable signals the class whichdefined the type, putting such types in one-to-one corre-spondence with classes. The only subtyping that holdsbetween these types is that declared via the inheritancehierarchy, e.g., for obj cp to be a subtype of obj p,the ColorPoint class had to inherit from Point.Furthermore, to enable this subtyping relationship, theColorPoint class had to explicitly list Point as its

parent by including p in CPpub, the public interfacefor ColorPoint objects. Additionally, when we com-bine encapsulation with constructors that return the non-extensible form of object, we get final classes, classesthat have no descendants. Such classes are used in Java asa security measure and in optimizing compilers to speedmessage sending. Thus the bottom portion of the inher-itance hierarchy is explicitly under programmer control.In contrast, object types without row variables corre-spond to interface types. For these types, subtyping isdetermined by inference rules that examine their struc-ture. Finally, since we have subtyping relationships be-tween these two kinds of types, e.g.,

obj p < : obj 〈〈getX : int, setX : int→ unit〉〉we get the rich, hybrid subtyping hierarchy described inSection 2.5. In particular, we get the benefits of imple-mentation types for the bottom portion of the hierarchy,while maintaining the flexibility of interface types in thetop portion.

Automatic Propagation of Base Class Changes. Theclass structure described in this section provides explicitsupport for inheritance. In particular, when a derivedclass inherits from its parent, it must do so by callingits parent’s constructor since the full representation ofthe parent is hidden. Hence changes to the parent classare automatically reflected in all derived classes. If sucha change causes a type error in a derived class, that erroris detected as soon as the derived class is rechecked.

Problems with this Approach. A weakness of the cur-rent formulation of our idea that “Classes = Prototypes+ Data Abstraction” is that subclasses must be declaredwithin the scope of their parent classes. We believe thisshortcoming may be overcome by replacing our block-structured construct with modules and a dot notation inthe style of [13, 28]. A further weakness of our inter-pretation is the need for negative information. In effect,parent classes must currently “guess” what methods theirdescendants will want to add. A variant of the kindingsystem used here that tracks method presences instead ofabsences relieves parent classes of the need to make suchguesses. Instead of listing the methods that descendantsmay add, a class lists the methods it uses. This tech-nique has the drawback, reflected in existing languageslike C++, of exposing private names. This weaknessis much less significant than requiring parents to guesswhat their descendants will add, however. Recent workshows that kinding systems based on method presencesare sound [8]. We believe that some form of method-renaming and α-binding for method names may solvethe problem of leaking private names. Work in this areais in progress.

THEORY AND PRACTICE OF OBJECT SYSTEMS–1998 21

Page 20: On the relationship between classes, objects, and data abstraction

A problem that we do know how to solve is that offinding a way for method types to refer to the type oftheir host object. Such a type, frequently called “my-type,” is useful both in automatically specializing methodtypes during inheritance and in supporting binary meth-ods. As an example of the first of these roles, we wouldlike the setX and setC methods from our example tohave return types indicating that the expressions theyreturn have the same type as the object to which theywere sent. (Recall that we adopted an imperative seman-tics earlier to side-step this problem.) Because such typeschange during inheritance, e.g., setX returns Point ob-jects when it is sent to Points and ColorPoint objectswhen sent to ColorPoints, the ability to name “mytype”is said to support method specialization. It also allows usto write binary methods. In the next section, we presenta framework that combines the strengths (and the aboveweaknesses) of the current system with additional sup-port for method specialization and binary methods.

7. Classes with “Mytype” Specialization

To support binary methods and method specialization,we need to have a way for method types to refer to thetype of their host object. To that end, we use type vari-ables and recall that both pro and obj are type bindingoperators. In particular, when a type variable u appearswithin one of the types τi in

pro u 〈〈m1 : τ1, . . . , mn : τn〉〉u refers to the whole type (i.e., to pro u 〈〈m1 : τ1, . . . ,mn : τn〉〉), and similarly for obj types.

The row variables that we used to express unknownlists of methods in the previous section must now beparametric in the type of the host object. Hence inthis section, row variables will range over row func-tions, functions from the type of the self object to thelist of method-type pairs it supports. Row functions arewritten using the notation λt.〈〈m1 : τ1, . . . , mk : τk〉〉 orλt.〈〈R |m1 : τ1, . . . , mk : τk〉〉, where R is any row.

The final change necessary is that we need to be ableto infer subtyping relationships between types of theform obj u cp u and obj u p u. Now simply knowing thatthe cp row variable must stand for a row that extendswhatever row p represents is not sufficient to insure thecorresponding types are subtypes, because of the pos-sible contravariance of the u type variable in the typeobj u p u. Nor can we simply look at the type obj u p uto determine that u appears covariantly, since the typecontains only type and row variables. Instead, we needto add a variance analysis system to our kind system tokeep track of the variance of type variables that appearin our type and row expressions. For example, if we maygive a row function R kind T+ → (M; ∅), then the vari-ance annotation + on the T indicates that R’s argument

type appears covariantly. The ∅ in the kind indicatesthat R contains no free type variables.

7.1. Example Classes with “Mytype”

Figure 6 shows our simple class hierarchy written inthe full system. This version has the same structure asthe one in the previous section, differing only in the ad-dition of the “mytype” type variables and the varianceannotations. Because of the richer type system, we maywrite the constructors for these two classes without us-ing an imperative semantics, and we could write binarymethods such as eq methods for the two classes.

7.2. Evaluation

The type system used to type check the above exam-ple is presented in full and proved type sound in [21].Because this version has the same class structure as inthe previous section, it provides the same rich supportfor classes. Also as before, it provides a rich subtypinghierarchy, the bottom portion of which is under explicitprogrammer control. In addition, the system describedhere supports method specialization and binary meth-ods. The remaining problems to be solved are to changethe kinding system to track positive, instead of nega-tive, information about method existence and to replaceour block-structured abstraction mechanism with mod-ules and a dot notation in the style of [13, 28]. We leavethese tasks to future work.

8. Other Approaches

In this section, we briefly outline other approachespeople have taken to modeling classes.

8.1. Existential Model

In [42], Pierce and Turner interpret classes as object-generating functions. In their interpretation, inheritanceis interpreted as modifications to the object-generatingfunctions that model classes. This encoding is somewhatcumbersome, since it requires programmers to explic-itly manipulate get and put functions, which intuitivelyconvert between the hidden state of parent class objectsand derived class objects. Also, because this model pro-vides protection at the object-level, as opposed to theclass-level, binary methods require extra machinery. Onesuch solution appears in [41].

In [29], Hofmann and Pierce introduce a refined ver-sion of F<: that permits only positive subtyping. Withthis restriction, get and put functions are both guaran-teed to exist and hence may be handled in a more auto-matic fashion in class encodings. A disadvantage of thisapproach is that no subtyping is possible between exis-tential types because the put functions for existential

22 THEORY AND PRACTICE OF OBJECT SYSTEMS–1998

Page 21: On the relationship between classes, objects, and data abstraction

Abstype p < :w Ppub :: T+ → ({c, getC, setC}; ∅)

with newPoint : int→pro u p u

is {|p < :w Ppub :: (T+ → ({c, getC, setC}; ∅)) = Ppriv, Impp|}in

Abstype cp < :w CPpub :: T+ → (∅; ∅)

with newCPoint : int→ color→pro u cp u

is {|cp < :w CPpub :: (T+ → (∅; ∅)) = CPpriv, Impcp|}in

let newPoint : int→ obj u p u= newPoint

let newCPoint : int→ color→ obj u cp u= newCPoint

in 〈Program〉where

Ppub = λu.〈〈getX : int, setX : int→ u〉〉Ppriv = λu.〈〈x : int, getX : int, setX : int→ u〉〉CPpub = λu.〈〈p u |getC : color, setC : color→ u〉〉CPpriv = λu.〈〈p u |c : color, getC : color, setC : color→ u〉〉

andImpp

def= λ(xi)〈 x = λ(self) 0;

getX = λ(self) self⇐x;setX = λ(self) λ(newX) self← λ(s)newX〉⇐setX xi;

Impcpdef= λ(xi)λ(ci)

〈newPoint xi←+ c = λ(self) red;←+ getC = λ(self) self⇐c;←+ setC = λ(self) λ(newC)

self← λ(s)newC〉⇐setC ic;

FIG. 6. Point and ColorPoint classes encoded as nested abstract datatypes with support for method specialization.

types are not meaningful. Since objects have existentialtype, this model does not currently support subtypingbetween object types; some form of explicit coercionsare necessary. Extensions that combine positive subtyp-ing with normal subtyping, addressing this lack of objectsubtyping, seem possible.

8.2. Direct Models

Kim Bruce has developed a family of type-safe for-mal languages that model classes directly instead ofvia an interpretation as the combination of more basicprimitives. In [9], Bruce describes TOOPL, a functionalobject-oriented language. PolyTOIL, presented in [11],incorporates imperative features and introduces the no-tion of matching, a relationship between object types thatholds whenever the first is an extension of the second,regardless of the variance of the “mytype” type variable.In these languages, the type of an object reflects only itspublic interface; it cannot convey implementation infor-mation. An advantage of this approach, with respect tothe one outlined in this paper, is that parent classes donot need to predict what methods their descendants willwant to add.

Scott Smith and the Hopkins Object Group have de-signed a type-safe class-based object-oriented languagewith a rich feature set called I-Loop, [19]. Their typesystem is based on polymorphic recursively constrainedtypes, for which they have a sound type inferencing al-gorithm. The main advantage of this approach is theextreme flexibility afforded by recursively constrainedtypes. Currently, the main problem is that it returns large,difficult-to-read types. Some form of simplification maybe required. Work in this area is in progress.

9. Future Work

As pointed out earlier, there are several problemswith our current formulation of classes as a combinationof prototypes and data abstraction. The most serious ofthese problems is the current need for negative informa-tion, essentially requiring parent classes to “guess” whatmethods their descendants will want to add. A variant ofthe kinding system used here that tracks method pres-ences, instead of absences, solves this problem. Work inthis area is in progress, and for a simple object calculuswith existential types but not mytype, has been success-ful [8]. With this modification, parent classes no longer

THEORY AND PRACTICE OF OBJECT SYSTEMS–1998 23

Page 22: On the relationship between classes, objects, and data abstraction

have to guess the names of the methods their descen-dants may require, but they do have to expose their pri-vate names, an undesirable leak of private information.To counter this problem, we intend to add a form alpha-binding for method names to make private names trulyprivate. Another, less significant problem with the cur-rent formulation is that derived classes must be declaredwithin the scope of their parents. We believe we canaddress this problem by replacing our block-structuredencapsulation construct with modules and a dot notation,in a fashion similar to that done in [13, 28].

10. Conclusion

Pure object systems are simpler than class-based sys-tems; however, most of the empirical evidence for theutility of object-oriented languages is based on class-based languages, and certain features of classes seemcritical to this success. These features include:

• Static checking that guarantees that the object “parts”defined in a class are consistent with each other (forexample, all premethods must have consistent as-sumptions about the type of “self”).

• Control over the initialization of objects (both in agiven class and in all of its descendants). This controlis essential for establishing inheritable invariants, forexample.

• The ability to specify which methods and fields of agiven class are private and which are public. Thesespecifications should be meaningful both within theclass itself and within its descendants.

• Explicit control over a portion of the subtyping hier-archy.

• Preservation of the relationships between classeswhen private or public components are added. Thisproperty implies that when changes are made to a par-ent class, those changes are automatically reflected inits descendants.

We believe it is important to evaluate pure object systemsin light of their ability to support class-based program-ming styles. The fundamental constructs that seem nec-essary are (i) an extensible form of object, or perhapspre-object, to support inheritance, (ii) a non-extensibleform of object that can be created easily from an exten-sible form, and (iii) subtyping on the types of proper ob-jects. Given structural subtyping on object types, we haveshown in this paper how to gain more precise controlover the class hierarchy using standard data abstraction,enhanced with subtype constraints as described in [14].

Acknowledgments

We would like to thank Jon Riecke, John Reppy, andthe anonymous referees who contributed many usefulsuggestions for improvement of a draft of this paper.

The second author was supported in part by NSFGrants CCR-9303099 and CCR-9629754.

References

[1] Abadi, M. (1996). A few thoughts on objects (ML2000).Private E-mail, August 1996.

[2] Abadi, M., and Cardelli, L. (1995). A theory of primitiveobjects: Second-order systems, Science of ComputerProgramming, 25(2-3):81–116. A preliminary versionappeared in the 1994 Proc. of European Symposiumon Programming.

[3] Abadi, M., and Cardelli, L. (1996). An imperative ob-ject calculus, Theo. Prac. Obj. Syst., 1(3):151–166. Anearlier version appeared in TAPSOFT ’95 proceedings.

[4] Abadi, M., and Cardelli, L. (1996). A Theory of Objects,Springer-Verlag.

[5] Abadi, M., and Cardelli, L. (1996). A theory of primi-tive objects: Untyped and first-order systems, Informa-tion and Computation, 125(2):78–102. An earlier ver-sion appeared in TACS ’94 proceedings, LNCS 789.

[6] Arnold, K., and Gosling, J. (1996). The Java Program-ming Language. Addison Wesley.

[7] Birtwistle, G. M., Dahl, O.-J., Myhrhaug, B., and Ny-gaard, K. (1973). Simula Begin. Studentlitteratur, Box1717, S-222 01 Lund, Sweden; Auerbach, Philadelphia.

[8] Bono, V., and Fisher, K. (1997). A first-order,extensible-object calculus with support for classes. Un-published document.

[9] Bruce, K. (1993). Safe type checking in a statically-typed object-oriented programming language. In Proc.20th ACM Symp. Principles of Programming Lan-guages, pp. 285–298.

[10] Bruce, K., Cardelli, L., Castagna, G., The Hopkins Ob-ject Group, Leavens, G., and Pierce, B. (1996). On bi-nary methods. Theo. Prac. Obj. Syst., 1(3):221–242.

[11] Bruce, K., Schuett, A., and van Gent, R. (1995). Poly-TOIL: A type-safe polymorphic object-oriented lan-guage. In Proc. 9th European Conference on Object-Oriented Programming, pp. 26–51, Aarhus, Denmark,Springer-Verlag LNCS 952.

[12] Cardelli, L. (1995). A language with distributed scope,Computing Systems, 8(1):27–59.

[13] Cardelli, L., and Leroy, X. (1990). Abstract types andthe dot notation, pp. 479–504 in IFIP State of the ArtReports, North Holland, March 1990. Also appeared asSRC Research Report 56.

[14] Cardelli, L., and Wegner, P. (1985). On understandingtypes, data abstraction, and polymorphism. ComputingSurveys, 17(4):471–522.

[15] Chambers, C. (1995). The Cecil language: Specificationand rationale. Available from http://www.cs.washing-ton.edu/research/projects/cecil.

[16] Cook, W. R. (1987). A self-ish model of inheritance.Unpublished Manuscript.

[17] Cook, W. R. (1989). A Denotational Semantics of In-heritance, Ph.D. Thesis, Brown University.

[18] DiBlasio, P., and Fisher, K. (1996). A concurrent objectcalculus, In CONCUR ’96 Proc., pp. 655–670, Pisa.Springer-Verlag LNCS 1119.

[19] Eifrig, J., Smith, S., and Trifonov, V. (1995). Soundpolymorphic type inference for objects. In ACM Conf.Object-Oriented Programming: Systems, Languages

24 THEORY AND PRACTICE OF OBJECT SYSTEMS–1998

Page 23: On the relationship between classes, objects, and data abstraction

and Applications, pp. 169–184.[20] Ellis, M., and Stroustrup, B. (1990). The Annotated C++

Reference Manual. Addison Wesley.[21] Fisher, K. (1996). Type Systems for Object-Oriented

Programming Languages, Ph.D. Thesis, Stanford Uni-versity.

[22] Fisher, K., Honsell, F., and Mitchell, J. C. (1994). Alambda calculus of objects and method specialization,Nordic J. Computing (formerly BIT), 1:3–37. A prelim-inary version appeared in Proc. IEEE Symp. on Logicin Computer Science, 1993, pp. 26–38.

[23] Fisher, K., and Mitchell, J. C. (1995). A delegation-based object calculus with subtyping. In Proc. 10thInt’l. Conf. Fundamentals of Computation Theory(FCT ’95), pp. 42–61, Springer-Verlag LNCS 965.

[24] Fisher, K., and Mitchell, J. C. (1995). The developmentof type systems for object-oriented languages, Theo.Prac. Obj. Syst., 1(3):189–220. A preliminary versionappeared in TACS ’94 proceedings.

[25] Fisher, K., and Mitchell, J. C. (1996). Classes = Ob-jects + Data Abstraction, Technical Report STAN-CS-TN-96-31, Stanford University.

[26] Goldberg, A., and Robson, D. (1983). Smalltalk–80:The Language and its Implementation, Addison Wes-ley.

[27] Gunter, C. A., and Mitchell, J. C., eds. (1994). Theo-retical Aspects of Object-Oriented Programming, MITPress.

[28] Harper, R., and Lillibridge, M. (1994). A type-theoreticapproach to higher-order modules with sharing. InProc. 21st ACM Symp. on Principles of ProgrammingLanguages.

[29] Hofmann, M., and Pierce, B. C. (1995). Positive subtyp-ing, Information and Computation, 126(1):11–33, 1996.A preliminary version appeared in Proc. 22nd ACMSymp. on Principles of Programming Languages, 1995.

[30] Katiyar, D., Luckham, D., and Mitchell, J. C. (1994).A type system for prototyping languages. In Proc. 21stACM Symp. on Principles of Programming Languages.

[31] Liquori, L. (1996). An extended theory of primitive ob-jects: First and second order systems, Technical ReportCS-23-96, Dipartimento di Informatica, Universita diTorino. A portion of this work appears in ECOOP ’97Proceedings, Springer-Verlag LNCS 1241.

[32] Liskov, B., et al. (1981). CLU Reference Manual,Springer-Verlag LNCS 114, Berlin.

[33] Liskov, B., and Guttag, J. (1986). Abstraction and Spec-ification in Software Development, MIT Press.

[34] Liskov, B., Snyder, A., Atkinson, R., and Schaffert, C.(1977). Abstraction mechanisms in CLU, Comm. ACM,20:564–576.

[35] Meyer, B. (1992). Eiffel: The Language, Prentice-Hall.[36] Milner, R., Tofte, M., and Harper, R. (1990). The Def-

inition of Standard ML, MIT Press.[37] Mitchell, J. C. (1990). Toward a typed foundation for

method specialization and inheritance. In Proc. 17thACM Symp. on Principles of Programming Languages,pp. 109–124.

[38] Mitchell, J. C. (1996). Foundations for ProgrammingLanguages, MIT Press.

[39] Mitchell J. C., and Plotkin, G. D. (1985). Abstract typeshave existential types, ACM Trans. on ProgrammingLanguages and Systems, 10(3):470–502. A preliminaryversion appeared in Proc. 12th ACM Symp. on Princi-ples of Programming Languages.

[40] Morris, J. H. (1973). Types are not sets. In 1stACM Symp. on Principles of Programming Languages,pp. 120–124.

[41] Pierce, B. C., and Turner, D. N. (1993). Statically typedfriendly functions via partially abstract types, TechnicalReport ECS-LFCS-93-256, University of Edinburgh,LFCS. Also available as INRIA-Rocquencourt Rapportde Recherche No. 1899.

[42] Pierce, B. C., and Turner, D. N. (1994). Simple type-theoretic foundations for object-oriented programming,Journal of Functional Programming, 4(2):207–248. Apreliminary version appeared in Proc. 20th ACMSymp. on Principles of Programming Languages, 1993,under the title Object-oriented programming withoutrecursive types.

[43] Reppy, J. H., and Riecke, J. G. (1996). Classes in ObjectML via modules. Presented at FOOL3 workshop.

[44] Reynolds, J. C. (1983). Types, abstraction, and para-metric polymorphism. In Information Processing ’83,pp. 513–523, North-Holland, Amsterdam.

[45] Sethi, R. (1989). Programming Languages: Conceptsand Constructs, Addison Wesley.

[46] Steele, G. L. (1984). Common Lisp: The Language, Dig-ital Press.

[47] Stroustrup, B. (1994). The Design and Evolution ofC++, Addison Wesley, Chapter 3: The birth of C++.

[48] Ungar, D., and Smith, R. B. (1987). Self: The power ofsimplicity, Lisp and Symbolic Computation, 4(3):187–206, 1991. A preliminary version appeared in Proc.ACM Symp. on Object-Oriented Programming: Sys-tems, Languages, and Applications, 1987, pp. 227-241.

[49] US Dept. of Defense (1980). Reference Manual for theAda Programming Language, GPO 008-000-00354-8.

[50] Wand, M. (1987). Complete type inference for simpleobjects. In Proc. IEEE Symp. on Logic in ComputerScience, pp. 37–44. Corrigendum in Proc. IEEE Symp.on Logic in Computer Science, p. 132, 1988.

THEORY AND PRACTICE OF OBJECT SYSTEMS–1998 25