Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Entity–attribute–value model wikipedia , lookup
Open Database Connectivity wikipedia , lookup
Microsoft Jet Database Engine wikipedia , lookup
Concurrency control wikipedia , lookup
Functional Database Model wikipedia , lookup
Clusterpoint wikipedia , lookup
ContactPoint wikipedia , lookup
Relational algebra wikipedia , lookup
Multiparadigm Design of a Simple Relational Database Charles D. Knutson Department of Computer Science Brigham Young University 2214 TMCB, Provo, Utah 84602 [email protected] Timothy A. Budd Department of Computer Science Oregon State University Corvallis, Oregon [email protected] Hugh Vidos Microsoft Corporation Redmond, Washington [email protected] Abstract: Multiparadigm programming languages (such as Leda) seek to merge elements of several programming paradigms into a single cohesive language that utilizes programming and conceptual aspects from various paradigms (including imperative, objectoriented, functional and logic). While there are relatively few fully multiparadigm languages, most popular languages increasingly incorporate aspects from multiple paradigms. Despite the fact that multiparadigm programming is an increasing reality, there are few methods for multiparadigm design. This paper uses Leda to explore multiparadigm program design via a simple database example. This paper is exploratory in nature, and exposes areas that should be researched further. Keywords : Multiparadigm Design, Multiparadigm Programming, Leda 1. Languages, Paradigms and Design Methods The preeminent programming paradigm has historically been imperative. As high-level imperative programming languages (such as Fortran, Cobol, and C) gained popularity, design and analysis methods grew to match them. These methods provide a framework for reducing a problem into its composite parts. They also tend to match the underlying imperative paradigm, decomposing problems into two basic parts: 1) data, and 2) functions or procedures that operate on that data. Programming is now moving strongly toward the object-oriented paradigm. Languages such as C++ and Java have emerged as languages of choice within this paradigm. With the rise of these object-oriented languages, corresponding methods have emerged for design and analysis. These methods also provide a framework for reducing a problem into its composite parts. In the object-oriented world, these parts take the form of class hierarchies with data and methods encapsulated in classes. Functional and logic programming languages have never risen in popularity the way imperative and object-oriented languages have. As a result, there is a dearth of information on how to perform design and analysis of programming problems from a functional or logic programming perspective, and there are few formal methods to aid in appropriate problem decomposition. The multiparadigm language Leda [1] merges elements from these four paradigms into a single cohesive language. Multiparadigm languages have yet to gain widespread popularity, although most popular languages are increasingly incorporating aspects from multiple paradigms.1 As a result, few methods for multiparadigm design currently exist, and none directly address the issues raised specifically by these four paradigms. This paper seeks to explore multiparadigm program design through a simple database example using the multiparadigm language Leda. This paper is exploratory in nature, and seeks to expose areas that should be researched further. 2. Example: A Simple Relational Database Typically an analysis phase precedes design. During analysis, we try to understand user or customer requirements. During this phase, we discuss objects and elements that are meaningful to the user, and attempt to do so without strongly tainting the analysis with a particular paradigm. Unfortunately, this is easier said then done. If the gap between analysis and design exists within a given paradigm, it is bound to be even more problematic when mixing paradigms. It is beyond our scope to resolve that issue here, but we should at least be aware that it exists, and understand some of the natural limitations under which we operate. One of the advantages in choosing a simple relational database as an example is 1 Consider Microsoft Visual C++, an object-oriented language that subsumes the imperative language C, and incorporates aspects of visual programming. The standard template library also implements functional programming capabilities. that the analysis and modeling is well-known, and consists of well-defined representations of its composite pieces and the actions that can be performed using them. In the relational model, a database is a collection of tables, each of which is assigned a unique name. A row in a table represents a relationship among a set of values. The columns of a table represent attributes that can be held by items in the database. Each row of a table contains information that relates instances of attributes to one another. Commonly “relation” is used in place of “table,” and “tuple” is used in place of “row.” A set of relations then, comprises the physical manifestation of a given database. A number of actions can be performed on a database. Relations can be created (given a unique name, and set of attributes, each assigned to a separate column) or deleted. Modifying a relation is done through manipulation of the tuples. Given a relation, tuples can be added, deleted or modified. Modification involves changing one or more attribute values for a given tuple. Finally, queries can be performed on databases, and can involve one or more relations. One of the most common methods for users to submit queries is through a language like SQL (Structured Query Language). The basic structure of an SQL expression consists of three clauses: select, from, and where. select is used to list the attributes desired in the result of a query. from lists the relations to be scanned in the execution of the expression. where consists of a predicate involving attributes of the relations that appear in the from clause. To summarize, the relational database model consists of the following elements: 1) A database, consisting of Relations (or tables), consisting of A unique name A list of attributes (column names) Tuples (or rows), consisting of Attribute data in appropriate columns 2) Actions on Relations, including Add a relation Delete a relation 3) Actions on Tuples, including Add a tuple Delete a tuple Modify a tuple 4) Queries on database, consisting of SQL statements, consisting of select clause, consisting of A list of result attributes from clause, consisting of A list of relations where clause, consisting of A predicate with desired attributes from the selected relations 3. Design Method In this section, we propose a multiparadigm design method, with the caveat that this approach is embryonic, and intended to stimulate further study and research. The methods described here are born out of practical experience and fertile imagination and have not been empirically validated or analyzed. Although there are well-developed imperative [2] and object-oriented design [3] methods upon which draw, we are hampered in our efforts by the fact that there are no such methods for functional and logic design. Therefore we have tried to introduce common sense methods for these paradigms, while suggesting that further work should yield more rigorous methods. We begin by identifying elements or pieces of our system, and actions that they either perform or that are performed on them. This correlates to describing a situation in English using nouns and verbs, or in other words, things and what they do. The main component that comprises our system is a database. And what that database does is store and retrieve information. Storing information consists of actions on relations and tuples. Retrieving information consists of queries. At this point, we have an acceptable view of the overall system components, without having made a commitment to any paradigm for representing these components. We now begin a top-down decomposition, but we allow ourselves the freedom to explore four different ways of decomposing our system at each level. This is done by reviewing what the different paradigms would impose conceptually on the system or subsystem at each level. Having reviewed each paradigmatic approach, we look at the four subdesigns for levels of compatibility and merge points. 4. Levels of Compatibility Paradigms impose particular conceptual views on a subsystem design. These views may be incompatible with other conceptual views, or incompatible with conceptual views that may come later in the decomposition. For example, if we perform an object-oriented decomposition of a subsystem, it will yield classes containing data and methods. The design will focus primarily on the ways in which those classes are hierarchically organized. Having organized a subsystem this way, we are free to view methods as imperative, object-oriented, logic or functional. In this way, object-oriented design tends to have a fairly high level of compatibility with other paradigms. This means that high-level system design using an object-oriented design method tends to preserve the ability to use capabilities from other paradigms later in the decomposition. On the other hand, if we view the system from a logic perspective, we must view the world as consisting of facts and rules of inference. If our multiparadigm language only allows logical access to facts and rules (rather than, say, imperative access to information created through logic means), then the decision to view a system at the highest level from a logic perspective is extremely incompatible with other paradigms later in the decomposition. We can generalize about levels of compatibility, but for our purposes, the question should be asked, “For each of these four potential designs at this level of decomposition, how will the decision to go with one design limit my ability to choose a different paradigm at a lower level?” In the event that the choice of one paradigm severely limits the use of other paradigms later on, we must try and understand the importance of this limitation. For example, certain systems (particularly small systems) may beg to be solved almost exclusively in a particular paradigm. In these situations, the level of compatibility of the preferred paradigm is less relevant, since the imposition of that paradigm at lower levels may be the best approach. 5. Merge Points Once we’ve explored the decomposition of the system from each of the four paradigms, we need to determine whether a preferred decomposition might involve two or more of the paradigms in a merged or hybrid design solution. An example is the use of a comparative operator as a clause in a logic relation. In a pure sense, the logic paradigm does not support this, but a multiparadigm language like Leda or G [4] might, and it may add capability to a logic solution that otherwise would be extremely difficult. At a higher level, we may look at an object-oriented design where the methods within a class are relational or functional instead of imperative. This step is even more difficult than assessing levels of compatibility, because the designer is very limited by the capabilities of the multiparadigm language being used. Some multiparadigm languages (such as L3P [5] and I+ [6]) support interfacing between paradigmatically pure (or nearly pure) modules [7], and don't easily support this hybrid approach. But we believe that the value of multiparadigm programming goes beyond the interfacing of different modules, and must include the merging of language elements to create new hybrid solutions to problems. The thrust of our method is to review the design implications of each of the paradigms on the module in question, determine the level of compatibility of each of these approaches, and then explore merge points, or places where the paradigms can and should appropriately merge into a hybrid solution. We then follow this approach in a top-down fashion as each module is further refined into objects and actions. We will now discuss our database example, and apply these principles to our design. 6. Database Following our proposed method, we look at a database system from each of the four paradigms, and see what each of them suggests for our design approach. Imperative: The database is a data structure (probably, but not necessarily, global). The actions on relations and tuples are functions triggered by user input and carried out directly on the database. SQL queries are strings input by the user and are translated into function calls that search the database tables directly and return appropriate information. Pros: Relatively simple design involving a structural definition for databases, and a procedural decomposition of the system. Cons: Potentially large side effect problems because of global data. Complex queries would have to be designed and coded explicitly. Level of Compatibility: High-level imperative design leaves open the opportunity for functions of all paradigms to be accessed as function calls, but limits the user of inheritance for reuse of functions. An imperative view of the database system has a relatively high level of compatibility. Object-oriented: The database is a class which is probably sub-classed into relations (which might be further sub-classed into tuples). Each of the classes consists of data (whether relation or tuple) and methods to perform actions. Actions on relations are part of the relation class. Actions on tuples are part of the tuple class. Queries are probably part of the database class. Pros: The high-level view of databases, relations and tuples as classes with data and methods is a very tidy description that preserves the ability to use other paradigms later in the design and provides reuse of methods through inheritance and composition. Cons: At this level, there are very few negatives associated with an object-oriented view of the database system. Level of Compatibility: High-level object-oriented design has all the advantages of imperative design with the added benefit that comes through the organization of data and behavior in class hierarchies. In this example, an object-oriented view of the database system has the highest level of compatibility. Functional: The database is represented by the composition of all functions performed on it, beginning with the function to create an empty database. New functions wrap around the database to create a new virtual database while preserving the layers inside. Queries are harder to conceptualize, and may depend on the representation used to conceptualize the tables and tuples. It may involve recursive traversal of a list or some other structure to obtain information. Pros: By creating a compositionally layered database, intermediate instances of the database are not destroyed, so various kinds of transaction tracking and data consistency can be provided. Cons: Since each view of the database is an additional functional composition, every query will involve re-computing the composite function recursively back to the creation of the database. Level of Compatibility: The nature of functional design precludes side effects, and so to choose a strictly functional view of the system at the highest level precludes the later use of imperative or object-oriented designs for sub-components. It therefore has a very low level of compatibility as a high-level design. Logic: The contents of the database are determined by statements of facts. Actions on the relations or tuples take the form of additional statements of fact. Queries are performed by converting SQL into logic relations and applying them to the database. Pros: The logic concept of relations and problem space searching ought to be easily exploited in performing queries across multiple relations. Separation of data into relations and tuples is very easy. Cons: Performing all database updates using statements of facts may be extremely cumbersome. Translation of SQL from string to intelligible command form is non-trivial. Level of Compatibility: Logic design has a very low level of compatibility at this level for reasons similar to functional. Leda does not allow facts and rules to be accessed in imperative fashion, so designing the database logically at a high-level would preclude using these other paradigms later. Levels of Compatibility (Summary): For the high-level design of our database system, it seems that the paradigm that most accommodates other paradigmatic approaches later in the design process is object-oriented, followed by imperative, logic and functional.2 2 There appears on the surface to be a general principle here concerning the levels of compatibility of different paradigms at different levels of design. If general principles can be laid out that place these paradigms into appropriate pecking orders at different design levels, this phase of our proposed design would be reduced dramatically. Deeper investigation of these principles is beyond the scope of this paper, but deserves further study. Merge Points: At this high-level view of the database, where we are trying to characterize components and what they do, it appears that the object-oriented approach captures this well in the notion of data and methods. After examining the various paradigmatic views of the database at the highest level, we see that there is really nothing that can be added by the other paradigms at this point. But what is immediately suggested is that certain kinds of transaction management might effectively be done using functional techniques, and predicate clauses within queries are probably best achieved through relational means. We conclude that the best approach to designing the database is to view the overall system from an object-oriented perspective. To do this we encapsulate the data and methods associated with the database as a class composed of relations. A database is a collection of relations, each of which has a unique name. There are many ways to represent this in data, but we chose to use Leda’s predefined classes Association and List. We first decided that the List class could be used to manage lists of relations. But we needed to make some decisions about how to manage the names associated with relations. One approach is to have each relation store its own name (which is an object-oriented view) and allows for encapsulation of that information within the relation class. Another approach is to view functions to add and delete relations as easily managed from within the Database class, since it maintains the list of relations. This approach is a little more imperative, since we know something about the form of relations within the Database class.3 We chose to use the Association class to maintain the relationship between relations and their names. The Association class maintains a list of associated pairs. In our case, the type of the first element in the pair is string, representing the name of the relation. The second element in the pair is of type Relation, which is discussed in the next section. Because of our decision to view Database from an object-oriented perspective, we can design this class without knowledge of the form that relations will take (although we do know something about how they are collected and managed). Figure 1 shows the implementation of the Database class in Leda. The add and delete functions are discussed later. class Database of equality; var relations : List[Association[string, Relation]]; function add (relationName : string, newRelation : Relation) begin {See Figure 3} end; function delete (relationName : string); begin {See Figure 4} end; end; Figure 1. Database Class. 7. Relations The Database class is composed of relations, but the object-oriented approach hides the nature of relations from us. We now need to decide what relations are going to look like. Following our method, we will look at relations from each of the four paradigms, identifying the pros and cons and the potential levels of compatibility. 3 We are aware that this is not ideal object-oriented design, but we are allowing ourselves the freedom to choose from (and blend) the various paradigms available to arrive at a solution. It begs the question of whether multiparadigm design must per force be based upon rigorous object-oriented design as a foundational principle. Imperative: Relations, or tables, are structures that contain tuples. The table structure is commonly viewed as either an array or a list of tuples. Individual tuples within a relation are accessed directly either by index (in the case of an array structure) or by pointer (in the case of a list structure). Functions that act on relations must understand their structure. Pros: The structure is reasonably easy to access. Cons: Loss of object-oriented benefits. Level of Compatibility: The functions that are built to access the relations can be imperative, logic, or functional, but not object-oriented, since they can’t be bundled with the data. Object-oriented: Relations are a class used as components in a database class. Pros: Many of the desired functions can be inherited from parent classes like List and Table. Cons: Very few, if any, negatives associated with viewing relations as object-oriented. Level of Compatibility: Functions that are built to access the relations can be from any of the paradigms being examined. Functional: Relations are represented by all the tuple operations that have been performed on the relations since creation. Pros: By creating a compositionally layered relation, intermediate instances are not destroyed, so various kinds of transaction tracking and data consistency can be provided at the level of relations. Cons: Since each view of the relation is an additional functional composition, every query will involve re-computing the composite function recursively back to the creation of the relation. Level of Compatibility: The nature of functional design precludes side effects, and so to choose a strictly functional view of the system at this level precludes the later use of imperative or object-oriented designs for subcomponents. It therefore has a very low level of compatibility. Logic: The contents of the relation are determined by statements of facts. Actions on tuples take the form of additional statements of fact. Queries are performed by converting SQL into logic relations and applying them to the relation. Pros: The logic concept of relations and problem space searching ought to be easily exploited in performing actions on the tuples. Separation of data into tuples is very easy. Cons: Performing all updates using statements of facts is cumbersome. Level of Compatibility: Logic design has a very low level of compatibility at this level for reasons similar to functional. Leda does not allow facts and rules to be accessed in imperative fashion, so designing the database logically at this level would preclude using these other paradigms later. Levels of Compatibility (Summary): For the design of relations, it seems once again that the paradigm that most accommodates other paradigmatic approaches further in the design process is object-oriented. Merge Points: There may be merge points relative to the methods that operate on relations, but the actual structure is rather simple, consisting of a name and a collection of tuples.4 class Relation of object; var tuples : List [Table[string, object]]; { our keys will always be of type string } { using polymorphism for the type of the value } function add (newTuple : Table[string, object]) begin {See Figure 5} end; 4 It is possible that there is a unifying principle that data is organized in object-oriented fashion while methods employ a merging of multiparadigm characteristics. This begs the question of whether data is sometimes best represented in other paradigms. This would per force involve a discussion of the ways in which those paradigms deal with data, and then a generalizable view of the implications on these approaches for levels of compatibility. This requires further study. function delete (value : object) begin {See Figure 6} end; end; Figure 2. Relation Class. Our conclusion is to continue viewing the data structures for relations in an object-oriented fashion, and encapsulate the relation data type with add and delete methods that allow relations to manage collections of tuples. Once again, we follow the same pattern we used for the Database class. The Relation class consists of tuples, which are defined here in a similar way to the lists of relations in the Database class. The add and delete methods are included here so that relations can manage their collections of tuples. One other similarity to the definition of the Database class is that relations know something about how collections of tuples are organized. We define tuples to be a List of Tables. The Table class manages lists of Associations. We want each element in a tuple list to be a pair consisting of the name of an attribute and a value associated with it. Figure 2 shows the implementation of the Relation class. The add and delete functions are discussed later. 8. Actions on Relations Our database contains a list of relations, and can either add new relations to this list, or delete existing relations from the list. Following our proposed method, we explore these two functions from the four programming paradigms to see what the advantages and disadvantages are, and to look at levels of compatibility and potential merge points. Imperative: An imperative view of adding and deleting relations requires that we be able to see them internally. But our Database class has defined them to be a List of Associations, which hides implementation details. A pure imperative view of adding and deleting relations is not possible without looking up the methods in the List and Association classes and duplicating it here. But since that code already exists, we should use it. (The actual code in add and delete can be imperative in its general flow as it utilizes methods from the List and Association classes.) Object-oriented: A set of methods for List and Association classes are already provided, and should be utilized.5 In addition, the class types that compose relations and tuples must be maintained in the add and delete functions, with the methods accessed through variables and parameters. Functional: A strictly functional view is somewhat impractical at this point, since we have already settled upon the List class as the container of choice for our structures. However, there are functional aspects in some of the methods within List, and these will probably suggest functional elements within our solution to add and delete. Logic: Similarly, a strictly logic solution at this point is impractical, given our use of Leda base classes like List and Association. However, to correctly add or delete a relation from a database, we must search through the existing lists to find the correct relation to delete, or to place a new relation in the list. This search suggests possible help from a logic view. Levels of Compatibility (Summary): At the level of a simple method like add or delete a relation, there are no serious issues involving levels of compatibility since there is nothing lower than this kind of method. 5 It appears that the most significant impact of object-orientation on software design is in the organization of data objects, and in the encapsulation of methods within classes. On the other hand logic and functional approaches seem limited in high-level organization, but very applicable within individual methods. This may suggest that our method should split along class/data and method lines, where object-orientation is used to define classes and data, while imperative, logic and functional paradigms are used in the implementation of methods. This requires further study. Merge Points: At this level, we should be strongly influenced by the various paradigms, and should find opportunity to merge them for a good solution. We will look first at the add function, and walk through its design. Remember that in our solution, a database is a list of Associations, each of which has a name and a relation. We must deal with the situation that occurs when the database has no relations. If at least one relation exists in the database, we can add the relation to the database. We only check for one other condition, and that is for duplicates. If a relation already exists in a database, we do not create a new one. Figure 3 shows the implementation of add. We designed the add function to take two parameters: a name and a relation. This presupposes that either some other function has constructed a relation, or we are passed an empty relation. In either case this lends simplicity to add, and maintains a nice level of modularity, since the add function should not be in the business of constructing relations to put in databases. It should only insert existing ones that it is given. function add (relationName : string, newRelation : Relation); var temp : Association[string, Relation]; found : boolean; begin if ~defined(relations) then relations := List[Association[string, Relation]] (relationName, newRelation); else begin { Check to see if relation already in database. } found := false; for relations.items(temp) do begin if temp.key = relationName found := true; end; if found = false then relations.add(relationName, newRelation); end; end; Figure 3. Add a relation to a database. Leda does not allow explicit use of pointers, but provides the defined function which tells us if something has not yet been assigned a value. We use this function to tell if this is the first relation in a database. In this case we simply put the new relation and its name into the database as the only element. If the database is not empty, we look through the list of relations to make sure there are no other relations by that name, since the database model requires unique names for relations. To do this, we iterate over all of the relation/name pairs in the database, checking the names against relationName. If it is not found in the database, we add the relation to the list. The details of the items function invoked in add are hidden to us at this level, and details are found in the List and Link classes. However, it should be noted that these functions are of a type that return logic relations (as opposed to the kinds of relations we’ve been discussing). By using the for statement in Leda, the logic search engine forces temp through all possible values, and hence it takes on the value of every Association in the list of relations. This is an example of a seamless merging of logic features into an otherwise object-oriented method with lots of imperative glue. function delete (relationName : string); begin relations.removeTest (function (element : Association[string, Relation])->boolean; begin return relationName = element.key; end; end; Figure 4. Delete a relation from a database. Next we look at the delete function, and walk through its design. Again we look at the methods provided by the List class, and we find removeTest, which invokes the removeTest function from the Link class. This function traverses a list, building a new list, and skipping any elements in the list that fail to meet the conditions described by a function that is passed to it as a parameter. This is an example of merging functional programming in our solution. We have a function that will create a new list minus the undesirable elements, and it is up to us to provide a boolean function that will describe the conditions for exclusion. Figure 4 shows the implementation of delete. Leda allows us to create a function in a parameter list. Our function returns true when the name we encounter is the same name passed to delete. 9. Actions on Tuples We must consider three actions for tuples: adding, deleting, and modifying. For the sake of simplicity, we will implement only add and delete. It can be argued that any modification can be viewed as the composition of a delete with an add, and this may be pleasing to functional purists. The calling routines can utilize these add and delete functions to facilitate modification. To implement add, we first look at the methods provided by the parent class, and we immediately find its add function. We could follow a similar design path as we did for actions on relations, but we don’t have the same uniqueness requirement here. Assuming no additional constraints, we can commonly use standard inherited functions, and this is one of the significant advantages to using object-oriented methods. function add (newTuple : Table[string, object]); begin tuples.add(newTuple); end; Figure 5. Add a tuple to a relation. Deleting a tuple from a relation involves basically the same logic required for the where clause of an SQL statement. That is, we must be prepared to administer a variety of logic constraints in order to choose an appropriate tuple from a relation (or in the case of the query, from one or more relations). We concluded that ideally this level of predicate logic was somewhat incompatible with the job description of the Relation class, that of maintaining a list of tuples. But the functional aspects of Leda provide us a mechanism for keeping the jobs separate. To create the delete function, we leveraged an idea that we used in the delete function for relations. We already have an inherited function removeTest that applies a predicate function to every element in the structure it’s given. By requiring the caller to provide the predicate function, within the relation class all that is required is that we provide access to the individual tuples for examination and possible deletion. This is a good example of modularization through the use of functions as parameters, allowing classes to manage their data, and leaving decision-making logic to other parts of the system. function delete (theFun : function(Table[string,object])->boolean; begin tuples.removeTest(theFun); end; Figure 6. Delete a tuple from a relation. 10. Database Queries Recall that SQL statements consist of three clauses: select, from, and where. A query involves the following steps (typically in this order): 1) Access all the relations involved in this query, by referencing the from clause; 2) Compute the Cartesian product of these relations to create a single relation that contains all the information from all the chosen relations; 3) Select tuples from this relation that match the predicate given by the where clause (this can also be viewed as eliminating all tuples from this relation that do not match the predicate); 4) Preserve the attributes (or columns) in the resulting relation that match those identified in the select clause (this can also be viewed as eliminating all attributes/value pairs from a tuple that do not match the selected attributes); 5) The resultant relation is the solution to the query. Through the design of the methods that perform actions on relations and tuples, we have seen certain patterns emerging. For example, we know that we can build lists using the methods inherited from the List class, based upon a particular predicate. We should therefore be able to apply this kind of function for each of the relations named in the from clause, and store this information in an instance of the Database class, representing the set of all relations participating in this query. To perform the Cartesian product of two relations, we must first create a new tuple definition that includes the attributes of both tables. Each of the tuples of the first relation is appended to each of the tuples of the second relation, so that the finished relation contains all the information from both relations. This process must be repeated for each relation in the from clause. We should note that the tables can potentially become quite large. However, discussions of efficiency are beyond our scope here. What is important to note is that there are really no more elaborate functions required (in terms of manipulation of the database and its component elements) than we have seen previously. The selection of tuples to preserve follows the same logic and design as deleting tuples within the Relation class. We would create a function to traverse each of the tuples in the new combined relation, and eliminate all tuples that failed to satisfy the predicate. Finally, this relation undergoes one more reduction, by eliminating all attributes not identified in the select clause. In the same way that we used a functional approach to eliminate tuples, we can use a functional approach to eliminate Associations within a given tuple (or in this case, applied to every tuple in the relation) where the attribute is not desired. The resulting relation will give the user the result of the query. We should be able to create an items function for the Relations class that will allow us to iterate over a relation and display its contents. 11. Conclusions The method proposed in this paper is still embryonic, and suggests many things that deserve more study. It appears that object-oriented design is preferred (over imperative, functional, or logic paradigms) for organizing systems and large subsystems. The advantages of inheriting methods showed itself repeatedly, as problems became simple because of the reuse of functions inherited from parent classes. It appears that logic and functional paradigms are very limiting to system design when imposed early in the design process, but yield valuable results when applied within individual methods. It also appears that logic and functional concepts may be valuable in that they suggest approaches and solutions that stand separate from the implementation paradigm. In other words, there may be a potential for cataloging the kinds of designs suggested by the various paradigms in such a way that better designs are possible irrespective of the paradigm used for implementation. A larger scale and fuller implementation of this database example would be valuable to further document the issues that come into play during this kind of system design. We believe that the database example provides a rich opportunity to exploit the multiparadigm nature of a language like Leda. We are also confident that we failed to fully take advantage of the logic capabilities, particularly in the creation of the predicate functions. A fuller implementation should expose the functions that call these simple methods (including the implementation of the predicate functions that get passed as parameters to delete and query). Finally, the primary focus of this paper was that of design, rather than implementation. We believe that there is much to be discovered about the implications for multiparadigm design, independent of a particular multiparadigm language, but even more powerfully in the presence of well-designed multiparadigm programming features. References [1] Timothy A. Budd. Multiparadigm programming in Leda. Reading, MA: Addison-Wesley, 1995. [2] Ed Yourdon. Modern structured analysis. Englewood Cliffs, NJ: Yourdon Press, 1989. [3] J. Rumbaugh, M. Blaha, W. Premerlani, F. Eddy, and W. Lorensen. Object-oriented modeling and design. Englewood Cliffs, NJ: Prentice Hall, 1991. [4] John Placer. “The multiparadigm language G.” Computer Language, 16(3):235-258, 1991. [5] Giuseppe Callegarin and Carlo Salvagno. L3P: Programmazione creativa con tre paradigmi (L3P: A language for three programming paradigms). Padova: Editrice CEDAM, 1994. [6] K.W. Ng and C.K. Luk. “I+: A multiparadigm language for object-oriented declarative programming.” Computer Language, 21(2):81-100, February 1995. [7] Pamela Zave. "A compositional approach to multiparadigm programming." IEEE Software, 15(9):15-25, September 1989.