26
Generic Packages and Expandable Classes Stein Krogdahl Dep. of Informatics, University of Oslo, Norway steinkr@ifi.uio.no Research Report no. 298 October 2001 Abstract This paper presents a generic mechanism aimed at large scale code reuse in object-oriented programs. The generic units are not classes, but rather collections of (mutually dependent) classes, called “generic packages”, in which some of the classes may be designated as “expand- able classes”. When a generic package is imported, these classes should be given a “named actual expansion”, and the import will result in a new textual copy of the package body where these expansions are in- cluded, and where the expandable classes are renamed throughout the package. We then get a program typed as if all code was written es- pecially for the importing program. Generic packages may also have “traditional” generic parameters (as are usual for generic types, gen- eric classes, template classes etc.). By allowing two expandable classes (may be from different generic packages) to have a common expansion, we may get a restricted form of multiple inheritance that are handled fully at compile time, and therefore gives no extra complexity at run time. Keywords: Generic mechanisms, code reuse, multiple inheritance 1

Generic packages and expandable classes

Embed Size (px)

Citation preview

Generic Packages and Expandable Classes

Stein Krogdahl

Dep. of Informatics, University of Oslo, Norway

[email protected]

Research Report no. 298October 2001

Abstract

This paper presents a generic mechanism aimed at large scale codereuse in object-oriented programs. The generic units are not classes,but rather collections of (mutually dependent) classes, called “genericpackages”, in which some of the classes may be designated as “expand-able classes”. When a generic package is imported, these classes shouldbe given a “named actual expansion”, and the import will result in anew textual copy of the package body where these expansions are in-cluded, and where the expandable classes are renamed throughout thepackage. We then get a program typed as if all code was written es-pecially for the importing program. Generic packages may also have“traditional” generic parameters (as are usual for generic types, gen-eric classes, template classes etc.). By allowing two expandable classes(may be from different generic packages) to have a common expansion,we may get a restricted form of multiple inheritance that are handledfully at compile time, and therefore gives no extra complexity at runtime.

Keywords: Generic mechanisms, code reuse, multiple inheritance

1

Contents

1 Introduction 3

2 Some examples 6

2.1 A graph example . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.2 Importing one generic package into another . . . . . . . . . . 9

2.3 Other useful features . . . . . . . . . . . . . . . . . . . . . . . 10

3 A more detailed definition of generic packages 11

3.1 Traditional generic parameters . . . . . . . . . . . . . . . . . 14

4 Multiple imports of the same generic package 15

5 A reduced “static” form of multiple inheritance 17

6 Renaming and visibility control 21

7 Extensions and variations 22

7.1 Allowing subclasses of expandable classes . . . . . . . . . . . 22

7.2 Can we avoid listing the expandable classes? . . . . . . . . . . 24

7.3 A shorthand notation for single generic classes . . . . . . . . . 24

2

1 Introduction

Even if classes are the basic building block of object-oriented programs,implementations of abstractions or functional units will often consist of col-lections of mutually dependent classes. Such collections of classes will oftenalso be the natural form of prewritten units meant for usage in other pro-grams. This is the main reason e.g. for introducing the package-concept inJava. One idea behind the work presented here is that if an object-orientedlanguage should be equipped with some sort of generic construct, then thebest solution might, for the same reasons as above, be to use the packageconstruct as a basis.

Thus, this paper introduces the concept of a “generic package”, which at theoutset simply is a package equipped with traditional generic parameters. Weshall write such packages in a textually closed form in the following style:

generic package GP [E,F] { class A{...E...F...} class B{...E...F...} ... }

Here E and F are formal generic parameters. These are class names, andfor the time being they are not further specified. When the package is used,these parameters should be replaced by actual classes.

A program using a generic package does this by a “generic import” statementthat also includes the actual generic parameters. One should note that ageneric import statement naturally is a “copy-import”, in the sense that eachsuch import statement gives a new (more or less modified) “textual copy” ofall the classes of the generic package. In contrast, a normal import in Java isan “access-import”, in the sense that two imports of the same package givesaccess to the very same set of classes.

Generic packages in the above form make up a concept that may be use-ful in many situations. However, it turns out that having only traditionalgeneric parameters is rather cumbersome for some frequently occurring situ-ations. To obtain a more flexible concept, we therefore propose an additionalmechanism called “expandable classes” which takes care of the case wherea number of related classes in the package should be extended “in parallel”when the package is used.

Roughly speaking, this mechanism works so that some of the classes in ageneric package are declared as expandable, and when the package is used in aprogram (through a generic import), each expandable class is given an “actualexpansion” (given as a special sort of subclass) and a new name. Then, inthe new textual copy generated of the package classes, the actual expansionswill be added, and the new names will replace the old ones throughout thepackage. As we shall see, this may lead to a program typed as if the packageclasses were written especially for the current program.

3

A full generic package will be written in the following form:

generic package GP expandable A, B [E, F]

{ class A{ ... } class B{ ... } class C{ ... } ... }

Generic import statements should be given at the start of programs (or, aswe shall see later, at the start of other, may be generic, packages). They willbe given in the following style:

generic import GP with X, Y [U, V];

...

class X expansion { The actual expansion of A for the above generic import }

