s

Genericity Versus Inheritance

Bertrand Meyer Interactive Software Engineering, Inc., 270 Storke Road, Suite #7, Goleta CA 93117

ABSTRACT with which a software system may be changed to account for modifications of its requirements), reus­ Genericity, as in Ada or ML, and inheritance, as in ability (the ability of a system to be reused, in whole object-oriented languages, are two alternative tech­ or in parts, for the construction of new systems), and niques for ensuring better extendibility, reusability, compatibility (the ease of combining a system with and compatibility of software components. This ar­ others). ticle is a comparative analysis of these two methods. Good answers to these issues are not purely It studies their similarities and differences and as­ technical, but must include economical and mana­ sesses to what extent each may be simulated in a gerial components as well; and their technical as­ language offering only the other. It shows what fea­ pects extend beyond programming language fea­ tures are needed to successfully combine the two ap­ tures' to such obviously relevant concerns as proaches in an object-oriented language that also fea­ specification and design techniques. It would be wrong, tures strong type checking. The discussion introduces however, to underestimate the technical aspects and, the principal aspects of the language EiffePM whose among these, the role played by proper programming design, resulting in part from this study, includes language features: any acceptable solution must in multiple inheritance and a limited form of genericity the end be expressible in terms of programs, and under full static typing. programming languages fundamentally shape the software designers' way of thinking. This article is a comparative analysis of two OVERVIEW classes of programming language features for en­ hancing extendibility, reusability, and compatibil­ Despite its name, today's software is usually ity. It assesses their respective strengths and weak­ not soft enough: adapting it to new uses turns out nesses, examines which of their components are in most cases to be a harder endeavor than should equivalent and which are truly different, shows how be. It is thus essential to find ways of enhancing such the two approaches complement each other, and ex­ software quality factors as extendibility (the ease plains how they have been combined in a particular programming language design. The two approaches studied are genericity and inheritance; both address the above issues by allow­ This article is a revised version of a paper presented at OOPSLA (First ACM Symposium on Object-Oriented Program­ ing the definition of flexible software elements ame­ ming Systems, Languages and Applications, Portland, Oregon, nable to extension, reuse, and combination. The first Sept. 29-0ct. 2, 1986), Ed. Norman Meyrowitz, published as Sig­ is a technique for defining elements that have more plan Notices, 21, 11, pp. 391-405. Trademarks: Unix (AT&T); Ada (U.s. DoD); Eiffel (Inter­ than one interpretation, depending on parameters active Software Engineering). representing types; the second makes it possible to

Journal of Pascal, Ada, & Modula-2, Vol. 7, No.2, pp. 13-30 (1988) © 1988 by John Wiley & Sons, Inc. cce 0735-1232/88/020013-18$04.00 Genericity versus Inheritance

