Generic Programming David R. Mussery Rensselaer Polytechnic Institute Computer Science Department Amos Eaton Hall Troy, New York 12180 Alexander A. Stepanov Hewlett{Packard Laboratories Software Technology Laboratory Post Oce Box 10490 Palo Alto, California 94303{0969 Abstract Generic programming centers around the idea of abstracting from concrete, efcient algorithms to obtain generic algorithms that can be combined with dierent data representations to produce a wide variety of useful software. For example, a class of generic sorting algorithms can be dened which work with nite sequences but which can be instantiated in dierent ways to produce algorithms working on arrays or linked lists. Four kinds of abstraction|data, algorithmic, structural, and representational| are discussed, with examples of their use in building an Ada library of software components. The main topic discussed is generic algorithms and an approach to their formal specication and verication, with illustration in terms of a partitioning algorithm such as is used in the quicksort algorithm. It is argued that generically programmed software component libraries oer important advantages for achieving software productivity and reliability. This paper was presented at the First International Joint Conference of ISSAC-88 and AAECC-6, Rome, Italy, July 4-8, 1988. (ISSAC stands for International Symposium on Symbolic and Algebraic Computation and AAECC for Applied Algebra, Algebraic Algorithms, and Error Correcting Codes). It was published in Lecture Notes in Computer Science 358, Springer-Verlag, 1989, pp. 13-25. y The rst author's work was sponsored in part through a subcontract from Computational Logic, Inc., which was sponsored in turn by the Defense Advanced Research Projects Agency, ARPA order 9151. The views and conclusions contained in this document are those of the authors and should not be interpreted as representing the ocial policies, either expressed or implied, of the Defense Advanced Research Projects Agency, the U.S. Government, or Computational Logic., Inc. i ii CONTENTS Contents 1 Introduction 2 Classication of Abstractions 2.1 2.2 2.3 2.4 Data abstractions : : : : : : : Algorithmic abstractions : : : Structural abstractions : : : : Representational abstractions : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 3 Algorithmic Abstractions 4 A Generic Partition Algorithm 5 Abstract Algorithm Specication and Verication 5.1 5.2 5.3 5.4 Basic specication of Partition : : : : : : : : : : : : Obtaining a sequence satisfying Test : : : : : : : : Correctness when Swap is an assignment operation Correctness of the body of Partition : : : : : : : : : 6 Conclusion : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 1 2 2 3 3 4 4 7 8 11 12 14 15 16 1 1 Introduction By generic programming, we mean the denition of algorithms and data structures at an abstract or generic level, thereby accomplishing many related programming tasks simultaneously. The central notion is that of generic algorithms, which are parameterized procedural schemata that are completely independent of the underlying data representation and are derived from concrete, e cient algorithms. The purpose of this paper is to convey the key ideas of generic programming, focusing mainly on establishing a methodological framework that includes appropriate notions of algorithm and data abstractions, and what it means to formally specify and verify such abstractions. We present the issues relating to generic algorithms mainly in terms of a single example, a Partition algorithm such as is used in quicksort, but we will also allude to a large collection of examples we have developed in Ada as part of an Ada Generic Library project 6], 7]. The structure of the library is designed to achieve a much higher degree of modularity than has been found in previous libraries, by completely separating data representations from algorithmic and other data abstraction issues. Some of our goals are in common with the \parameterized programming" approach advocated by J. Goguen 4], but the most fundamental dierence is that Goguen mainly addresses metaissues|namely, how to manipulate theories|while our primary interest is in building useful theories to manipulate. The notion of generic algorithms is not new, but we are unaware of any similar attempt to structure a software library founded on this idea. The Ada library developed by G. Booch 1], for example, makes only very limited use of generic algorithms. Booch has developed an interesting taxonomy of data structures and has populated it with many dierent abstract data types, but the implementations of these data types are for the most part built directly on Ada's own data structure facilities rather than using other data abstractions in the library i.e., there is very little layering of the implementations. (Some use of generic algorithms and layering is described for list and tree structure algorithms, but almost as an afterthought in a chapter on utilities). In fact, most work on development of abstraction facilities for the past decade or more has focused on data abstraction 2], 9]. Algorithmic abstraction has received little attention, even in the more recent work on object oriented programming. Most work on procedural abstraction has been language- rather than algorithm-oriented, attempting to nd elegant and concise linguistic primitives our goal is to nd abstract representations of e cient algorithms. As an example of algorithmic abstraction, consider the task of choosing and implementing a sorting algorithm for linked list data structures. The merge sort algorithm 2 2 CLASSIFICATION OF ABSTRACTIONS can be used and, if properly implemented, provides one of the most e cient sorting algorithms for linked lists. Ordinarily one might program this algorithm directly in terms of whatever pointer and record eld access operations are provided in the programming language. Instead, however, one can abstract away a concrete representation and express the algorithm in terms of the smallest possible number of generic operations. In this case, we essentially need just four operations: Next and Set Next for accessing the next cell in a list, Is End for detecting the end of a list, and Test, a binary predicate on (the data in) cells. For a particular representation of linked lists, one then obtains the corresponding version of a merge sorting algorithm by instantiating the generic access operations to be subprograms that access that representation. We believe it is better whenever possible to give programming examples in a real language rather than using pseudo-language (as is so frequently done). Although we do not argue that Ada is perfect for expressing the programming abstractions we have found most useful, it has been adequate in most cases and it supports our goal of e ciency through its compile time expansion of generics and provision for directing that subprograms be compiled inline. For numerous examples of the use of generic programming techniques in the Scheme language, and a brief discussion of the relative merits of Ada and Scheme for this type of programming, see 5]. 2 Classication of Abstractions We discuss four classes of abstractions that we have found useful in generic programming, as shown in Table 1, which lists a few examples of packages in our Ada Generic library. Each of these Ada packages has been written to provide generic algorithms and generic data structures that fall into the corresponding abstraction class. 2.1 Data abstractions Data abstractions are data types and sets of operations dened on them (the usual denition) they are abstractions mainly in that they can be understood (and formally specied by such techniques as algebraic axioms) independently of their actual implementation. In Ada, data abstractions can be written as packages which dene a new type and procedures and functions on that type. Another degree of abstractness is achieved by using a generic package in which the type of elements being stored is a generic formal parameter. In our library, we program only a few such data abstractions directly|those necessary to create some fundamental data representations and dene how they are implemented 2.2 Algorithmic abstractions 3 Data Abstractions System Allocated Singly Linked Data types with operations User Allocated Singly Linked dened on them fInstantiations of representational abstractionsg Algorithmic Abstractions Sequence Algorithms Families of data abstractions Linked List Algorithms with common algorithms Vector Algorithms Structural Abstractions Singly Linked Lists Intersections of Doubly Linked Lists algorithmic abstractions Vectors Representational Abstractions Double Ended Lists Mappings from one structural Stacks abstraction to another Output Restricted Deques Table 1: Classication of Abstractions and Example Ada Packages in terms of Ada types such as arrays, records and access types. Most other data abstractions are obtained by combining existing data abstraction packages with packages from the structural or representational classes dened below. 2.2 Algorithmic abstractions These are families of data abstractions that have a set of e cient algorithms in common we refer to the algorithms themselves as generic algorithms. For example, in our Ada library there is a package of generic algorithms for linked-lists and a more general package of sequence algorithms whose members can be used on either linked-list or vector representations of sequences. The linked-list generic algorithms package contains 31 dierent algorithms such as, for example, generic merge and sort algorithms that are instantiated in various ways to produce merge and sort subprograms in structural abstraction packages such as singly-linked lists and doubly-linked lists. We stress that the algorithms at this level are derived by abstraction from concrete, e cient algorithms. 2.3 Structural abstractions Structural abstractions (with respect to a given set of algorithmic abstractions) are also families of data abstractions: a data abstraction A belongs to a structural abstraction 4 3 ALGORITHMIC ABSTRACTIONS S if and only if S is an intersection of some of the algorithmic abstractions to which A belongs. An example is singly-linked-lists, the intersection of sequence- , linked-list-, and singly-linked-list-algorithmic abstractions. It is a family of all data abstractions that implement a singly-linked representation of sequences (it is this connection with more detailed structure of representations that inspires the name \structural abstraction"). Note that, as an intersection of algorithmic abstractions, such a family of data abstractions is smaller than the algorithm abstraction classes in which it is contained, but a larger number of algorithms are possible, because the structure on which they operate is more completely dened. Programming of structural abstractions can be accomplished in Ada with the same kind of generic package structure as for generic algorithms. The Singly Linked Lists package contains 66 subprograms, most of which are obtained by instantiating or calling in various ways some member of the Sequence Algorithms package or one of the linkedlist algorithms packages. In Ada, to place one data abstraction in the singly-linked-lists family, one instantiates the Singly Linked Lists package, using as actual parameters a type and the set of operations on this type from a data abstraction package such as System Allocated Singly Linked that denes an appropriate representation. 2.4 Representational abstractions These are mappings from one structural abstraction to another, creating a new type and implementing a set of operations on that type by means of the operations of the domain structural abstraction. For example, stacks can easily be obtained as a structural abstraction from singly-linked-lists. Note that what one obtains is really a family of stack data abstractions, whereas the usual programming techniques give only a single data abstraction. The following sections give more detailed examples of algorithmic abstractions. Further discussion and examples of data, structural, and representational abstraction may be found in 6]. 3 Algorithmic Abstractions As an example of generic algorithms, we consider the sequence algorithmic abstraction: diverse data abstractions which can be sequentially traversed. These data abstractions belong to numerous dierent families: singly-linked lists, doubly-linked lists, vectors, trees, and many others. There are many algorithms that make sense on all of them and require 5 only a few simple access operations for their implementation: nd an element, accumulate values together (by + or , for example), count elements satisfying some predicate, etc. The solution that has been taken in Common Lisp 8] is to index all kinds of sequences by natural numbers. So the Common Lisp generic find function always returns a natural number, which is not particularly useful on linked lists. In the generic programming approach, we use generic indexing by a generic formal type, Coordinate. Coordinate is instantiated to type Natural for vectors for linked lists, however, cells themselves can serve as Coordinate values. A generic Find can thus return a Coordinate value that can be used to reference the located element directly. The intended semantics for Coordinate is that there are functions  Initial from Sequence to Coordinate,  Next from Coordinate to Coordinate,  Is End from Sequence Coordinate to Boolean, and  Ref from Sequence Coordinate to a third type Element, such that for any sequence S there are a natural number N (called the length of S ) and coordinates I0 I1 : : :  IN such that  I0 = Initial(S ) and Ii = Next(Ii;1) for i = 1 : : :N   the elements of S are given by Ref(S I0) Ref(S I1) : : : Ref(S IN ;1)  Is End(S Ii) is false for i = 0 1 : : :  N ; 1, and true for i = N . We further assume that each of the functions Initial, Next, Ref, and Is End is a constant time operation. It is important that Ref provides constant time access, so that after Find returns the coordinate it is possible to access the data without any additional traversal of the sequence. Thus, for example, one could not use natural numbers as coordinates when the sequences are linked lists. In Ada we can write: generic type Sequence is private type Coordinate is private type Element is private with function Initial(S : Sequence) return Coordinate 6 3 ALGORITHMIC ABSTRACTIONS with function Next(C : Coordinate) return Coordinate with function Is_End(S : Sequence C : Coordinate) return Boolean with function Ref(S : Sequence C : Coordinate) return Element package Sequence_Algorithms is -- definitions of sequence algorithms such as -- Count, Find, Every, Notany, Some, Search e.g., generic with function Test(S : Sequence C : Coordinate) return Boolean procedure Find(S : Sequence Result : out Coordinate Is_Found : out Boolean) end Sequence_Algorithms We have made Find a procedure instead of a function so that the case in which an element satisfying Test is not found does not require some \extra" coordinate value to be returned such an extra value might not exist for some instances of the coordinate type. Note also that Find is a generic procedure in addition to forming an instance of the Sequence Algorithms package, the programmer would also create particular instances of Find in which some particular test, such as equality to a particular value, would be substituted for Test. The body of Find could be expressed as follows: package body Sequence_Algorithms is -- among other things procedure Find(S : Sequence Result : out Coordinate Is_Found : out Boolean) is Current : Coordinate Flag : Boolean begin Current := Initial(S) while not Is_End(S, Current) loop if Test(S, Current) then Result := Current Is_Found := True end if Current := Next(Current) end loop Result := Current Is_Found := False return 7 end Find end Sequence_Algorithms It should be noted that not every possible data abstraction which contains a set of elements can be a member of the sequence algorithmic abstraction. Some data abstractions do not contain an explicit coordinate type (stacks, or queues, for example). Intuitively speaking, the intended data abstractions are those which can be iterated through without sideeects, and where there is a coordinate type which can be used to represent the current position in a manner allowing constant time access. 4 A Generic Partition Algorithm Another limitation of sequence algorithms as we have dened them in the previous section is that they allow only for one-directional traversals of sequences. There are several algorithms which require a bidirectional traversal of sequences by two variables of the type Coordinate advancing towards each other. If we assume there is a Prev operation such that Prev(Next(I )) = Next(Prev(I )) = I , and a Swap operation for exchanging elements, then we can obtain generic implementations of such procedures as Reverse and Partition (as in quicksort). We examine the Partition algorithm in particular as a more detailed example of the issues that arise with programming and reasoning about algorithmic abstractions. In Ada, we could provide such algorithms in a generic package, generic type Sequence is private type Coordinate is private with function Next(I : Coordinate) return Coordinate with function Prev(I : Coordinate) return Coordinate with procedure Swap(S : in out Sequence I, J : Coordinate) package Bidirectional_Sequence_Algorithms is -- for example: procedure Reverse(S : in out Sequence) generic with function Test(S : Sequence C : Coordinate) return Boolean procedure Partition(S : in out Sequence 8 5 ABSTRACT ALGORITHM SPECIFICATION AND VERIFICATION F, L : in Coordinate Middle : out Coordinate Middle_OK : out Boolean) end Bidirectional_Sequence_Algorithms which could be used along with the Sequence Algorithms package to construct collections of algorithms for dierent kinds of linear lists. In this package we do not need to have Element and Ref as generic parameters, since algorithms such as Reverse and Partition do not directly refer to them. To describe the Partition algorithm informally (a formal treatment follows in Section 5), we speak of Next(I ) giving a \larger" coordinate than I and Prev giving one that is smaller and we also speak of elements that satisfy Test as \good" and those that don't as \bad." Provided Swap(S I J ) exchanges the elements with coordinates I and J and leaves all other elements of S unchanged, the Partition algorithm rearranges the elements of S between those with coordinates F and L so that all of the good elements come rst, followed by all the bad elements. Middle = M is computed as a coordinate between F and L (inclusive) such that all of the elements with smaller coordinates are good and all elements with greater coordinates are bad the M -th element is good if and only if Middle OK is true. Middle OK is needed because for N elements there are N + 1 possibilities for the boundary between good and bad elements, but we are guaranteed of having only N coordinate values in general we cannot assume the existence of coordinate values outside the range from F to L. This complication does not arise with the usual concrete partition algorithm in which coordinates are integers, since one could use values F ; 1 or L + 1 for M . This is an example of the extra care that must be taken in expressing an algorithm at a more abstract level. The Partition algorithm can be expressed in Ada as shown in Figure 1. A somewhat shorter implementation could be achieved in which calls to Test with the same arguments might be repeated, but since Test is a generic parameter we must be careful to avoid such redundant calls, since one might instantiate the algorithm with a Test that is fairly expensive to compute. 5 Abstract Algorithm Specication and Verication The main idea of our approach to specication and verication of a generic algorithm is similar to classical program verication techniques, e.g., Dijkstra's idea of weakest pre- 9 procedure Partition(S : in out Sequence F, L : in Coordinate Middle : out Coordinate Middle_OK : out Boolean) is First : Coordinate := F Last : Coordinate := L begin loop loop if First = Last then Middle := First Middle_OK := Test(S, First) return end if exit when not Test(S, First) First := Next(First) end loop loop exit when Test(S, Last) Last := Prev(Last) if First = Last then Middle := First Middle_OK := False return end if end loop Swap(S, First, Last) First := Next(First) if First = Last then Middle := First Middle_OK := False return end if Last := Prev(Last) end loop end Partition Figure 1: Body of Partition Algorithm 10 5 ABSTRACT ALGORITHM SPECIFICATION AND VERIFICATION conditions 3], in which one attempts to obtain a strong statement about the result of a computation while making as few assumptions as possible about its initial conditions. In discussing preconditions of ordinary, nongeneric algorithms, the assumptions one makes about the operations in terms of which the algorithm is expressed are xed, since the operations themselves are xed. But for generic algorithms we want to make these operations generic parameters and vary the assumptions about them our goal is both to consider a variety of possible postconditions and to maximize the number of dierent models (abstract data types) under which an an algorithm attains a given postcondition. In terms of proof theory, we want to consider how to prove various postconditions under a variety of assumptions about the generic parameters, so that later we can easily prove correctness of a wide variety of instances. It appears that the best approach is to build up the specications and the verication lemmas in stages, just as we build up algorithmic capabilities in layers. In fact we will nd it advantageous to have even more layering in the specications and proofs than in the construction of the algorithms. We introduce the main ideas in terms of the Partition algorithm given in the previous section. What are the minimal assumptions we need to make about the generic parameters in terms of which Partition is programmed, namely the Sequence and Coordinate types and the Next, Prev, and Swap operations? If we want to use the algorithm only for partitioning in the usual sense, and the only use of abstraction is in the use of the Coordinate type rather than a more specic integer type (for an array version) or pointer type (for a linked list version), then we could carry out the specication and proof in one step in which we make strong assumptions about the generic parameters. Instead, however, we begin with weaker assumptions about these generic parameters, and obtain a lemma about the results of Partition that enables us to deal with less conventional instances of partitioning. For example, suppose that we are only interested in the part of the output consisting of elements that satisfy Test i.e., we do not need to process the elements that don't satisfy it. Then we can obtain a more e cient partitioning algorithm by using for Swap(S I J ) an operation that just performs the assignment of the element with the J -th coordinate to the I -th coordinate. If we had made a stronger assumption about Swap, that it exchanges two elements in the sequence, then the theorem about the generic algorithm would not be applicable to this instance. Instead, we carry out the specication and proofs in layers, one of which allows us to \tap in" at the level we need to verify the second kind of Partition, while the rst, more usual, kind can be veried when additional assumptions are made about Swap and combined with the lemma stated at the rst level. First, we assume that associated with the Coordinate type there is a predicate < 5.1 Basic specication of Partition 11 which is a well-founded partial ordering1 on Coordinate, dened by I < J  9N (N > 0 and J = NextN (I )): For coordinates F and L, where F  L, we dene Coordinate Range(F L) = fNexti (F ) : 0  i  N g where N is the smallest integer such that NextN (F ) = L. Note that Previ (L) = NextN ;i (F ): 5.1 Basic specication of Partition Initially we make only a weak assumption about Swap: Swap Assumption 1 If I J 2 Coordinate Range(F L), then Swap(S I J ) computes S1 such that for all K 2 Coordinate Range(F L) ; fI J g, Test(S1 K ) = Test(S K ): The reason that we can get by with such a weak assumption is simply that we express the result Partition computes as equivalent to that produced by a straight-line sequence of calls of Swap. The specication asserts the existence of two sequences of coordinate values that serve as arguments to the Swap calls and constrains the relationship between these values. Syntactic Specication procedure Partition(S F, L Middle Middle_OK : : : : in out Sequence in Coordinate out Coordinate out Boolean) Formal Semantics and Prev are assumed to obey the relations discussed above and to have no side eects Swap is assumed to obey Swap Assumption 1. With inputs S = S F = F L = L, with F  L according to the partial ordering relation < dened above, Partition outputs S = S1  Middle = M Middle OK = B such that: Next Note that < is not generic parameter of the package because it is not used in expressing the algorithms themselves, as it would be expensive to implement for, say, doubly-linked lists. 1 12 5 ABSTRACT ALGORITHM SPECIFICATION AND VERIFICATION 1. M 2 Coordinate Range(F L), 2. There are two sets of coordinates Accept Reject = = ( (F M ) if B is true (F M ) ; fM g if B is false Coordinate Range(F L) ; Accept Coordinate Range Coordinate Range and a nonnegative integer n and two Coordinate sequences I1 : : :  In and J1 : : : Jn such that (a) F  I1 < : : : < In  M  Jn < : : : < J1  L (b) For k = 1 : : :  n, Test(S Ik) is false while Test(S Jk ) is true. (c) For P 2 Accept ; fI1 : : : Ing, Test(S P ) is true. (d) For P 2 Reject ; fJ1 : : :  Jng, Test(S P ) is false. (e) S1 is the nal value of S computed by Swap(S, I1, J1) ... Swap(S, In, Jn ) The fact that the coordinate type is abstract compels taking considerable care in this specication to avoid the mention of coordinate values that might not exist e.g., we write Coordinate Range(F M ) ; fM g instead of Coordinate Range(F Prev(M )). Later we show how the above input-output specication can be proved as a lemma, by annotating the algorithm with assertions and using the inductive assertions method. For now, we consider how to combine this specication with additional assumptions about Swap in order to draw stronger conclusions about the output of Partition. We add these assumptions one at a time, thereby obtaining several useful lemmas that apply to dierent instances of Partition. 5.2 Obtaining a sequence satisfying Test By making a second assumption about Swap, we can draw a stronger conclusion about Partition. Swap Assumption 2 If I J 2 Coordinate Range(F L), then Swap(S I J ) computes S1 such that Test(S1  I ) = Test(S J ): This can be combined with the basic Partition specication to deduce: 5.2 Obtaining a sequence satisfying Test 13 Partition Lemma 1 If Swap satis es Swap Assumptions 1 and 2, then the sequence S1 computed by Partition satis es Partition Property 1 For all K 2 Accept, Test(S1 K ) is true. Similarly, we can introduce Swap Assumption 3 If I J 2 putes S1 such that (F L), then Coordinate Range (S I J ) com- Swap (S1 J ) = Test(S I ): Test and this can be combined with the basic Partition specication to deduce: Partition Lemma 2 If Swap satis es Swap Assumptions 1 and 3, then the sequence S1 computed by Partition satis es Partition Property 2 For all K 2 Reject, Test(S1 K ) is false. Partition Lemma 3 If Swap satis es Swap Assumptions 1, 2 and 3, then the sequence S1 computed by Partition satis es Partition Properties 1 and 2. Note that we have not yet made any assumption about Swap actually exchanging two elements of a sequence we have only assumed that it does not aect elements other than those with coordinates I and J insofar as can be determined by Test, and that it changes the I -th element to have the same Test-value as the J -th, or vice-versa. Thus, for example, if we have sequences of integers and Test just checks whether an element is positive, a Swap operation that assigns 1 or ;1 to the I -th element according to whether the J -th is nonpositive or positive would satisfy Swap Assumption 1 and 2! Thus to be able to conclude that the sequence S1 computed by Partition is a permutation of its input S , we need to assume that Swap satises Swap Assumption 4 If I J 2 Coordinate Range(F L), then Swap(S I J ) computes S1 such that for all K 2 Coordinate Range(F L) ; fI J g, (S1 K ) = Ref(S K ): Ref Swap Assumption 5 If I J 2 (F L), then Coordinate Range putes S1 such that Ref(S1 I ) = Ref(S J ). (S I J ) com- Swap 14 5 ABSTRACT ALGORITHM SPECIFICATION AND VERIFICATION Swap Assumption 6 If I J 2 (F L), then Coordinate Range Ref putes S1 such that Ref(S1 J ) = (S I ). (S I J ) com- Swap From these assumptions we obtain Partition Lemma 4 If Swap satis es Swap Assumptions 4, 5, and 6, then it also satises Swap Assumptions 1, 2 and 3, and Partition has Partition Properties 1 and 2, as well as Partition Property 3 The output sequence S1 is a permutation of the input sequence S. 5.3 Correctness when Swap is an assignment operation Now suppose, instead of performing an exchange of two elements, Swap just does an assignment operation, so that it satises Swap Assumption 5 but not Swap Assumption 6. We can still conclude: Partition Lemma 5 If Swap satis es Swap Assumptions 5 and 6 , then it also satis es Swap Assumptions 1 and 2, and Partition has Partition Property 1, as well as Partition Property 4 The sequence of elements of the output sequence S1 with coordinates in is the subsequence of the elements of S with coordinates in (F L) and satisfying Test. Accept Coordinate Range This tells us that by instantiating Partition with Swap as an assignment operation, we obtain a version that brings together all the elements that satisfy Test. It does not yield all the elements that do not satisfy Test, but in some applications we would not need this information. The main benet of dividing the specication of assumptions about Swap and conclusions about Partition into small pieces is that we can deal with the question of correctness of dierent instances merely by citing the appropriate lemmas (or we can create and prove new lemmas with comparatively little eort). The work of proof has been factored into small steps that allow us the same benets of reuse in proofs as generic algorithms allow us in programming. 5.4 Correctness of the body of Partition 15 5.4 Correctness of the body of Partition To prove partial correctness, we add three internal assertions to the body of Partition (Figure 1). In these assertions note that S refers to the initial value of variable S, while S1 refers to the current value at the point of the assertion: 1. (At the beginning of the rst inner loop.) There are a nonnegative integer n and two Coordinate sequences I1 : : :  In and J1 : : :  Jn such that (a) (b) (c) (d) (e) F  I1 < : : : < In < First  Last < Jn < : : : < J1  L For k = 1 : : :  n, Test(S Ik ) is false while Test(S Jk ) is true for P 2 Coordinate Range(F First) ;fI1 : : :  In Firstg, Test(S P ) is true for P 2 Coordinate Range(Last L) ; fLast J1 : : :  Jng, Test(S P ) is false The current value S1 of S is the value of S computed by Swap(S, I1, J1 ) ... Swap(S, In , Jn ) 2. (At the beginning of the second inner loop.) Assertion 1, Test(S First) is false. First 6= , and Last 3. (Just before the Swap call.) Assertion 2 and Test(S, Last) is true. These assertions along with entry and exit assertions obtained from the Formal Semantics of the algorithm are su cient for carrying out an inductive assertions proof of partial correctness: any path from the beginning of the procedure to the exit is composed of a nite number of path segments between two assertions, and one can verify that for each such path segment the assertion at the beginning combined with the semantics of the statements along the path implies the assertion at the end. We omit these proofs. To prove total correctness, we need to show also that the procedure always terminates. Actually, the result we prove is conditional on the termination of Test and Swap. Note that 1. it can be shown inductively that, at all times, First  Last 2. every path segment (as dened in the partial correctness proof) that can be repeated in any execution of the procedure contains an assignment that increases First or decreases Last (on one path both are changed, but not without violating 1) 16 REFERENCES Therefore, any execution consists of only a nite number of path segments. Since each path segment contains only assignment statements, equality tests, or calls to Test and Swap, we have Partition Lemma 6 If the generic parameters Test and Swap always terminate, then Partition always terminates. 6 Conclusion In this paper we have attempted to develop a framework su cient to encompass the key aspects of generic programming, with illustrations from our experience in building a library of generic software components in Ada. Although the documentation of the initial library in 7] is informal, and we have not yet carried out formal specication and verication of the library components, we believe that this task would be both mathematically very interesting and practically very useful. On the mathematical side, the correctness of generic algorithms oers greater challenges and less tedium than concrete algorithms, for often one must create the appropriate abstract concepts in terms of which one can eectively express and reason about the behavior of an algorithm or collection of algorithms. The nature of the problem of verifying generic algorithms should be attractive to researchers in computer science and mathematics, whereas the problem for concrete algorithms is often regarded as so tedious as to be worth doing only if most of the work can be done with an automated reasoning system. On the practical side, the considerable work of composing a formal specication and carrying out a detailed proof of correctness at a generic level is compensated by the ease with which one is then able to deal with the correctness of many distinct instantiations. While it is often di cult to justify the amount of eort required for formal verication of concrete programs, except in the case of software used in life-critical systems, the possibility of verifying components in generic software libraries may open the way for the benets of this technology to become much more widely available. References 1] G. Booch, Software Components in Ada. Benjamin/Cummings, 1987. 2] O.-J. Dahl, E. W. Dijkstra, and C. A. R. Hoare, Structured Programming, Academic Press, 1972. REFERENCES 17 3] E. W. Dijkstra, A Discipline of Programming, Prentice-Hall, Englewood Clis, New Jersey, 1976. 4] J. Goguen, \Parameterized Programming," Transactions on Software Engineering, SE-10(5):528-543, September 1984. 5] A. Kershenbaum, D. R. Musser and A. A. Stepanov, \Higher Order Imperative Programming," Computer Science Dept. Rep. No. 88-10, Rensselaer Polytechnic Institute, Troy, New York, April 1988. 6] D. R. Musser and A. A. Stepanov, \A Library of Generic Algorithms in Ada," Proc. of 1987 ACM SIGAda International Conference, Boston, December, 1987. 7] D. R. Musser and A. A. Stepanov, The Ada Generic Library: Linear List Processing Packages, Springer{Verlag, 1989. (This book supercedes General Electric Corporate Research and Development Reports 88CRD112 and 88CRD113, April 1988). 8] G. L. Steele, Common LISP: The Language, Digital Press, 1984. 9] N. Wirth, Algorithms + Data Structures = Programs, Prentice-Hall, 1976.