class Y expansion { The actual expansion of B for the above generic import }

...

class U { ... } ... class V { ... } ...

Thus, the actual expansions are given as classes, but they are marked withthe keyword “expansion”. Such a class can have no superclass in the normalsense.

As usual, the intent when proposing a generic concept is to provide a mech-anism for adjusting the types used in prewritten code to those of the environ-ment where it is to be used. Thus, quite general code can be prewitten, andcan be used in different settings. One should note that for object-orientedlanguages, already the concepts classes/subclasses offers a way to write suchflexible reusable code, without the use of generic constructs.

As an example, separately written code may define a linked list system wherethe element class has a next-pointer for implementing the list, but otherwiseno data. To use this code in a program, we simply insert it textually anddefine the actual element class, say class “ActElem” with a variable “name”,as a subclass of the prewritten one, and things will then work out fine toquite a large extent.

However, the above scheme will not work out exactly as if we had writtenthe class ActElem ourselves, including the list handling methods and thevariable “next”. In that case we could, within the class ActElem, have said“next.name”, but with the separately written list system, we would haveto use casting and say “((ActElem)next).name”. Thus, if we only wantedActElem objects in the list, and we wanted the compiler to check and utilizethis, we did not get exactly what we wanted. However, if the list system hadbeen written as a generic package with the element class as an expandableclass, then ActElem (with the name-variable only) could given as its actualexpansion, and we would obtain exactly what we wanted.

The above problem may also be solved with more traditional generic classes(of the sorts proposed e.g. in [1–3, 7]), but if the situation becomes more

4

involved, this solution will be quite complicated and less natural than usingexpandable classes.

An important aspect of the generic packages proposed here is that it shouldbe possible to fully check them semantically without knowing the contextin which they will be used. Thus e.g. generic parameters will have to bespecified if they are to be used in any elaborate way within the package.Whether a generic package can also be separately compiled will to a largeextent depend on what format it should be compiled to, and what sort of“linker” will be used to link together the separately compiled pieces. Com-piling to Java’s bytecode and using the loader/linker of JVM will e.g. givemuch more flexibility than compiling to native relocatable code and usinga traditional linker. We shall not pursue the question of compilation anyfurther, but only talk about full semantic check-outs of the generic packages.

As we shall see, the mechanisms around generic packages and expandableclasses may also naturally be extended so that two or more expandable classes(may be from different generic packages) can be given a common actualexpansion. This leads to a reduced “static” form of multiple inheritancewhere the “superclasses” and the actual extension are merged into one classat compile time. With such a mechanism you may build the basic classes ofyour program by fetching code from expandable classes in different genericpackages, and have it typed so that it will smoothly work together as oneclass. However, all this will be done by the compiler, and will give no extracomplexity at run time.

The work presented here has lines back to earlier work done for the Simulalanguage, see [4,5]. The proposed mechanism also has some similarity to thevirtual classes proposed for Java by Thorup in [8], which in turn goes backto virtual classes in the Beta language, see [6]. However, the mechanismproposed here has a more static nature, and can be defined through puretextual transformations. It might therefore be easier to understand and use.On the other hand, its static nature will exclude some of the special usagesthat a more dynamic concept can handle, but these cases will probably notoccur very often in practise.

Thus, this paper does not claim that the proposed generic mechanism in itscurrent form is the obvious choice for an object oriented language. Rather, itsvirtue is that it is simple and covers the basic needs for code reuse in a flexibleway, and that it therefore seems worth studying. It should be extensivelytested in practical examples, and be compared to competing mechanisms.The syntax used in this paper is not meant as a final proposal, and thesyntax chosen in a specific language will heavily depend on the syntax andthe concepts of the rest of that language.

This paper is not tightly connected to any specific object-oriented language,

5

but we shall present our examples and discussions on a background of a“Java-like” language. It is hoped that the deviations we make from strictJava syntax will not be confusing.

2 Some examples

In this section we shall look at a few examples showing how generic packagesmay be useful. However, we shall first look a little closer at the ideas ofthese packages, and (as traditional generic parameters works rather straight-forward) we shall first concentrate on the mechanisms around expandableclasses. A more thorough definition will be given in a later section.

A generic package GP (without generic parameters), could look as follows:

generic package GP expandable A, B

{

class A { ... A ... B ... C ... }

// The body of A, with occurences of the names A, B, and C

class B { ... A ... B ... C ... }

// Likewise for B

class C { ... A ... B ... C ... }

// C is a "normal" (not expandable) class in the package

}

There may be certain restrictions concerning how the expandable classescan be defined and used in the package, but we skip this discussion for themoment. Instead, we look at how the above package may be imported to anactual program. This is done by a “generic import”, and the three first linesbelow make up a generic import statement and the corresponding extensions,while the last line is an ordinary class in the program. The class names listedin the bodies represent those classes that may be directly referenced at thesepoints.

generic import GP with X, Y;

class X expansion { ... X ... Y ... C ... Z ... }

class Y expansion { ... X ... Y ... C ... Z ... }

...

class Z { ... X ... Y ... C ... Z ... }

The idea now is that the three first lines of this program are replaced byadjusted textual copies of the package classes A, B and C. Thus, A and Bshould be expanded and renamed X and Y respectively, and all variables,methods etc. that are typed by A or B should correspondingly be re-typed