define elements as extensions or restrictions of pre­ Unconstrained Genericity viously defined ones. 1 Both methods apply some form of polymor­ In its simplest form, unconstrained genericity phism, a notion that may be defined as the ability may be seen as a technique to bypass the unneces· to define program entities that may take more than sary requirements imposed by static type checking, 1 one form. A simple form of polymorphism, used in Consider the example of a simple procedure both cases, is overloading, the ability to attach more for exchanging the values of two variables. In a Ian· F than one meaning to the same name, ambiguities guage which is not statically typed, such as Lisp, we being resolved by examining the context of each oc­ would write something like the following, syntactic currence of the name, either at compile time (for differences notwithstanding: statically typed languages) or at run time. c Although the two approaches may be applied procedure swap (x, y) is outside the strict realm of programming, for example t local to specification or design languages, we shall confine begin our study to programming languages. In this field, t : = x; x : = y ; y : = t; genericity is most notably present in Ada; inheri­ end swap E tance is a feature of object-oriented languages and 1\ was introduced by Simula 67. The type of the elements to be swapped and Ada and object-oriented languages have until of the local variable t does not have to be specified, now aroused interest in rather different communi­ However, this may be too much freedom since a call [ ties and it is not surprising that no comparative of the form swap (a, b), where a is, say, an integer! analysis seems to have been published. (The only and b a character string, will not be prohibited even related work that we know of is the as yet unpub­ though it is probably an error. 3 lished, more theory-oriented article by Cardelli and To address this issue, statically typed Ian· Wegner [6], of which we became aware as this paper guages, such as Pascal, require programmers to ex­ was going to press.) However, we feel that beyond plicitly declare the types of all variables and formai I "cultural" differences the real goals pursued are the parameters, and enforce a statically checkable type same, so that it is fruitful to perform an in-depth compatibility constraint between actual and formal comparison of the technical solutions obtained on parameters in calls and between source and target both sides. in assignments. In such a language, the procedure to exchange the values of two variables of type T becomes:

GENERICITY procedure T_swap (x, y: 1in out T) is tT Genericity as offered by Ada is present in few begin other programming languages (examples include CLU t : = x; x : = y ; y : = t; [10] and LPG [2]), but is offered by several formal end swap specification languages, such as Z [1], Clear [5], OBJ2 [9], and LM [15]. A variant of this approach was Demanding that T be specified as a single type developed in connection with the language ML [16, averts type incompatibility errors, but has the un­ 7] and has been integrated into a number of func­ pleasant consequence of requiring a new procedure tional languages. declaration for each type for which a swap operation We shall concentrate on the Ada form, re­ is needed; in the absence of overloading, a different stricting ourselves to type genericity~ that is to say name must be assigned to each such procedure, for the ability to parameterize a software element (in example int_swap, str_swap and so on. Such mul­ Ada, a package or subprogram) by one or more types. tiple declarations lengthen and obscure programs. Generic parameters have other, less interesting uses The example chosen is particularly bad since all the in Ada, such as parameterized dimensions for arrays. declarations will be identical except for the two oc­ We shall distinguish between unconstrained currences of T. genericity, whereby no specific requirement is im­ Static typing may be considered too restrictive posed on generic parameters, and constrained ge­ here: the only real requirement is that the two actual nericity, whereby a certain structure is required. parameters passed to any call of swap should be of

14 Journal of Pascal, Ada, & Modula-2, March/Apri11988 Genericity versus Inheritance the same type; and that their type should also be portant applications of packages, and the only one applied to the declaration of the local variable t. considered in this article, is data abstraction: each A language with genericity provides a tradeoff package contains the implementation of a type and between too much freedom, as with untyped lan­ of the operations applicable to elements of that type. guages, and too much restraint, as with Pascal. In Ada packages may be declared with generic such a language, one may declare T as a generic type parameters. For example, the following package pro­ parameter to the swap procedure. In quasi-Ada, the vides stacks of elements of an arbitrary type T procedure may be declared as follows generic generiC type T is private; type T is private; package STACKS is procedure swap (x, y: 1in out T) is type STACK (size: POSITIVE) is tT record begin space: array (1 .. size ) of T; t : = x; x : = y ; y : = t; index: NATURAL end swap end record; function empty (s: 1in STACK) The only difference with real Ada is that we return BOOLEAN; have merged together, for ease of presentation, the procedure push (t: 1in T; s: 1in out STACK); two parts of an Ada subprogram declaration, header procedure pop (s: 1in out STACK); and body; their separation in Ada comes from a con­ function top (s: 1in STACK) return T; cern for information hiding, orthogonal to this end STACKS discussion. The generic ... clause introduces type param­ We have given only the public part ("specifi­ eters. By specifying T as "private", the writer of this cation") of the package; the package implementation procedure allows himself to apply to objects of type ("body"), which describes the subprogram bodies, must T (x, y, and t) operations available on all types, such be declared separately. For technical reasons having as assignment or comparison, and these only. to do with the problems of Ada compilation, the im­ A declaration such as the above does not ac­ plementation of the types supported by a package, tually introduce a procedure but rather a procedure such as STACK here, is given in the public part. For pattern; actual procedure instances are obtained by information hiding purposes, this implementation providing actual type parameters, as in may be given in the private clause of the public part, a kind of purgatory between specification and body; procedure int_swap is new swap (INTEGER); however we do not need this feature for the present procedure str _swap is new swap (STRING); discussion. As with generic subprograms, the above does etc. Now assuming that i andj are variables of type not define a package but a package pattern; actual INTEGER, sand t of type STRING, then of the fol­ packages may be obtained by instantiation, as in lowing calls package INT _STACKS is inLswap (i, j); new STACKS (INTEGER); str _swap (s, t); package STR_STACKS is inLswap (i, s); new STACKS (STRING); str _swap (s, j); str _swap (i, j); In a program unit that has access to both of these instances of STACKS, dot notation may be all but the first two are statically incorrect. used to distinguish between namesake elements: for More interesting than parameterized subpro­ example the type "stack of integers" will be denoted grams are parameterized packages. Ada packages by INT _STACKS .STACK, and the type "stack of (and their equivalents in other modular languages, strings" by STR _STACKS .STACK,' the correspond­ such as modules in Modula-2) are syntactical encap­ ing "push" procedures are INT _STACKS .push and sulations of groups of related entities such as sub­ STR _STACKS .push. programs, types and variables. One of the most im- We may note again the compromise that ge-

Journal of Pascal, Ada, & Modula-2, March/April1988 15 Genericity versus Inheritance

neric declarations achieve between typed and un­ with function "< =" (a, b: T) typed languages. STACKS provides a pattern for the return BOOLEAN is <>; function minimum (x, T) return T is 1 declaration of modules implementing stacks of ele­ y: ments of all possible types TJ while retaining the begin possibility to enforce type checks: for example it will if x < = y then return x; not be possible to push an integer onto a stack of else return y end if; strings. end minimum F Both examples above (swap and stack) evi­ dence a form of genericity which we call uncon­ The keyword with introduces generic param­ strained since there is no specific requirement on the eters representing subprograms, such as "< =". 1 types that may be used as actual generic parameters: We may instantiate minimum for any type, say Tl, such that there exists a function, say TI_ c one may swap the values of variables of any type and create stacks of values of any type-provided le, of type function (a, b: Tl) return BOOLEAN: v all the values in a given stack are of the same type. Other generic definitions, however, only make function T1 _minimum is sense if the actual generic parameters satisfy some new minimum (T1, T1_le); E conditions. We call this form constrained genericity. If, on the other hand, function Tl_le is in fact called "< = ," that is to say if its name and type match Constrained Genericity those of the corresponding formal subprogram, then [ it may be omitted from the list of actual parameters As in the unconstrained case, we consider two to the generic instance. For example, type INTEGER constrained examples, a subprogram and package. has a predefined "< = " function with the right type, 3 Assume we want a generic function subpro­ so that we can simply declare gram to compute the minimum of two values. We may use the pattern of swap: function int _minimum is I new minimum (INTEGER); generic type T is private; This use of default subprograms with match· function minimum (x, y: T) return T is ing names and types is made possible by the clause begin is <> in the declaration of the formal subprogram, if x < = y then return x; here "< =". Operator overloading, as permitted (and else return y end if; in fact encouraged) by the design of Ada, plays an end minimum essential role here: many different types may have a "< = " function. However, such a function declaration is not This discussion of constrained genericity for always meaningful: it should only be instantiated subprograms readily transposes to packages. As· for types T on which a comparison operator < = is sume we need a generic package for handling ma­ defined. In an untyped language, we might defer trices of objects of any type T, with matrix sum and checking of this property until run-time, but this is product as basic operations. Such a definition only not acceptable in a language that enhances security makes sense if type T has a sum and a product of through static typing . We need a way to specify that its own, and each of these operations has a zero ele­ type T must be equipped with the right operation. ment; these features of T will be needed in the im­ In Ada, this will be written by treating the plementation of matrix sum and product. The public operator < = as a generic parameter of its own. Syn­ part of the package may be written as follows tactically it is a function; as a syntactic facility, it is possible to invoke such a function using the usual generic infix form if it is declared with a name in double type T is private; quotes, here "< =." Again the following declaration zero: T; becomes legal Ada if the public part and implemen­ unity: T; tation are taken apart. with function" +" (a, b: T) return Tis <>; with function "*" (a, b: T) return Tis <>; generic package MATRICES is type T is private; type MATRIX (lines, columns: POSITIVE) is

16 Journal of Pascal, Ada, & Modula-2, March/April1988

:: Genericity versus Inheritance

array (1 .. lines, 1 .. columns) of T; trieved using the apostrophe notation as in function "+" (m 1, m2: MATRIX) mm'lines which in this case has value 100; return MA TRIX; • if a is an array, a'RANGE(i) denotes the range function "*" (m1, m2: MATRIX) of values in its i-th dimension; for example return MATRIX; ml'RANGE(l) above is the same as 1. .. ml'lines; end MATRICES; • if requested to multiply two dimension-wise in­ compatible matrices, the program raises an ex­ Typical instances of the package are ception; it does not execute the code that follows the raise instruction. The package should in­ package INT _MATRICES is clude code to handle the exception. new MATRICES (INTEGER, 0, 1); The minimum and matrix examples are rep­ package BOOL_MATRICES is new resentative of Ada techniques for constrained ge­ MATRICES (BOOLEAN, false, true, "or", "and"); nericity. They also show a serious limitation of these techniques: the fact that only syntactic constraints Again, actual parameters corresponding to may be expressed. All that a programmer may re­ formal generic subprograms (here" +" and "*") may quire is the presence of certain subprograms ("< =", be omitted for type INTEGER, which has matching "+ ", "*" in the examples) with given types; but the operations; but they must be included for BOOL­ declarations are meaningless unless some semantic EAN. (It is convenient to declare such parameters constraints are also satisfied. For example, mini­ last in the form list; otherwise keyword notation is mum only makes sense if "< =" is a total order re­ required in calls which omit the corresponding ac­ lation on T; and the MATRICES package should not tual parameters.) be instantiated for a type T unless the operations It is interesting here to show how the imple­ "+" and "*" not only have the right type (T x T ~ mentation part of such a package will look. It is T) but also give T the structure of a ring (associa­ enough to give one of the function bodies in this tivity, distributivity, zero a zero element for" +" and package; we take matrix product as an example. unity for "*", etc). To include such formal constraints, one has to package body MA TRICES is leave the realm of programming languages such as ...... other declarations ...... Ada for such specification languages as Clear and function "*" (m1, m2: T) is OBJ2 (the latter executable) or the experimental result: MATRIX (m1'lines, m2'columns); programming language LPG. begin if m1 'columns / = m2'lines then raise INCOMPATIBLE_SIZES; Implicit Genericity end if; for i 1in m1'RANG£(1) loop It is important to mention a form of genericity for j 1in m2'RANGE(2) loop quite different from the above Ada-style explicit pa­ result (i, j) : = zero; rameterization: the implicit polymorphism exem­ for k 1in m l' RANGE(2) loop plified by the work on the ML functional language result (i, j) : = [16, 7]. result (i, j) + m 1 (i, k) * m2 (k, j) This technique is based on the remark that end loop; explicit genericity, as seen above, places an unnec­ end loop; essary burden on the programmer, who must give end loop; generic types even when the context provides enough return result information to deduce a correct typing. It may be end "*"; argued, for example, that the very first version (/11) end MATRICES; given for procedure swap, with no type declaration, is acceptable as it stands: with adequate typing rules, Three comments are in order for the reader a compiler has enough information to deduce that x, not familiar with all the details of Ada: y, and t must have the same type. Why not then let • for a parameterized type such as MATRIX (lines, programmers omit type declarations when they are columns: POSITIVE), a variable declaration not strictly ne.eded conceptually, and have the com­ must provide actual parameters, e.g. mm: MA­ piler check that all uses of an identifier are consistent? TRIX (l00, 75); their values may then be re- This approach, sometimes called "unobtrusive

Journal of Pascal, Ada, & Modula-2, March/April1988 17 Genericity versus Inheritance

type checking", attempts to reconcile the freedom of fect which may be achieved in any language offering untyped languages with the security of typed ones. modular features and information hiding, such as Ada or Modula-2), but are such implementations. In 1 It has been elegantly implemented in ML and other functional languages. One may argue, of course, that other words, the defining equality of object-oriented some obtrusiveness may be useful; the redundancy languages is entailed by explicit type declarations may enhance program readability. Be it as it may, the question of Module == Type F explicit or implicit genericity is not directly con­ nected to the present discussion; for the purposes of This dogmatic identification of two apparently comparison with inheritance, both forms of gener­ distinct programming notions, one syntactic, the other icityare somehow equivalent. semantic, may appear too strict and indeed has some disadvantages. But it also gives object-oriented pro­ ( Without committing ourselves as to which form is best, we have chosen to rely on the explicit Ada gramming languages and the associated design form, which, for our study, has the obvious advan­ method a strong conceptual integrity, and provides tage that generic parameters stand out more visibly. powerful techniques for satisfying the software qual­ ity requirements mentioned above. E As an example of such a module-type, called a class in Eiffel as in Simula and many other object· INHERITANCE oriented languages, consider the following outline of an implementation of "special files" in the Unix sense, [ The inheritance technique was introduced in that is to say, files associated with devices 1967 by Simula 67 [3, 8, 11]. It has been widely imitated in other object-oriented languages. class DEVICE export 3 As with genericity, we will mostly introduce open, close, opened this technique through examples. We shall rely on feature the notation of the object-oriented language Eiffel open (file_descriptor: INTEGER) is I [13,12,14].* Much of the discussion would transpose do to other object-oriented languages; however Eiffel's emphasis on static typing, and its design as an object­ end; -- open .. oriented language for actual software engineering close is applications (as opposed to, say, artificial intelli­ do gence or exploratory programming) make it partic­ ularly suitable for this discussion. Only the elements end; -- close of Eiffel which are essential to this article are in­ opened: BOOLEAN troduced; more details may be found in the refer­ end -- class DEVICE ences quoted. The fundamental idea of inheritance is that This class is the implementation of an ab­ new software elements may be defined as extensions stract data type characterized by three "features," of previously defined ones, which should not have to open, close, and opened. There are two kind of fea­ be modified for the occasion. tures: attributes and routines. Routines, like open This concept blends particularly well with the and close here, are operations applicable to objects object-oriented approach, in which basic software of the class; routines are further divided into pro­ elements (modules) are implementations of abstract cedures which, as the two shown here, perform some data types: the extensions of software elements men­ actions, and functions (seen in later examples), which tioned above will then correspond to refinements of return a value. Attribute features, like opened here, hierarchies of abstract data types. are data elements associated with each object of the The basic tenet of object-oriented languages type. may be described as the idea that modules not only As a type, a class such as DEVICE may be contain abstract data type implementations (an ef- used to declare objects; their features may then be accessed through dot notation, as in

*Eiffel and the associated compiler and environment are products of Interactive Software Engineering, Inc., Goleta d1: DEVICE; i1: INTEGER (California). d1.Create;

18 Journal of Pascal, Ada, & Modula-2, March/April1988 Genericity versus Inheritance d1.open (fl); for example, DEVICE could have DISK as another if d1.opened then .... heir, with its own specific features (such as direct access read, etc.). In Eiffel, classes may also have Create is a universal procedure applicable to more than one parent: this is known as multiple all classes; it allocates the necessary space for an inheritance, a very powerful technique for reusabil­ object such as dl. Further initialization, if needed, ity, allowing the comb ination of more than one pre­ may be described in a possibly parameterized pro­ viously developed environment. Eiffel also intro­ cedure declared in the class with the name Create. duces the technique of "repeated inheritance," making Note that each routine always has, besides its it possible to inherit more than once from the same normal list of arguments, a special argument, the class. object to which the procedure is applied (dl in the From the module viewpoint, the ancestor re­ above calls). This is characteristic of object-oriented lation is a program structuring mechanism; from the languages: every operation is relative to a distin­ type viewpoint, it yields a rule on legal assignments. guished object. Within the class, unqualified feature The rule is simple: an assignment names implicitly refer to this object; the predefined name Current may be used when an explicit refer­ x:= y ence is needed. These comments account for the "type" aspect where x and yare of class types, is permitted if and of a class. From the "module" standpoint, it should only if the type of x is an ancestor of the type of y. be noted that the class is the only program struc­ Thus the above assignment is legal if, for example, turing facility of Eiffel; thus the above example use x has been declared as a device and y as a tape. This of DEVICE must be in some class, say C. A class may be explained by noting that the inheritance re­ such as C which declares entities (that is to say fea­ lation is really the "is-a" relation [4]: every tape is tures, routine parameters, or function results) of type a device, but every device is not a tape. DEVICE is said to be a client of DEVICE. The ex­ It sometimes happens that a feature of a class port clause lists the features of a class which are should be implemented differently in some de­ accessible to clients, in read-only mode for attributes scendants of the class. For example, there could be and execution mode for routines (here all features a special "open" mechanism for tape devices. Eiffel shown are exported). Since information hiding is not allows such redefinitions, as follows a concern for this discussion, we shall omit export ... clauses in the sequel. class TAPE inherit The notion of inheritance is a natural exten­ DEVICE redefine open sion to this basic framework. Assume we want next feature to define the notion of tape device. For our purposes, open (file_descriptor: INTEGER) is a tape unit has all the properties of devices, as rep­ do ..... special open for tape devices .... end; resented by the three features of class DEVICE, plus rewind is the ability to (say) rewind its tape. Rather than re­ do ...... end defining a new class from scratch, we may declare end -- class TAPE class TAPE as an extension of DEVICE, as follows This possibility must be seen in the light of class TAPE inherit DEVICE feature the assignment rule: if x is a device, then the call rewind is do ...... end x.open (f1) end -- class TAPE may be executed differently depending on the as­ With this declaration, objects of type TAPE signments performed on x before the call: for ex­ automatically possess (by "inheritance") all the fea­ ample, after x: = y, wherey is a tape, the tape version tures of DEVICE objects, plus their own (here re­ should be executed. Such feature redefinitions are wind). We say that TAPE is an heir to DEVICE, common in Eiffel programming, which also allows a which is a parent of TAPE. The "descendants" of a parameterless function to be redefined as an attri­ class are the class itself and the descendants of its bute (which is useful for changing representations heirs; the reverse notion is that of "ancestor." in program refinement). A class may of course have more than one heir; This facility characterizes the powerful brand

Journal of Pascal, Ada, & Modula-2, March/April1988 19 •

Genericity versus Inheritance

of polymorphism offered by object-oriented lan­ class FILE feature guages with inheritance: the same feature reference open (file_descriptor: INTEGER) is deferred end; may have several interpretations depending on the close is deferred end; actual form of the object at run-time. To achieve this end -- class FILE effect, many object-oriented languages have re­ nounced static type checking; Eiffel, however, is stat­ Descendants of FILE should provide actual ically typed (and the binding of feature names to definitions of open and close. The rules of the lan­ actual features is done statically whenever possible). guage prohibit application of these features to ob­ The remarkable benefits of the inheritance jects for which they might not be defined. technique with respect to reusability, extendibility, An interesting application of this technique and compatibility come from the fact that software is Ada or Modula-like separation between interface

( elements such as DEVICE are both usable as they and implementation of a module: although an Eiffel are (they may be compiled as part of an executable class is normally defined as a single piece, the effect program) and still amenable to extensions (if used of Ada's two-level declaration (specification and body) as ancestors of new classes). Thus, a compromise may be achieved by declaring a first class with de­ between usability and flexibility, fundamental for ferred features only, and a second one, heir to the E the qualities mentioned, is achieved. first, with the implementation of these features. This One more property of Eiffel, borrowed from technique has an important advantage over its Ada Simula, will be useful for the discussion below: de­ equivalent: it allows different implementations of ferred features (corresponding to Simula's "virtual the same feature to coexist in a single software system. [ procedures"). Deferred features correspond to oper­ ations that must be provided on all objects of a class, but whose implementation may only be given in par­ SIMULATING INHERITANCE WITH 3 ticular descendants of the class. GENERICITY Assume for example that, as under Unix, de­ I vices are a special kind of files; DEVICE should thus To compare genericity with inheritance, we be an heir to class FILE, whose other heirs may be shall study how, if in any way, the effect of each TEXT _FILE (itself with heirs NORMAL and DI­ feature may be simulated in a language offering the ,. RECTORY) and BINARY_FILE. Figure 1 shows other. the inheritance graph, a tree in this case. First consider a language such as Ada, offer­ Any file may be opened or closed; but how ing genericity but not inheritance. Can it be made these operations are performed depends on whether to achieve the effects of inheritance? the file is a device, a directory etc. Thus, at the FILE The easy part is the overloading. In a lan­ level we declare the corresponding procedures as de­ guage such as Ada or Algol 68 where the same sub­ ferred, giving only a header and handing over the program name may be reused as many times as needed burden of actual implementation to descendant classes provided it is applied too perands of different types, there is no difficulty in defining types such as TAPE, DISK, etc., each with its own version of open, close etc.:

procedure open (p: 1in out TAPE; descriptor: 1in INTEGER); procedure close (p: 1in out DISK); etc.

Provided the subprograms are distinguished by the type of at least one operand, as is the case here, no ambiguity will arise. Yet this solution falls short of providing true polymorphic entities as in languages with inheri­ tance, where, as discussed above, an operation may be executed differently depending on the form of its Fig. 1. Inheritance graph for files. operand at run-time (even though it is possible, at

20 Journal of Pascal, Ada, & Modula-2, March/April1988 Genericity versus Inheritance least in Eiffel, to check at compile time that the SIMULATING GENERICITY WITH operation is defined in all possible cases). A typical INHERITANCE example is the call d.close, which will be carried out differently after the assignments d : = di and d : = We now address the reverse problem: can we ta, where di is a DISK and ta a TAPE. The Ada-like achieve the effect of Ada-style genericity in an ob­ form of overloading does not provide anything like ject-oriented language with inheritance? this remarkable possibility. As before, we use Eiffel as our vehicle for ex­ The only feature of Ada which could be used pressing object-oriented techniques. As explained to emulate this property of object-oriented languages later, Eiffel does provide a generic parameter mech­ is in fact shared with Pascal and has nothing to do anism (included in the language as a result of the with overloading or genericity; it is the record with study reported here); but of course, since the object variant fields. We could for example define some­ of this section is to analyze how one may simulate thing like genericity with inheritance, we must temporarily refrain from using the Eiffel generic mechanism. The type DEVICE (unit: DEVICE_ TYPE) is reader should thus be warned that the solutions pre­ record sented in this section are substantially more complex ...... fields common to all device types ...... than those obtainable with full Eiffel, described in case unit is the next section. when tape = > ...... fields for tape devices The simulation turns out to be easier, or at least less artificial, for constrained genericity-a when disk = > ...... fields for disk devices ..... ; surprising result since unconstrained genericity is ...... other cases ...... ; conceptually simpler. Thus we begin with the con­ end case strained case. end record where DEVICE_TYPE is an enumeration type with Constrained Genericity: Overview elements tape, disk, etc. Then there would be a single version of each the procedures on devices (open, close The idea is to associate with a constrained etc.), each containing a case discrimination of the formal generic type parameter a class. This is a nat­ form ural thing to do since a constrained generic type may be viewed, together with its constraining operations, case d' unit is as an abstract data type. Consider for example the when tape = > ..... action for tape devices ..... ; generic clauses in our two constrained examples, when disk = > ..... action for disk devices ..... ; minimum and matrices: ...... other cases ...... ; end case generic type T is private; Such a solution, however, is unacceptable from with function" < =" (a, b: T) a software engineering viewpoint; it runs contrary return BOOLEAN is <>; to the criteria of extendibility, reusability, and com­ patibility. Not only does it scatter case discrimina­ generic tions (here on DEVICE_TYPE) all over the pro­ type T is private; gram; worse yet, it closes the set of possible choices: zero: T; as opposed to the Eiffel class DEVICE which can at unity: T; any time be used as parent or a new class, the Ada with function" +" (a, b: T) is <>; type DEVICE has a fixed list of variants, one for with function "*" (a, b: T) is <>; each element of the enumeration type DEVICE_ TYPE. To add a new case, one must change the dec­ We may view these clauses as the definitions laration of DEVICE, invalidating any program unit of two abstract data types say COMPARABLE and that relied on it. RING; the former is characterized by a comparison So the answer to the question posed at the operation "< = ", and the latter by features zero, un­ beginning of this section-can inheritance be sim­ ity, "+" and "*". ulated with genericity?-is no. In an object-oriented language, such types may

Journal of Pascal, Ada, & Modula-2, March/April1988 21 • ---

Genericity versus Inheritance

be directly represented as classes. We cannot define minimum (one: COMPARABLE; other: these classes entirely, for there is no universal im­ COMPARABLE): 1 plementation of "< = ", "+ ", etc.; rather, they are to COMPARABLE is be used as ancestors of other classes, corresponding -- Minimum of one and other to actual generic parameters. The deferred feature do ..... end mechanism of Eiffel provides exactly what is needed to express such classes In an object-oriented language, however, every F routine (Eiffel term for subprogram) appears in a class COMPARABLE feature class and is relative to the "current" object of that Ie (other: COMPARABLE): BOOLEAN class; thus it seems preferable to include minimum is deferred end in class COMPARABLE, argument one becoming ( end -- class COMPARABLE the implicit current object. The class becomes -- Ie corresponds to "< = "; -- there are no infix functions in Eiffel. class COMPARABLE feature Ie (other: COMPARABLE): BOOLEAN is class RING feature deferred E plus (other: RING) is deferred end; end; times (other: RING) is deferred end; minimum (other: COMPARABLE): COMPARABLE zero: RING; is unity: RING -- Minimum of current element and other [ end -- class RING do if Ie (other) then Result: = Current The comment made earlier about the lack of else Result: = other end semantic specification in Ada constrained genericity 3 end -- minimum would seem to apply here too: we have not specified end -- class COMPARABLE any of the required properties on le, plus, etc. Eiffel I does, however, permit the specification of such prop­ (The predefined variable Result contains the result erties in the form of preconditions and postcon­ to be returned by any function in which it appears; ditions on routines. Simple examples of this facility it is implicitly declared of the function's result type, will be given later on. here COMPARABLE.) To compute the minimum of The reader will also have noted that plus and two elements, we must declare them of some des­ times are defined here as procedures rather than cendant type of COMPARABLE. For example, we functions; the convention we will follow in the Eiffel may declare examples is that .plus (r 1) is an instruction that performs a side-effect on r, adding to its value the class INT _COMPARABLE inherit value ofr 1, rather than an expression returning the COMPARABLE sum of these values (and similarly for times). In con­ feature trast, the Ada operators" + " and "*" were functions. Ie (other: INT_COMPARABLE): BOOLEAN is The difference is not essential and we use procedures in Eiffel mainly for brevity. Subject to the following -- Is current element less than -- or equal to other? discussion, the examples may be changed into func­ tions, as in do Result: = value < = other. value end value; INTEGER; I~ -- Value associated with current element plus (other: RING): RING is deferred end; change _ value (new: T) is -- Make new the value associated -- with current element do value: = new end; Constrained Genericity: Subprograms end -- class INT_COMPARABLE

A subprogram such as minimum may now be To find the minimum of two integers, we may written by specifying its arguments to be of type now apply function minimum, not to arguments of COMPARABLE. Based on the Ada pattern, the func­ type integer, but to arguments of type INT _COM­ tion would be declared as PARABLE, say icl and ic2, as follows

22 Journal of Pascal, Ada, & Modula-2, March/April1988 ------

Genericity versus Inheritance

ie3 : = ie1.minimum (ie2) do Result: = general_minimum (other) To use the generic le and minimum functions, end; -- minimum we have to renounce direct references to integers, value: INTEGER; -- As above using INT _COMPARABLE entities instead; hence change _ value (new: T) is -- As above the need for attribute value and routine change_ do value: = new end; value to access and modify the associated integer end -- class INT _COMPARABLE values. We would similarly introduce heirs of COM­ We have used here the renaming mechanism PARABLE, say STR_COMPARABLE, REAL_ of Eiffel; the rename ... subclause of the inherit... COMPARABLE, and so on, for each type for which clause makes it possible to access the features of the a version of minimum is desired. ancestor class (COMPARABLE) even though they Of course, having to declare similar features are redefined in the descendant. Eiffel prohibits value and change_ value for all descendants of COM­ overloading of names within a class, so that re­ PARABLE is unpleasant. But by paying this rela­ naming is necessary to allow use of both sets of fea­ tively small price in terms of ease of program writ­ tures in the class. (Another use of renaming is in ing-renouncing the direct use of predefined types­ multiple inheritance, to remove name clashes when we seem to achieve the effect of genericity. features are inherited from more than one class). There is a hitch, however, if we are concerned What we have done is to redefine the header about static typing. We clearly want to disallow a of minimum-not its body, which remains the orig­ call such as inal one, made accessible under the name general_ minimum. This seems to take care of the static typ­ ie1.minimum (c) ing conflict, while introducing yet more complication. However, the careful reader will have noted where c is a COMPARABLE but not an INT _COM­ that a serious typing problem remains. The call to PARABLE. Function le has indeed been redefined general_minimum is correct with respect to its ar­ to accept only INT _COMPARABLE arguments; the gument other: since general_minimum (that is to rules of Eiffel permit such redefinition of an entity say, COMPARABLE's version of minimum, as given of a class in a descendant of that class, if the new on p. expects COMPARABLE objects, an entity like type is itself, as here, a descendant of the original other declared of the descendant type INT _COM­ type. But minimum has not been redefined; in fact PARABLE is an acceptable substitute under the as­ this is the whole point of the game: to make sure signment rule. But there is a problem with the result that minimum is a polymorphic feature, applicable of the function: general_minimum returns a COM­ to all kinds of "comparable" objects. So, regrettably, PARABLE whereas INT _COMPARABLE's version c is in fact a legal argument as used above. of minimum should return an INT _COMPARABLE. To ensure type consistency we must redefine minimum in INT _COMPARABLE so that its ar­ ie3 : = ic 1.minimurh (ie2) guments and result are of type INT _COMPARA­ BLE. The body of the routine does not change: only ic3 should be an INT _COMPARABLE; the assign­ its header has to be modified. The class declaration ment is illegal if the right-hand side returns just a may thus be rewritten as follows COMPARABLE. In fact, the permitted type combi­ nations in assignments are the inverse ones: the source class INT _COMPARABLE inherit should be of a descendant type from the target. COMPARABLE With what we have seen so far there is no way rename minimum as general_minimum; to resolve this issue other than by redefining mini­ redefine minimum mum completely-not only its header, but its body feature as well-so that it will indeed return an INT _COM­ Ie (other: INT _COMPARABLE): BOOLEAN is PARABLE. This of course defeats the whole purpose ...... As in 1271 ..... ; of genericity: a similar redefinition must be repeated minimum (other: INT _COMPARABLE): in each descendant of COMPARABLE, with all in­ INT _COMPARABLE stances of minimum identical except for the type is declarations of arguments and results. -- Minimum of current element and other We shall only be able to provide a satisfactory

Journal of Pascal, Ada, & Modula-2, March/April1988 23 Genericity versus Inheritance solution to this problem by introducing declaration To define the equivalent of the Ada generic by association. package instantiation

package BOOL_MATRICES is Constrained Genericity: Packages MATRICES (BOOLEAN, false, true, "or", "and"); The previous discussion transposes to pack­ ages. We use a class to represent the matrix abstrac­ we must declare the "ring" corresponding to booleans tion implemented in Ada by the MATRICES package class BOOL_RING inherit class MATRIX feature RING redefine zero, unity impl: ARRAY2 [RING]; freeze zero, unity feature entry (i: INTEGER: j: INTEGER): RING is value: BOOLEAN; -- Value of the (i, j) entry of the matrix change_value (b: BOOLEAN) is do Result: = impl.entry (i, j) end; -- Assign value b to current element enter (i: INTEGER; j: INTEGER; v: RING) is do value: = bend; -- Assign value v plus (other: BOOL_RING) is -- to entry (i, j) of the matrix -- Boolean addition: or do impl. enter (i, j, v) end; do change_value (value or other. value) end; plus (other: MATRIX) is times (other: BOOL_RING) is -- Add other to current matrix -- Boolean multiplication: and local do change_value (value and other. value) end; t1: RING zero: BOOL_RING is do -- Zero element for boolean addition ...... loop do ...... Ioop Result. Create; t1 : = entry (i, j); Result. change _ value (false) t1.plus (other. entry (i, j)); end; -- zero enter (i, j, t1) unity: BOOL_RING is end -- Zero element for boolean multiplication end do end; -- plus Result. Create; times (other: MATRIX) is Result. change _ value (true) -- Multiply current matrix by other end -- unity local ...... do ...... end end -- class BOOL_RING end -- class MATRIX Note that zero and unity are redefined as func­ Here ARRA Y2 [T] denotes a predefined Eiffel tions returning a value of type BOOL_RING. How­ class whose elements are two-dimensional arrays of ever, these are actually constant functions: the clause type T. Array types are treated in Eiffel as class freeze ... , not seen before, indicates that zero and types; the basic operations on an element a of type unity are evaluated just once and their values shared ARRA Y2 are a.entry (i, j), which returns the i, j among all instances of the class. This is how con­ entry of array a (that is to say, a [i, j] in standard stants of class types may be introduced in Eiffel. Pascal notation), and a.enter (i, j, v), which assigns How do we provide the equivalent to the Ada value v to this entry (that is to say, a [i, j] : = v). package instantiation for boolean matrices recalled Corresponding operations are declared above for above? The same reasoning that was applied to class matrices. COMPARABLE and function minimum prevents us We have left out some details (such as how from keeping MATRIX as it is if type checking is a the dimensions of a matrix are set) but outlined the concern: we want to make sure that an integer ele­ plus procedure, exhibiting the object-oriented form ment, say, may not be entered into a boolean matrix. of overloading: the internal call to plus is the oper­ To achieve this, we define an heir BOOL_MATRIX ation on RING, not MATRIX. Similarly, routines of MATRIX, where routines entry, enter, plus and * enter and entry are used in both their ARRAY2 and are redefined to act only on objects of type BOOL­ MATRIX versions. RING rather than any RING. As with minimum,

24 Journal of Pascal, Ada, & Modula-2, March/April1988 Genericity versus Inheritance only the headers of the routines have to be changed, coming less tolerable here as the dummy types do not their implementations; this is achieved as fol­ not correspond to any obviously relevant data lows, using again renaming to allow access to re­ abstraction. defined features of the parent class. Let us apply the previous technique to both our unconstrained examples, swap and stack, begin­ class BOOL_MATRIX ning with the latter. We need a class, say STACK­ inherit ABLE, describing objects that may be pushed onto MATRIX and retrieved from a stack. Since this is true of any rename entry as general_matrix _entry, object, this class has no property beyond its name: enter as general_matrix _enter, plus as general_matrix _plus, class STACKABLE end times as general_matrix_times redefine impl, entry, enter, plus, times We may now declare a class STACK, whose feature operations apply to STACKABLE objects impl: ARRAY2 [BOOL_RING]; entry (i: INTEGER; j: INTEGER): BOOl_RING is class STACK feature -- Value of the (i, j) entry of the matrix space: ARRAY [STACKABLE]; do Result: = general_matrix _entry (i, j) end; index; INTEGER; ... and similarly for enter, plus and times ... size: INTEGER; end --class BOOL_MATRIX empty: BOOLEAN is -- Is the stack empty? The reader may note the same problem for the do Result: = (index = 0) end; result of function entry as previously discussed for push (x: STACKABLE) is minimum: this result should be of type BOOL_RING, -- Add x on top of the stack but general_matrix_entry will only return a RING. require With the language features seen so far, all we can index < size do is to redefine the body of entry, making it a copy do of the body of general_ matrix_entry rather than a index : = index + 1; call to this routine; then the result will be of the space. enter (x, index) right type. Note that the problem only arises for end; -- push functions, so the other routines of the class are not top: STACKABLE is affected. -- Last element pushed This problem notwithstanding, the construc­ require tion achieves with inheritance the effect of con­ not empty strained genericity. The price to pay is a certain do heaviness in expression; note in particular that what Result: = space. entry (index) has been done for BOOL_MATRIX must be repeated end; -- top for any descendant of MATRIX representing a ge­ pop is neric instantiation, as INT _MATRIX, REAL_MA­ -- Remove last element pushed TRIX, etc. Furthermore, features value and change require _value must be declared anew in each descendant of not empty the associated class RING. We shall see later how do such heaviness may be removed. index: = index - 1 end; -- pop Create (m: INTEGER) is Unconstrained Genericity -- Create stack with space for m values do The mechanism for simulating unconstrained space.Create (1, m); genericity is the same; this case is simply seen as a size := m special form of constrained genericity, with an empty end .- Create set of constaints. As above, formal type parameters end -- class STACK will be interpreted as abstract data types, but here with no relevant operations. The technique works, The require ... clauses illustrate how routine but suffers from the heaviness mentioned above, be- preconditions (which must be satisfied by actual pa-

Journal of Pascal, Ada, & Modula-2, March/April 1988 25 ! II IiIj Genericity versus Inheritance space: ARRAY [INT _STACKABLE]; rameters upon entry to a routine) are written in push ((X: INT _ STACKABL~) is Eiffel. Postconditions and class invariants may also do general_stack _push (x) end; be expressed (in ensure ... and invariant... clauses). 11 This aspect of the language falls beyond the scope 1 etc. (the reader may complete this example based on ! of this discussion; see Meyer [12] for more details. STACK relies on the predefined class ARRA Y the MATRIX case). II The other unconstrained example, procedure I for one-dimensional arrays, whose main procedures j swap, may be treated along the same lines; a class are entry. enter, and Create; the latter takes two ar­ f! SWAPPABLE will be introduced. The treatment is guments and allocates the array with the values of i left to the reader. i these arguments as bounds. The Create procedure 1\ for stacks takes just one argument (the stack size). I ! To instantiate this definition for stacks of spe­ (I cific types, we apply the same techniques as above: GENERICITY AND INHERITANCE IN I EIFFEL \1 define descendants of STACKABLE, such as I We may draw the following conclusions from class INT _STACKABLE inherit I the previous discussion. EI STACKABLE • Inheritance is the more powerful mechanism, ! feature There is no way to provide a reasonable sim· ~l value: INTEGER; ulation with genericity. I change_value (n: INTEGER) is q -- Make n the value of the current element • The equivalent of generic subprograms or pack· ages may be expressed in a language with in· I do value : = n end end -- INT _STACKABLE heritance, but one does not avoid the need for certain spurious duplications of code. The extra 3 and similarly STR _STACKABLE, etc. verbosity is particularly hard to justify in the Here we run again into the typing problem case of unconstrained genericity, for which the I evidenced by minimum and BOOL_MATRIX. Stacks simulation mechanism is just as complex as for declared simply of type STACK cannot be statically the conceptually more difficult constrained case, guaranteed to contain only objects of a certain class • Type checking introduces difficulties in the use of "stackables," say INT _STACKABLE; and we have of inheritance to express generic objects, the problem of the type of the result returned by To address these issues, Eiffel offers a limited function top. In the following sequence form of genericity and the notion of declaration by association. (The specification language LM, asso· s: STACK; ins: INT _STACKABLE ciated with the M specification method [15], relies on a similar tradeoff.) s.Create (10); ins.Create; ins.change_value (50); s.push (ins); Simple Genericity ins := s.top Since unconstrained genericity is both the the last assignment has a left-hand side of type simpler case and the one for which the pure inher· INT _STACKABLE and a right-hand side of type itance solution is least acceptable, it seems adequate STACKABLE; this is typewise wrong even though to provide a specific mechanism for this case, distinct the code seems quite legitimate semantically (one from the inheritance mechanism. Consequently, Eif· pushes the value of a variable and retrieves it im­ fel classes may have unconstrained generic paramo mediately into the same variable). eters. A class may be defined as For both these reasons, it is necessary to do as in the previous examples, that is to say declare class C [T1, T2, .... , Tn] ..... heirs to STACK, such as INT _STACK, STR _STACK etc. Features of STACK will be redefined in each of where the parameters represent arbitrary types these classes, but only to adapt the types of their (simple or class). An actual use of the class will use arguments and, in the case of top, of the result. Thus actual type parameters, as in for example, INT _STACK will contain feature re­ definitions such as x: C [INTEGER, RING, ... , DEVICE]

26 Journal of Pascal, Ada, & Modula-2, March/April1988 Genericity versus Inheritance

We have in fact already encountered such par­ class RING [n feature ameterized classes: the basic classes ARRAY and plus (other: RING [T]) is deferred end; ARRA Y2 are naturally generic. It should also be times (other: RING [T]) is deferred end; noted (although the present paper is about concepts zero: RING [n; rather than implementation) that Eiffel compilation unity: RING [T]; techniques make it possible to generate a single ob­ value: T; ject module for a parameterized class, as opposed to change_value (new: T) is do value: = new end Ada techniques which treat generic packages as ma­ end -- class RING cros to be expanded anew for each instantiation. class MATRIX [n feature The examples of the previous sections provide impl: ARRAY2 [RING [n; obvious cases where generic parameters are useful. entry (i: INTEGER; j: INTEGER): RING [T] is For instance COMPARABLE becomes ... As before '" (see /31/); ... and similarly for enter, plus and times ... class COMPARABLE [T] feature end -- class MATRIX Ie (other: COMPARABLE [T]): BOOLEAN is deferred Note how the use of a generic parameter in end; two related classes, RING and MATRIX, makes it minimum (other: COMPARABLE [T]): possible to ensure type consistency (all elements of COMPARABLE [T] is a matrix will be of type RING [T] for the same T). ... As in /26/ ... ; As with COMPARABLE, the declarations of fea­ value: T; tures value and change_value have been factored change _ value (new: T) is do value : = new end out: they now appear in class MATRIX rather than end -- class COMPARABLE being repeated in all its descendants. In the unconstrained case, the need for dummy Here we see an immediate and important ben­ classes disappears; class STACKABLE and its heirs efit of generic parameters: we can solve almost com­ INT _STACKABLE, STR_STACKABLE, etc. are not pletely the problem of type checking by specifying needed any more, since STACK may be rewritten as that the arguments to le and minimum and the local variable m are of type COMPARABLE [T], for the class STACK [n feature same T as the class itself. Thus we rid ourselves of space: ARRAY [n; the necessity to redefine, at least formally, minimum index: INTEGER; for each descendant of COMPARABLE, which plagued size: INTEGER; our previous attempts. The generic parameter T also ... The rest of the class as in /351 allows us to lift the declarations of features value ... except that T is used in lieu of STACKABLE ... and change_value from the various descendants of end -- class STACK COMPARABLE to a single instance in COMPA­ RABLE itself. There is also no more need for classes such as However, we have not yet found a proper type INT _STACK, STRING _STACK, etc.; simply use for minimum's result, which remains a COMPA­ STACK [INTEGER], STACK [STRING], and so on. RABLE[T] even in descendants; more on this below. The typing problem for top disappears since the re­ To define INT _COMPARABLE all we have sult of this function is now simply of type T. to write now is A remarkable degree of simplification has been achieved. Auxiliary classes are not needed any more class INT _COMPARABLE inherit for unconstrained genericity. However, we do not COMPARABLE [INTEGER] introduce constrained genericity in the language: this feature feature would be redundant with the inheritance Ie (other: INT _COMPARABLE): BOOLEAN is mechanism. To provide the equivalent of a con­ -- Is current element less than or equal to strained formal generic parameter, we retain the other? technique introduced earlier: declare a special class do Result: = value < = other. value end whose features correspond to the constraints (that is end -- class INT _COMPARABLE to say, the with subprograms in Ada terminology), and declare any corresponding actual parameters as The other examples are treated similarly descendants of this class. Providing the class with

Journal of Pascal, Ada, & Modula-2, March/April1988 27 Genericity versus Inheritance

generic parameters simplifies its use and partly solves value: T; the type checking problem. change_value (new: T) is do value: = new end end -- class COMPARABLE 1 Declaration by Association Note how this device solves at once all the remaining type checking problems: not only are Ie Let us look more closely at the remaining part and minimum constrained to act, in all descendants of the type checking problem. Consider again class of COMPARABLE, on homogeneous entities (com. f COMPARABLE as defined last. Keeping in mind paring only integers with integers, strings with that COMPARABLE is intended for use as an ances­ strings, etc.); it also ensures that the result of min. tor for more specific classes, we do not really want imum is of the right type, that of its arguments. other (in both functions), m and the result of mini­ The same technique readily applies to the other ( mum to be of type COMPARABLE [T]: what is re­ cases. For example, RING becomes quired of these entities is to be of the type of the \. "current" entity, whatever this may be in a descen­ class RING [7] feature dant of COMPARABLE. When this type changes, plus (other: like Current) is deferred end; E we want the other entities to follow suit. times (other: like Current) is deferred end; This possibility is achieved in Eiffel through zero: like Current; the mechanism of declaration by association. Let a unity: like Current; class C contain a declaration of the form value: T; [ change_value (new: T) is do value: = new end x: 0 end -- class RING

3 where D is a class type. We may then declare another In contrast with the STACK case, we do need entity as here, because of the deferred procedures, to explicitly declare descendants of RING for various implemen­ y: like x tations of plus and times; for example

Such a declaration means the following: the class BaaL_RING inherit type of y is the same as the type of x; if x is redefined RING [BOOLEAN] in a descendant class of C as being of a class type redefine zero, unity D', which must be a descendant of D, theny will be freeze considered to have been redefined likewise. Note that zero, unity this is a purely static mechanism; it may be viewed feature as an abbreviation allowing the redeclaration of just ..... as in /321 ...... one from a group of related entities to stand for the end -- class BOOL_RING redeclaration of the whole group. When this distinguished entity, x above, is redeclared, it "drags" along all entities declared like Artificial Anchors it. We call it the anchor of the association. The anchor may be the current entity, as in For MATRIX, a small addition is necessary to ensure that all entities of type RING [T] are al­ y: like Current ways redefined consistently. When a group of entities are redefined to­ This readily applies to the previous example gether by association, one of the entities must serve as the anchor for the association. In the final versions class COMPARABLE [T] feature -- Contrast with obtained above for COMPARABLE and RING, the 139/ current element is the anchor. Ie (other: like Current): BOOLEAN is In MATRIX, the entities to be redefined are deferred end; of a type, namely RING, different from the current class. In such a case, the class usually contains a minimum (other: like Current): like Current is feature of the required type which can serve as an­ do ... see 1261 ... end; chor. For example, the basic Eiffel library [12] in-

28 Journal of Pascal, Ada, & Modula-2, March/April1988 Genericity versus Inheritance cludes an implementation oflinked lists through two full extent of both inheritance and Ada-like gener­ classes: LINKED_LIST [T] for the lists themselves icity would, as we think this discussion has shown, and LINKABLE [T] for list cells (a cell contains a result in a redundant and overly complex design; but value of type T and a reference to another cell). The including only inheritance would make it too diffi­ implementation of a list contains a reference first_ cult for programmers to handle the simple cases for element to the first cell of the list; first_element is which unconstrained genericity offers an elegant used as anchor for other LINKABLE entities of class expression mechanism, like in the stack example. LINKED _LIST and redefined in descendants of Thus we have put the borderline at uncon­ LINKED _LIST, such as the classes for two-way lists strained genericity. Eiffel classes may have uncon­ and trees (both viewed as special cases of linked strained generic parameters; constrained generic pa­ lists). rameters are treated through inheritance. Class MATRIX, however, has no feature of Declaration by association completes this ar­ type RING; the reason is that "ring" elements are chitecture by allowing for completely static type entered into the matrix indirectly, as arguments to checking, while retaining the necessary flexibility. procedure entry. Thus, we cannot avoid the need for We hope to have achieved in this design a good a dummy feature of type RING serving as anchor: balance between the facilities offered by two impor­ tant but very different techniques for the imple­ class MATRIX [T] freeze anchor feature mentation of extendible, compatible, and reusable soft­ anchor: RING [T]; ware. imp I: ARRAY2 [like anchor]; entry (i: INTEGER; j: INTEGER): like anchor is .. , As before ... (/31 I) ... ; Acknowledgments enter (i: INTEGER; j: INTEGER; v: like anchor) is .. , As before ... ; This work benefited from comments by Vin­ plus (other: like Current) is ... As before ... ; cent Cazala and was done in part as the author was times (other: like Current) is ... As before ... ; with the University of California, Santa Barbara. end -- class MA TRIX

(Listing anchor in the freeze clause avoids the waste References of run-time space that would result from physically storing an anchor within each object of the class.) 1. Jean-Raymond Abrial, Stephen A. Schuman, and Ber­ Here too specialized classes must be declared for var­ trand Meyer, "A Specification Language," in On the ious generic instances of MATRIX. However, the Construction ofPrograms, ed. R. McNaughten and R.C. declarations are now trivial: all that needs to be done McKeag, Cambridge University Press, 1980. is to redefine anchor. For example 2. Didier Bert, "Manuel de Reference du Langage LPG, Version 1.2," Rapport R-408, IFIAG, IMAG Institute (Grenoble University), Grenoble, December 1983. class BaaL_MATRIX inherit 3. Graham Birtwistle, Ole-Johan Dahl, Bjorn Myrhaug, MATRIX [BOOLEAN] redefine anchor and Kristen Nygaard, SimulaBegin, Studentliteratur feature and Auerbach Publishers, 1973. anchor: BaaL_RING 4. Ronald J. Brachman, "What IS-A and isn't: An Anal­ end -- class BOOL_MA TRIX ysis of Taxonomic Links in Semantic Networks," Com­ puter(lEEE), vol. 16, no. 10, pp. 67-73, October 1983. Such a redeclaration closely models the correspond­ 5. Rod M. Burstall and Joe A. Goguen, "An Informal ing Ada package instantiation (/12/). Introduction to Specifications using Clear," in The Correctness Problem in Computer Science, ed. R.S. Boyer and J.S. Moore, pp. 185-213, Springer-Verlag, New CONCLUSION York,1981. 6. Luca Cardelli and Peter Wegner, "On understanding Genericity and inheritance are two important Types, Data Abstraction and Polymorphism," Com­ techniques towards the software quality goals men­ puting Surveys (to appear). tioned at the beginning of this article. We have tried 7. Luca Cardelli, "Basic Polymorphic Typechecking," to show which of their features are equivalent, and AT&T Bell Laboratories Computing Science Techni­ which are complementary. cal Report, 1984, 1986. (Revised version, to appear). Providing a programming language with the 8. Ole..Johan Dahl, Bj¢rn Myrhaug, and Kristen Ny-

Journal of Pascal, Ada, & Modula-2, March/April1988 29 Genericity versus Inheritance

gaard (Simula) Common Base Language, Norsk Reg­ Software Engineering, Version 2.1, August 1986, No. nesentral (Norwegian Computing Center), Oslo, Feb­ vember 1985 (Revised, August 1986). ruary 1984. 13. Bertrand Meyer, "Eiffel: Programming for Reusabilitv 1 9. K. Futatsugi, Joseph A. Goguen, Jean-Pierre Jouan­ and Extendibility," ACM Sigplan Notices, 1987. (T~ naud, and Jose Messeguer, "Principles of OBJ2," in appear) Proceedings o{ the 1985 ACM Symposium on Princi­ 14. Bertrand Meyer, Object-oriented Software Construc_ ples of Programming Languages, vol. 12, pp. 52-66, tion, 1987. (To appear) 1985. 15. Bertrand Meyer, "M: A System Description Method" 10. Barbara H. Liskov, R. Atkinson, T. Bloom, E. Moss, Technical Report TRCS85-15, University of Califo~. J.C. Schaffert, R. Scheifler, and Alan Snyder, CLU nia, Santa Barbara, Computer Science Department Reference Manual, Springer-Verlag, Berlin-New York, May 1985. ' 1981. 16. Robin Milner, "A Theory of Type Polymorphism in 11. Bertrand Meyer, "Quelques concepts importants des Programming," Journal of Computer and System Sci. ( langages de programmation modernes et leur expres­ ences, vol. 17, pp. 348-375, 1978. sion en Simula 67," Bulletin de la Direction des Etudes 17. Rishiyur S. Nikhil, "Practical Polymorphism," in v et Recherches d'Electricite de France, Serie C (In{or­ Functional Programming Languages and Computer matique), no. 1, pp. 89-150, Clamart (France), 1979. Architecture, Nancy (France), 16-19 September 1985 E Also in GROPLAN 9, AFCET, 1979. Lecture Notes in Computer Science 201, ed. Jean-Pie~ 12. Bertrand Meyer, Ei{{el: a Language {or So{tware En­ Jouannaud, pp. 319-333, Springer-Verlag, Berlin-New gineering, Technical Report TRCS86-4, Interactive York, 1985.

[

3

T

I

30 Journal of Pascal, Ada, & Modula-2, March/April1988