Download Multiparadigm Design of a Simple Relational Database

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Entity–attribute–value model wikipedia , lookup

Open Database Connectivity wikipedia , lookup

IMDb wikipedia , lookup

Microsoft Jet Database Engine wikipedia , lookup

Concurrency control wikipedia , lookup

Database wikipedia , lookup

Functional Database Model wikipedia , lookup

Clusterpoint wikipedia , lookup

ContactPoint wikipedia , lookup

Relational algebra wikipedia , lookup

Database model wikipedia , lookup

Relational model wikipedia , lookup

Transcript
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.