6

to X or Y. Thus, roughly speaking, the effect is that we get the followingprogram:

class X // Expanded and renamed version of A

{

... X ... Y ... C ... // Contents of A, with A and B replaced by X and Y

... X ... Y ... C ... Z // Contents of the expansion X, unchanged

}

class Y // Expanded and renamed version of B

{

... X ... Y ... C ... // Contents of B, with A and B replaced by X and Y

... X ... Y ... C ... Z // Contents of the expansion Y, unchanged

}

class C { ... X ... Y ... C ... } // A and B are replaced by X and Y

class Z { ... X ... Y ... C ... Z ... } // No change

In this sketch there are obviously many unanswered questions concerningname collisions, constructors, overriding of methods, visibility etc., but weshall take care of these later. One may note that the original names A andB of the expandable classes do not occur at all in the resulting program(except if we give the actual expansion the same name as the expandableclass, which is perfectly legal).

2.1 A graph example

As a first example of how this may be used, we look at a generic package“Graph” which is implementing the essence of a graph concept through twoclasses “Node” and “Edge”. Both these classes are defined as expandable inthe package, and a sketch of such a package could be as follows:

generic package Graph expandable Node, Edge

{

class Node

{

Edge firstEdge; // First in the list of edges leaving this node

Edge insertEdgeTo(Node to){ ... } // Delivers the inserted new edge

...

}

class Edge

{

Node from, to;

Edge nextEdge; // Next in the list of edges leaving the same node

void deleteMe(){ ... }

...

7

}

}

Now assume that we want to use this package as a basis for the data structurein a program handling cities and roads, where the class City is to be anexpansion of Node and Road an expansion of Edge. (One might here saythat the edges of the graph are naturally directed while roads are not, butwe disregard this to obtain a simple example.) The package may then beused as follows:

generic import Graph with City, Road;

class City expansion // Expansion of Node

{

String name;

void someMethod(){

...

int n = firstEdge.length; // OK, as type of "firstEdge" is now Road

City c = ... ;

Road r = insertEdgeTo(c); // No co- or contra-variance problems

...

}

}

class Road expansion // Expansion of Edge

{

int length;

void someMethod(){

...

String s = to.name; // OK, as type of "to" is now City

...

int n = nextEdge.length; // OK, as type of "nextEdge" is now Road

...

}

}

As indicated in the comments, no casting is necessary here, as everythingthat was typed Node or Edge in the package has been re-typed to City andRoad in the copy of the Graph-classes imported to the program. Thus weget the typing of variables and methods that we would naturally have chosenif we had written the classes City and Road from scratch in the program.

With the above scheme, the expandable classes are normally given new namesin the program where they are used. However, it would also be practical tobe able to change other names (e.g. firstEdge to firstRoad above) when ageneric package is imported. This may suitably be done by a “renaming-clause” at the end of the generic import statement, but we shall return todetails about this in a later section.

8

2.2 Importing one generic package into another

As a second example we shall look at how one generic package may use an-other. We shall demonstrate this by first writing a generic package “Linked-List” that implements the concept of singly linked lists, in a way where eachlist has a head. We shall then use this package to implement the list ofedges connected to each node in the generic package Graph. A sketch of thegeneric package LinkedList may be as follows:

generic package LinkedList expandable Head, Element

{

class Head

{

Element first;

void insert(Element e); { ... }

...

}

class Element

{

Element next;

void deleteMe(); { ... }

}

}

This package can obviously be used (with suitable expansions) in any pro-gram needing that sort of linked lists. However, as announced above, wemay also use it in the implementation of the package Graph, which can nowbe written as below. As an actual expansion may be seen as a (very specialkind of) subclass of the expandable class, we shall say that the expansion“inherits” variables and methods from the corresponding expandable class.

generic package Graph expandable Node, Edge

{

generic import LinkedList with Node, Edge;

class Node expansion

{

// The inherited variable "first" has taken the role of

// "firstEdge". Again a renaming facility could be appropriate

void insertEdgeTo(Node to){

// May suitably use the inherited method "insert(...)",

// which is here fully appropriately typed

}

...

}

class Edge expansion

9

{

Node from, to;

// The inherited variable "next" has taken the role of "nextEdge"

// The inherited method "deleteMe()", inherited from Head, is

// usable as it is for users of the Graph package

...

}

}

Notice that when a generic package P is imported to another package Q(generic or not), it is natural to import P to the package Q as such, not tothe individual classes of Q (as is done when one package is imported intoanother in Java). This is because each generic import statement gives anew (adjusted) textual copy of the package classes, so that multiple genericimport statements would be misleading.

In the above example, the class Edge, which is the actual expansion of classElement, is again declared as expandable in the generic package Graph (andlikewise for Node and Head). Such “propagation” of expandable classes willprobably occur quite often when implementations are built upon each otherin several layers.

2.3 Other useful features

We have already mentioned that generic packages should also allow moretraditional generic parameters, and that one should probably have sometype of renaming facility for the generic imports, so that e.g. “first” in theexpandable class Head could be renamed “firstEdge” in the above genericimport. Note that such renaming is unproblematic, as each generic importgives a new independent copy of the package classes.

In addition, we should look closely at how visibility of names should bestbe regulated for generic packages. We shall return to renaming and visib-ility regulation in a later section, but below we look at an example of howtraditional generic parameters may be useful.

For this we write a variation ElementList of the LinkedList package definedabove. Here we want elements to be able to enter multiple lists, and we wantthe class of the elements to be fully provided by the program importing thepackage. Such a package could look as follows:

generic package ElementList expandable Head [Element]

{

class Head

{

ElemRepr first;

10

void insert(Element e){

// Make new ElemRepr-object, insert it, and set its "elem" to "e"

}

Element getFirst(); { ... }

...

}

class ElemRepr

{

ElemRepr next;

Element elem; // Pointer to the actual element

...

}

}

In this example the formal parameter Element is not given any further spe-cification, as any class is a legal actual parameter. However, such specific-ations should obviously be allowed to make possible more elaborate use ofthe parameter classes inside the the package. The above package may nowbe used as follows in a program:

generic import ElementList with PersonGroup [Person]

class PersonGroup expansion

{

String groupName;

...

void someMethod(...){ ... String s = getFirst().name; ... }

}

class Person // Could have been provided from an imported package

{

String name, address;

...

}

One should note that when Element is a traditional generic parameter, theactual parameter (class Person) could have been brought into the programthrough another import statement (generic or not). This would not havebeen possible if Element was an expandable class of the generic package.

3 A more detailed definition of generic packages

We now give a more detailed definition of the mechanisms around genericpackages. For simplicity we at first exclude traditional generic parameters,and we postpone to a later section the discussion of visibility-control and

11

renaming (and assume for the moment that everything has full visibility).Java-like interfaces will not be discussed in this paper.

For the definition, we again look at the following schematic example of ageneric package:

generic package GP expandable A, B

{

class A { ... A ... B ... C ... }

// The body of A, with occurences of A, B, C

class B { ... A ... B ... C ... }

// Likewise for B

class C { ... A ... B ... C ... }

// C is a "normal class" in the package

}

For the time being we shall adapt the restriction that the expandable classes(A and B above) may not have subclasses within the package (but a possiblerelaxation of this will be discussed later). These classes may, however, havesuperclasses, and other classes in the package (like C above) may have bothsubclasses and superclasses.

We then assume that GP is used as follows:

generic import GP with X, Y;

class X expansion { ... X ... Y ... C ... Z ... }

class Y expansion { ... X ... Y ... C ... Z ... }

class Z { ... X ... Y ... C ... Z ... }

Here, each class marked “expansion” should occur exactly once in the with-lists of the generic import statements of the program. These classes cannothave “normal” superclasses, but they may have subclasses.

To more explicitly define the meaning of the above generic import, we saythat the above program by definition is equivalent to the following:

// Code from GP:

class $1$A { ... X ... Y ... C ... } // Class A with modified name and body

class $2$B { ... X ... Y ... C ... } // Class B with modified name and body

class C { ... X ... Y ... C ... } // Only body is modified

// Code from the program:

class X extends $1$A { ... X ... Y ... C ... Z ... }

class Y extends $2$B { ... X ... Y ... C ... Z ... }

class Z { ... X ... Y ... C ... Z ... }

}

Here the names ‘$1$A’ and ‘$2$B’ are “fresh internal names”, that by defin-ition are not usable in normal programs. (They could equally well have

12

been chosen as only ‘$1$’, ‘$2$’, ..., but the original names are included forreadability.)

In addition to the above, one more change has to be made in the code ori-ginating from GP, to obtain the correct semantics at dot-access: For eachoccurence of constructs of the form “R.s” or “R.s(...)”, where R is an expres-sion typed with an expandable class, say A, we add a suitable type-cast,changing it to “(($1$A)R).s”. The reason for this is that the type of R willchange from A in GP to X in the above program, and if X now has an at-tribute ‘s’, this could lead to a wrong result. Similar adjustments will alsobe necessary many places below (also in connection with traditional genericparameters), but it will be rather obvious where, and we shall not commenton this any further.

Constructors and object generation

As part of the transformations described above, each occurence of “newA(...)” in GP will be changed to “new X(...)”. Thus, for the resulting programto be legal, the class X must have constructors with corresponding paramet-ers and types as those of A. In a Java-like language this could suitably beprovided as follows:

The class A is renamed ‘$1$A’, and therefore also all constructors of A shouldbe renamed ‘$1$A’ (while their parameters and bodies are re-typed accordingto the general scheme). For each such modified constructor, the following isdone: If a constructor with the same number/types of parameters is definedin X, then nothing is done. Otherwise, a new constructor with the sameparameters is automatically defined in X, with a body that simply calls thecorresponding constructor in $1$A, with the same parameters. Note that thetypes of the parameters of the constructors in $1$A are already modified.

The resulting picture

As the internal names $1$A and $2$B are used only in the special waysdescribed above, and as the names A and B will not occur at all in theresulting program, the effect of this program will correspond closely to theone we rather roughly described in the previous section. Note e.g. that noobjects of the classes $1$A or $1$B will ever be generated as each “new A(...)” will be transformed to “new X( ... )”.

In addition, with the above definition, most issues that were earlier leftunclear is now well defined:

Overriding: From the above it is clear that a method defined e.g. in theexpandable class A can be overridden in its actual expansion X. One should

13

note that a method which is typed e.g. “A methodName(A a, B b)...” in Awill thus be overridden by a method typed “X methodName(X a, Y b)...” inX. This seems very convenient, and will have the effect that some of the oftendiscussed problems concerning co- and contra-variance for methods will notbecome an issue in this setting. If we, from the body of X, explicitly wantto call the A-version of “methodName”, this can (in a Java-setting) be doneby the construct “super.methodName(...)”.

Name conflicts: It is also fully legal to use the same name for a variablein X and a variable in A. However, as the user cannot use the name $1$Afor casting (or for anything else), accessing such a variable in A from thebody of X (by up-casting) is difficult, but in a Java-setting the construct“super.varName” will do it.

Finally, one should note that, even if a generic import is defined as above, itsactual implementation can to a very large extent be finalized in the compiler,by concatenating and adjusting the bodies of the expandable classes and thecorresponding actual expansions. Conceptually it should also be viewed inthis way.

3.1 Traditional generic parameters

Including also traditional generic parameters, a general generic package maylook as follows:

generic package GP expandable A, B [D <spec. of D>, E <spec. of E>]

{

<import statements>;

class A { ... A ... B ... C ... D ... E ... }

class B { ... A ... B ... C ... D ... E ... }

class C { ... A ... B ... C ... D ... E ... }

}

The specifictions of the formal class names D and E could be given either byindicating that the actual parameter should be a subclass (in the transitivesense) of a given class (as is done e.g. in [1]), or by some variant of so-called“where-clauses”, as described in [7]. Which alternative should be used willnot be further discussed in this paper. In any case, the idea is that theformal classes D and E may only be used inside the package according to thespecification.

It is important here that we, in the specification of the generic parametersD and E, at least can refer to classes made available in normal import state-ments given at the start of the package body. Allowing this will probablyalso cover most of our needs.

14

One may also discuss whether it should be allowed in these specifications torefer to classes from generic imports to the package, and/or to local classes(including expandable classes?) of the package. It is, however, not clear howuseful this would be, and this issue will therefore not be further discussedhere.

The above package can be used as follows:

import ... // U and V may be imported here

generic import GP with X, Y [U, V]

class X extends expandable { ... X ... Y ... C ... Z ... U ... V ... }

class Y extends expandable { ... X ... Y ... C ... Z ... U ... V ... }

class Z { ... X ... Y ... C ... Z ... U ... V ... }

// Or U and V may be defined here:

class U { ... X ... Y ... C ... Z ... U ... V ... }

class V { ... X ... Y ... C ... Z ... U ... V ... }

If we assume that U and V are defined locally, this should, by definition,result in the following program:

// Code from GP:

<import statements>;

class $1$A { ... X ... Y ... C ... U ... V ... }

class $2$B { ... X ... Y ... C ... U ... V ... }

class C { ... X ... Y ... C ... U ... V ... }

// Code from the program:

class X extends $1$A { ... X ... Y ... C ... Z ... U ... V ... }

class Y extends $2$B { ... X ... Y ... C ... Z ... U ... V ... }

class Z { ... X ... Y ... C ... Z ... U ... V ... }

class U { ... X ... Y ... C ... Z ... U ... V ... }

class V { ... X ... Y ... C ... Z ... U ... V ... }

Concerning generic parameters, one should also remember the problems thatoften will occur in languages with some variant of overloading (see e.g. [1]).If e.g. D and E are formal generic parameters and a class in the genericpackage contains the declarations “void M(D x){ ... }” and “void M(E x){... }” this seems perfectly legal. However, if D and E are given the sameactual parameter, it is no longer legal, and this must somehow be handled.We shall, however, not discuss this and similar cases any further here.

4 Multiple imports of the same generic package

For normal imports of packages in Java it is fully dummy to import thesame package twice to the same context. However, for generic imports eachimport will give a new modified copy of the package classes, and multipleimports with different expansions and parameters will therefore often be of

15

interest. This may e.g. be the case if a generic package defines a list system,and a program wants list systems with different element classes, and theselist systems should be kept fully apart at compile time.

If all classes of the generic package are expandable, such multiple importswill cause no name collission problems, as all classes will be given new names(and expansions) in each import. However, if the generic package containsclasses that are not expandable (as the class Head below), these will getthe same name in each generic import, so that a name collision will occurbetween the different textual versions of the class.

To handle this, we propose to add a mechanism for naming generic importstatements. As an example we look at our old generic package LinkedList,but this time the class Head is not expandable:

generic package LinkedList expandable Element

{

class Head

{

Element first;

void insert(Element e); { ...}

...

}

class Element

{

Element next;

...

}

}

We now allow the generic imports to be named, and if they are, we use dot-notation for accessing the correct version of the non-expandable classes ofthe package. Thus, a program using this package could look as follows:

P: generic import LinkedList with Person;

C: generic import LinkedList with Car;

class Person expansion { String name, ... }

class Car expansion { int weight, ... }

...

P.Head employees = new P.head(); employees.insert(new Person());

C.Head myCars = new C.head(); myCars.insert(new Car());

employees.insert(new Car()); // Will give compile time error!!

In the above, we have given names to both of the generic imports for reasonsof symmetry. However, to avoid name collisions it would have been enoughto name only one of the imports. We should note that such naming of genericimports may also be of interest if we import different generic packages thataccidentally have non-expandable classes with the same name.

16

One should also note that if we allow renaming in generic imports, then thiscan be another way to avoid such name collisions. We shall say more aboutsuch renaming later.

This naming facility for generic imports can easily be incorporated into thedefining transformation scheme described earlier. A new type of internalnames should then be used, and the program above could be transformed tothe following:

class $P$Head // The name of the generic import is used as a prefix

{

Person first;

void insert(Person e); { ...}

...

}

class $1$Element

{

Person next;

...

}

class $C$Head // The name of the generic import is used as prefix

{

Car first;

void insert(Car e); { ...}

...

}

class $2$Element

{

Car next;

...

}

class Person extends $1$Element { String name; ... }

class Car extends $2$Element { int weight, ... }

$P$Head employees = new $P$head(); employees.insert(new Person());

$C$Head myCars = new $C$head(); myCars.insert(new Car());

employees.insert(new Car()); // Compile time error!!

5 A reduced “static” form of multiple inheritance

Multiple inheritance is a much discussed feature, which in its general formgives a lot of possibilities, but also a lot of problems both implementationallyand conceptually. Thus it may be of interest to look for compromises thatcould give as many as possible of the advantages of multiple inheritance,while avoiding at least some of the typical problems.

17

It turns out that the concepts around generic packages and expandableclasses could be a framework for such a compromise. The basic idea isto allow the same expansion class to be used for more than one expandableclass, may be from different generic imports. Thus, assume that the followinggeneric packages are defined:

generic package GP1 expandable A, B { class A{ ... }, class B{ ... } }

generic package GP2 expandable C, D { class C{ ... }, class D{ ... } }

We may then e.g. use these as follows:

generic import GP1 with X, Y;

generic import GP2 with Z, X;

class X expansion { ... } // Is an expanison for both A and D;

class Y expansion { ... } // Normal expansion

class Z expansion { ... } // Normal expansion

Relying on concatenation as in the more intuitive definition used in section2, this should now lead to the following program:

class X { // consists of the concatenation of the following parts:

< The body of A, with A and B replaced by X and Y respectively >;

< The body of D, with C and D replaced by Z and X respectively >;

< The expansion X, unmodified >;

}

class Y {

< The body of B, with A and B replaced by X and Y respectively >;

< The expansion Y, unmodified >;

}

class Z {

< The body of C, with C and D replaced by Z and X respectively >;

< The expansion Z, unmodified >;

}

Thus, we have obtained a class X which contains the textual concatenationof two classes from different generic packages, re-typed so that all the codecan work nicely together, and so that it can work together with other classesfrom the two packages.

If the expandable classes A and D both have a (normal) superclass, then thesewill naturally also be superclasses of the concatenated class X. This wouldgive full-fledged multiple inheritance in the resulting program, which mightnot be desirable e.g. in Java. This problem could be handled in different

18

ways, but to currently have a simple rule we shall say that in settings wheremultiple expandable classes can be given a common expansion, we simply donot allow expandable classes to have superclasses (but they may very wellthemselves be expansions, even to multiple expandable classes).

As an example of how this “static” form of multiple inheritance may beused, we again assume that the Graph package discussed earlier is given,and that we as before aim at producing a program for handling Cities andRoads. However, we now assume that we also have another generic package,GeographyData, that has expandable classes CityData and RoadData withvariables and methods for handling information like number of inhabitantsfor cities and length for roads. Thus, we assume that the following packagesexist:

generic package GeographyData expandable CityData, RoadData { ... }

generic package Graph expandable Node, Edge { ... }

The program can then be written as follows:

generic import Geography with City, Road;

generic import Graph with City, Road;

class City expansion

{

// Should anything be added?

}

class Road expansion

{

// Should anything be added?

}

This example shows that, with this mechanism, we can “compose” the ba-sic classes of our program by concatenating code from different expandableclasses, often taken from different generic packages. And, equally important,the classes we use in the composition will still work smoothly together withthe rest of the generic package from which they were taken.

The definition by concatenation used above leaves many questions unanswered.To get a more detailed definition, we could try to use a transformation withinternal names similar to the one used earlier. The above program shouldthen be equivalenet to the following:

class $1$CityData { ... }

class $2$RoadData { ... }

19

class $3$Node { ... }

class $4$Edge { ... }

class City extends $1$CityData, $3$Node

{ ... }

class Road extends $2$RoadData, $4$Edge

{ ... }

As expected, we here end up with a program with full-blown multiple in-heritance. However, we would like to define this reduced form of multipleinheritance also in languages without full-blown multiple inheritance (e.g.Java). One way around this is to define how full multiple inheritance “shouldhave worked” if it had been included in the language, and then let the abovetransformation define the meaning of static multiple inheritance.

We then obviously only have to define the meaning of multiple inheritancefor those programs that will result from the above transformation. Oneimportant property of these programs is that they will contain no variables,methods or parameters that are typed with the $n$-classes, and no objectsof these classes will be generated. This may simplify the definition.

We shall not go into detail about how the mechanisms around multiple inher-itance could be defined for this purpose. It will depend on other propertiesof the language in question. One could e.g. use the multiple inheritancemechanism in C++ as a model, as far as it fits. The most important pointsto make clear are:

1. How and when can virtual methods e.g. in $1$CityData and $3$Node beoverridden in City?

2. If there are declarations with the same name in e.g. $1$CityData and in$3$Node, how can the one or the other of these be accessed from methodsin City?

3. All occurences of “new CityData(...)” in the package “GeographyData”and all occurences of “new Node(...)” in “Graph” will be transformed to “newCity(...)” in the resulting program. One has to make sure that this will resultin a correct program either by automatically inserting suitable constructors,or by somehow restricting the use of object generation for expandable classesin generic packages.

Finally, one should note that if some type of name-substitution scheme con-nected to generic inserts is introduced, then this may influence how the abovepoints may be solved.

20

6 Renaming and visibility control

As observed in the introduction, a normal import of a package in Java is an“access-import” in the sense that other import statements to the same pack-age will give access to exactly the same set of classes. On the other hand, ageneric import statement is a “copy-import” in that each such statement res-ults in new “copies” of the involved classes, may be with some extensions andadjustments. Thus, while e.g. renaming in connection with an access-importcould lead to double-naming and alias-problems, any sort of renaming or vis-ibility regulations in connection with generic packages and generic importsare simple to define, enforce, and understand.

An often occurring situation is that one package P imports another packageQ, where P wants to use the concepts defined by Q in its internal implement-ation. In such a case it is obvious that we want all “exported” conceps ofQ to be fully visible within P. However, to protect the concistency of P, wegenerally want the concepts of Q not to be visible from the programs thatimport P.

As explained above, this sort of distinction between what is visible insideand outside of P is easily enforced if Q is copy-imported by P. However, if Qis only access-imported by P, the users of P can usually also access-import Q,and thus gain indirect access to the implementation of P, especially becauseaccess-restrictions are usually enforced “by class”, not “by object”.

Thus, as things are so much clearer for copy-imports, we should utilize thisto offer a mechanism that satesfies the needs sketched above. Thus, for ageneric import made inside a package P (generic or not) we can offer twoversions of generic imports:

(generic) package P

{

public generic import Qa ...;

private generic import Qb ...;

...

}

Here, the public properties of Qa will be visible also outside P, while thepublic properties of Qb will be visible inside P, but not outside it. Thus, theclasses and methods exported by Qb may be used freely to implement theinner consistency of P, without fearing that the users of P can disturbe thisconsistency by accessing the same classes and methods in the same way.

In some cases we may want specific names from Qb to be visible also outsideP, e.g. if a method that should naturally be offered by P is already implemen-ted by Qb. Thus, there should be a syntax for doing this in P, which could

21

simply be e.g “public” followed by the name from Qb to be made visible alsooutside P. An example is given below.

One should note that for generic imports in programs (as opposed to thosemade in packages) the distiction between public and private generic importsis not significant. One may also note that it could be of interest to allownon-generic packages to be generically imported. This should mean that weget new “copies” of the package classes, so that we with the same effect asabove can use the public and private modifiers. This can be done by simplyconsidering the non-generic package as a generic package with no expandableclasses or generic parameters.

For renaming similar arguments apply. At the end of a generic import onecan safely add a syntactic unit describing the name substitutions you want.As an example both of a private generic import and of renaming, we mayuse the generic package LinkedList in the package Graph as follows:

generic package Graph expandable Node, Edge

{

private generic import LinkedList with Node, Edge

rename Head.first=>firstEdge, Element.next=>nextEdge;

class Node expansion

{

// Can now refer to "firstEdge"

}

class Edge expansion

{

// Can now refer to "nextEdge"

// Re-export of a method privatly imported from LinkedList:

public deleteMe();

}

}

Note that in the renaming description one should be allowed to refer to classnames that are not otherwise visible outside the imported package, and tonames of expandable classes. The rename clause should thus be seen as anextension of the body of the imported generic package.

7 Extensions and variations

7.1 Allowing subclasses of expandable classes

In the above definitions we did not allow expandable classes to have sub-classes within the generic package. The reason was that the full meaningof this was not immediately obvious. However, as a general idea, this could

22

indeed be of interest. Assume e.g. that we write a generic package Traffic,with an expandable class Vehicle, and two (not expandable) subclasses Carand Bus. When using this package we could want all Vehicles to have anadditional variable “int weight”, and we therfore import it as follows:

generic import Traffic with WeightVehicle;

class WeightVehicle expansion { int weight; }

...

Thus, when importing this generic package we get a ready-made class hier-archy, but during the import we still have the freedom to add attributes thatget effect for the whole hierarchy. This obviously gives a useful flexibility.

However, the problems with this construct are equally obvious. When run-ning the above program using Traffic, all Car-objects should probably havethe weight-attribute. But when separately checking out the package Traffic,we can obviously not accept something like:

Car c = new Car(); c.weight = 300;

However, if the very same statement occurs in the program where the packageis imported, it should be fully acceptable. This might seem a little confusing.And also, what if the class Vehicle has a method M(), and a redefinition isgiven in the expansion WeightVehicle? To which version of M() should thefollowing call then lead, and should also this depend on where the statementoccurs?:

Car c = new Car(); c.M();

Probably, it should lead to the version in WeightVehicle, independant ofwhether it occurs inside or outside the Traffic-package. A number of similarcases will turn up, and the problem is whether a consistent and understand-able definition of this mechanism can be given. Probably the possibilitiesthat opens up if we allow something like the above construct is so interestingthat it is indeed worth a closer look.

One may note that similar possibilities and problems arise if we supportmultiple static inheritance, and define another generic package W, with justone class WeightContainer which is expandable and has the only attribute“int weight”. Then the follwing should have about the same effect as theprogram above:

generic import Traffic with WeightVehicle;

generic import W with WeightVehicle;

class WeightVehicle expansion { ... anything more? ... }

...

23

7.2 Can we avoid listing the expandable classes?

The above discussion also opens up for another interesting possibiliy. Untilnow we have insisted on listing the expandable classes in the heading of ageneric package, and the main technical reason for this has been to be ableto check during the check-out of the package that the expandable classes hasno subclasses.

However, if we could remove the ban on subclasses of expandable classes,we could maybe also avoid listing the expandable classes at the top of thegeneric packages. This would allow us to choose which classes to considerexpandable until we really make use of the package. Thus we could throwaway the expandable-clause in the package definition, but on the other handwe would have to mention which class is considered expandable in the genericimport statement. Such a statement could then e.g. look like:

generic import graph with Node => City, Edge => Road;

class City expansion { ...}

class Road expansion { ...}

...

7.3 A shorthand notation for single generic classes

In simple situations where only single classes are involved, the full apparatusof generic packages might seem too heavy, and a shorthand notation couldbe appropriate. Such a notation can easily be defined, e.g. by allowing thefollowing notation:

...

generic class GC[D <spec. of D>, E <spec. of E>] { ... }

...

class C1 expands GC[U, V] { ... body of C1 ... }

...

class C2 expands GC[X, Y] { ... body of C2 ... }

...

Here U, V, X, and Y must be classes conforming to the respectve specifica-tions. This can simply be shorthand for first defining the following package:

generic package $1$GC expandable GC [D <spec. of D>, E <spec. of E>]

{

<The necessary imports from the program for the specifications>;

class GC{ ... }

}

Afterwards, this package is imported to the program as follows:

24

generic import $1$GC with C1 [U, V];

generic import $1$GC with C2 [X, Y];

...

class C1 expansion { ... body of C1 ... }

...

class C2 expansion { ... body of C2 ... }

...

We should note that C1 and C2 here become fully independent classes, andthere will be no (normal) class in the program named GC. Together, thisshould give a flexible mechanism for generic classes comparable in strengthto a number of similar constructs.

We should probably also allow constructs like:

...

generic class GC1[D <spec. of D>, E <spec. of E>] { ... }

...

generic class GC2[W <spec. of W>] expands GC1[W,Z] { ... body of GC2 ... }

// The specification of W must conform to the specification of D

...

class C expands GC1[U] { ... body of C ... }

...

Here Z must be a class conforming to the specification of E and U a classconforming to the specification of W. Finally, if multiple static inheritanceis allowed, we might also allow:

...

generic class GC1[D <spec. of D>, E <spec. of E>] { ... }

...

generic class GC2[F <spec. of F>] { ... }

...

class C expands GC1[X, Y], GC2[V] { ... body of C ... }

...

Acknowledgements

I would like to thank Birger Møller-Pedersen for reading early versions ofthis paper and for comments and suggestions. In addition Arne Wang, PeterJensen, Jo Piene, Lars Tafjord, Holm-Kjetil Holmsen, and lately Endre Ro-gnerud, Elin Bardal, and Bjørn Willassen have participated in a number of,for me, inspiring discussions around the theme of this paper.

25

References

[1] Ole Agesen, Stephen N. Freund, and John C. Mitchell. Adding typeparameterization to the Java language. OOPSLA’97, Sigplan Notices,vol 32, 1997.

[2] Gilad Bracha, Martin Odersky, David Stoutamire, and Philip Wadler.Making the future safe for the past: Adding genericity to the Java pro-gramming language. OOPSLA’98, Sigplan Notices, vol 33, 1998.

[3] Robert Cartwright and Guy L. Steele Jr. Compatible genericity withrun-time types for the Java programming language. OOPSLA’98, Sigplan

Notices, vol 33, 1998.

[4] S. Krogdahl, A. Wang, P. Jensen, and J. Piene. A module-mechanismfor flexible code reuse. ASU Newsletter, 21(2), 1993.

[5] S. Krogdahl. On modernizing the Simula language. Technical report,Department of Informatics, University of Oslo, 1986.

[6] O. L. Madsen and B. Møller-Pedersen. Virtual classes – a powerful mech-anism in object-oriented programming. OOPSLA’89, Sigplan Notices, vol24, 1989.

[7] Andrew C. Myers, Joseph A. Bank, and Barbara Liskov. Parameterizedtypes for Java. Symposium on Principles of Programming Languages,ACM:132 – 145, 1997.

[8] Kresten Krab Thorup. Genericity in Java with virtual types. InECOOP’97, LNCS 1241. Springer Verlag, 1997.

26