Aditi-Prolog needs to know for every predicate in a program what modes it may be called in ..... that has an edge for every call in a program directed from the calling predicate ... This was a factor in the choice of name for the facility, but the main.
Aditi-Prolog language manual James Harland David B. Kemp Tim S. Leask Kotagiri Ramamohanarao John A. Shepherd Zoltan Somogyi Peter J. Stuckey Jayen Vaghani
Abstract Aditi is a deductive database system under development at the Collaborative Information Technology Research Institute by researchers from the University of Melbourne. The main language in which users interact with Aditi is Aditi-Prolog. This document is a reference manual for Aditi-Prolog.
Contents 1 Introduction
1
2 The elements of Aditi-Prolog programs
1
3 Mode information
4
2.1 Basic de nitions : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 2.2 Meta-predicates : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 2.3 Built-in operators : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
3.1 3.2 3.3 3.4
Mode declarations : : : : : Mode analysis : : : : : : : : Finding mode information : Controlling the ow of data
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
1 2 4
4 5 8 9
4 Optimizations
10
5 Compiling predicates together
12
6 Calling Aditi from NU-Prolog
15
6.1 6.2 6.3 6.4
Preparing Aditi predicates for access from NU-Prolog Transparent access from NU-Prolog to Aditi : : : : : : Explicit access from NU-Prolog to Aditi : : : : : : : : Interface predicates : : : : : : : : : : : : : : : : : : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
: : : :
15 18 19 22
7 Calling NU-Prolog from Aditi-Prolog
26
8 Acknowledgements
27
1 Introduction Aditi is a deductive database system under development at the Collaborative Information Technology Research Institute by researchers from the University of Melbourne. This document is an introductory guide to the Aditi system. It is one of four documents describing Aditi. The other documents are:
\An introduction to the Aditi deductive database system" [11]. This is an overview of
the Aditi system. It introduces the concepts behind Aditi and describes its structure as well as its main components. \Aditi users' guide" [4]. This is an introductory guide to the Aditi system and its commands. \Experiences with a ights database" [5]. This describes one application of Aditi. All four documents are distributed together with Aditi. This users' guide assumes familiarity with the rst two of these documents and provides the background necessary for the third. In the examples in this document, programs and program fragments will be in a typewriter style font. As Aditi is still undergoing development, some of the facilities described in this document are prototypes only and are not complete, robust, and/or as fast as they could be.
2 The elements of Aditi-Prolog programs 2.1 Basic de nitions Aditi-Prolog de nes variables, integers, oating-point numbers, strings and constant, function and predicate symbols the same way as NU-Prolog. Terms are also the same as NU-Prolog's except that terms of the form $VAR(T ) are not allowed for any term T . Aditi-Prolog programs are thus not restricted to Datalog, but can contain (almost) arbitrary terms. Aditi-Prolog supports NU-Prolog's list syntax and a prede ned set of standard NU-Prolog operators (see below) with the same semantics as in NU-Prolog. Aditi-Prolog de nes atoms the same way as NU-Prolog. Formulas are built up from literals using the NU-Prolog operators for negation (not), conjunction (,) and disjunction (;). Negation has the highest precedence, disjunction the lowest. The precedence can be overridden by explicit placement of parentheses. An Aditi-Prolog query is a formula. An Aditi-Prolog clause is either an atom followed by a period, or an atom, a \:-" sign, a formula and a period. A predicate de nition is a set of clauses with the same predicate symbol in the head. Variables within a clause are universally quanti ed at the front of the clause unless they occur in certain argument positions of calls to meta-predicates (see below). In this version of Aditi-Prolog, a variable is universally quanti ed over the clause even if it occurs only once in the body of a clause and does not occur in the head of the clause. This is one of the few 1
points where Aditi-Prolog diers from NU-Prolog; the dierence is most apparent when the call containing the singly-occurring variable is inside a negation. Explicit quanti cation, the operators some and all, will eventually to be supported (as will implication). Aditi-Prolog programs are sets of predicate de nitions augmented by the declarations introduced in later sections. Comments are the same in Aditi-Prolog as in NU-Prolog: they extend from an unquoted \%" character to the next newline.
2.2 Meta-predicates The two Aditi-Prolog meta-predicates are \not" and \aggregate". As their names imply, they do negation and aggregation respectively. Although in general one wishes to perform negation and aggregation over formulas, for reasons of implementation simplicity the current version of Aditi-Prolog supports them only over single atoms. Although this may sometimes be inconvenient, it does not reduce the formal expressive power of the language, since programmers can always add a new clause NewAtom :- Formula and rewrite e.g. not(Formula ) to not(NewAtom ). The current version of Aditi requires all occurrences of negation and aggregation to be strati ed, i.e. it must be possible to order the predicates of the program into numbered strata in such as way that if a rule de ning a predicate at a given stratum has a negation or aggregation in it, then the predicate of the atom being negated or aggregated over belongs to a lower stratum. The call \not(Atom )" is true for all ground substitutions for which Atom is false with respect to the usual iterated model of the database. In the call \aggregate(Function , Varlist , Atom )", Function must be one of Var Var Var Var Var
= = = = =
min(Term ) max(Term ) sum(Term ) count solutions(Term )
Varlist is a list of groupby variables, and Atom is the formula whose solutions provide the raw material for the meta-predicate. The variable Var must not appear elsewhere in the call, while the variables in Term must appear in Atom . A variable that occurs in Atom but does not occur in Varlist must not occur outside Atom . The call \aggregate(Var = : : : , Varlist , Atom )" de nes a relation over the variables in Varlist and Var . This relation is computed by taking the set of solutions of Atom , eliminating duplicates, and grouping together all the tuples that agree on the values of all the variables in Varlist . For each such group, the result relation will have one tuple; this tuple will consist of the variables in Varlist and the variable Var . The value of Var will be computed from the multiset of Term values in the group by applying the named aggregation function. The \solutions" aggregation function collects the values of Term together in a list in no particular order; the \count" aggregation function computes the length of this list. The aggregate func-
2
tions \min", \max" and \sum" rst evaluate each value of Term as an arithmetic expression, then take the minimum, maximum or sum respectively as the value of Var . Suppose the ightdist relation holds the tuples sydney, honolulu, 8165 honolulu, toronto, 7464 sydney, melbourne, 700 melbourne, honolulu, 8500
The query \aggregate(Min = min(Dist), [From], ightdist(From, To, Dist))" de nes this relation over \From, Min": sydney, 700 honolulu, 7464 melbourne, 8500
The query \aggregate(Max = max(2*Dist), [To], ightdist(From, To, Dist))" de nes this relation over \To, Max": melbourne, 1400 toronto, 14928 honolulu, 17000
The query \aggregate(Sum = sum(Dist), [From, To], ightdist(From, To, Dist))" de nes this relation over \From, To, Sum": sydney, melbourne, 700 sydney, honolulu, 8165 honolulu, toronto, 7464 melbourne, honolulu, 8500
The query \aggregate(Count = count, [From], ightdist(From, To, Dist))" de nes this relation over \From, Count": honolulu, 1 melbourne, 1 sydney, 2
The query \aggregate(Set = solutions(To), [From], ightdist(From, To, Dist))" de nes this relation over \From, Set": sydney, [honolulu, melbourne] honolulu, [toronto] melbourne, [honolulu]
3
Note that the \solutions" function returns lists containing a multiset of values. The total length of the solutions lists will be equal to the number of unique tuples in the relation over which the aggregation is taking place, nevertheless solution lists may contain the same value twice if not all variables of the Atom appear in either the list of group-by variables or the argument of the \solutions" function. The order in which values appear in each list is not de ned.
2.3 Built-in operators Although Aditi-Prolog does not allow the de nition of new operators, it does support some of the standard NU-Prolog operators (at the same precedences as in NU-Prolog). Apart from the standard uni cation operators \=" and \~=" for syntactic equality and inequality, the supported operators are mainly those used for arithmetic. One can build arithmetic expressions with the operators \+", \{", \*" and \/". These arithmetic expressions are terms like any other; they are evaluated only when they appear in contexts in which only a number makes sense. These contexts are:
the right argument of the arithmetic assignment operator \is"; an argument of an arithmetic comparison predicate, i.e. one of the operators \=:=" (equality), \=n=" (inequality), \=, >, < and =< all have the mode
bb. The arithmetic assignment predicate is has the modes fb and bb. The arithmetic addition/subtraction predicate plus has the modes bbf, bfb, fbb and bbb.
3.4 Controlling the ow of data During mode analysis the compiler may encounter a situation in which it can choose more than one call as the next to be executed. The criterion the compiler uses to make its choice can be controlled by associating one of the following ags with the predicate in question: minfreesips
When this ag is speci ed, the compiler will choose the literal with the least number of free (\f") arguments, breaking ties by choosing the leftmost in the original order. maxboundsips When this ag is speci ed, the compiler will choose the literal with the most number of bound (\b") arguments, breaking ties by choosing the leftmost in the original order. leftrightsips When this ag is speci ed, the compiler will choose the literal that appears leftmost in the original order. This ag is not strictly necessary, since this behavior is the default. The sux \sips" shared by these ag names stands for sideways information passing strategy (see [3]). If a predicate is available in only one mode, the leftrightsips ag allows the programmer to de ne precisely the order in which calls in the body are evaluated. However, the programmer may wish to do likewise even for predicates that are available in more than one mode. The
ags listed above may not be sucient for this, so Aditi-Prolog allows programmers to specify that a clause should be used only in certain modes of a predicate. By providing separate 9
versions of a clause for each mode of a predicate and using the default left to right evaluation, programmers can exert complete control over the order of evaluation of literals in the body. The way to specialize a clause to a set of modes is to put a list of the modes concerned, separated by semicolons, at the start of the body of the clause, and follow them with a vertical bar, as in this example: ?- mode(p(b, f)). ?- mode(p(f, b)). ?- mode(p(b, b)). p(X, Y) :- mode(b, f) | q(X, Y), r(Y, Z), t(Z, X). p(X, Y) :- mode(f, b) ; mode(b, b) | r(Y, Z), t(Z, X), q(X, Y).
In each of its three modes, predicate p has one clause: in the mode bf it is the rst clause after the bar while in the other two modes it is the second clause after the bar. If q, r and t all have the mode bf, then for each mode the selected body can be evaluated left to right and therefore will be evaluated left to right.
4 Optimizations In the current version of Aditi, as in most other deductive databases, the programmer has full control of the set of optimizations to be applied to each predicate of a program. This is essential if one wishes to be able to compare the performance of several optimization methods. Once we have gained enough experience with the strengths, weaknesses and (most important) the area of excellence of each method, we intend to make the choice of optimization methods automatic. The Aditi-Prolog syntax for specifying an optimization is a ag declaration like the following, which requests the magic set transformation for the predicate stops/3: ?- flag(stops, 3, magic)
The current version of Aditi-Prolog has two kinds of optimizations: program transformations and evaluation methods. The optimizations implemented via program transformations are: magic
supmagic
This ag requests that the magic set transformation (described in [2, 3]) be performed on the named predicate. Specifying this ag for a predicate also implicitly speci es it for other predicates the named predicate is mutually recursive with and for other predicates in the same module (see next section). This transformation is applicable to all recursive predicates that have at least one input argument. This ag requests that the supplementary magic set transformation (described in [9]) be performed on the named predicate. Specifying this ag for a predicate also implicitly speci es it for other predicates the named predicate is mutually recursive with and for other predicates in the same module (see next section). This transformation is applicable to all recursive predicates that have at least one input argument. 10
context
This ag requests that the context transformation (described in [6]) be performed on the named predicate. The transformation is applicable only to predicates all of whose rules are either non-recursive or left-, right- or multi-linear (see that paper for details). The current version of the Aditi-Prolog compiler imposes three further restrictions: the named predicate must not be involved in any mutual recursion, it must be the only predicate in its module (see next section), and the recursive call must invoke the predicate in the same mode as the original call.
The optimizations implemented via evaluation methods are: diff
nodiff
msi
psn
parallel
This ag requests dierential evaluation for the named predicate. This method is also called semi-naive; it is described in [1]. This ag exists only for completeness since dierential evaluation is the default. This evaluation method is applicable to all recursive predicates. This ag requests non-dierential (i.e. naive) evaluation for the named predicate. Although in some cases naive evaluation is better than dierential, this ag is not really an optimization ag; it exists mostly to allow the speedup achieved by dierential evaluation to be measured. This evaluation method is applicable to all recursive predicates. This ag requests the evaluation method known as the magic set interpreter for the named predicate (see [7] for a description). This evaluation method is applicable to all recursive predicates that have at least one input argument. This ag requests the evaluation method known as predicate semi-naive for the named predicate (see [8] for details). This evaluation method is based on seminaive evaluation but makes the results of computations available as early as possible. This evaluation method is applicable to all recursive predicates, but it is usually not worthwhile applying it to predicates that are not involved in mutual recursion. This ag requests that independent tasks should be evaluated in parallel using the fork-join scheme described in [10]. In this scheme, the DAP can start several RAP operations at once, then wait for them all to nish. This evaluation method is applicable to all predicates, but it is usually not worthwhile applying it to predicates without multiple rules.
There are some restrictions on what combinations of control ags make sense:
If you specify more than one of magic, supmagic and context for the same predicate,
the compiler will ignore all but one. That one will be magic if speci ed, and supmagic otherwise. If you specify one of magic, supmagic and context together with msi, the results are unpredictable. If you specify both di and nodi, the di ag is ignored. 11
If you specify both psn and nodi, what you get is a naive version of the PSN evaluation
method. If you specify both psn and parallel, the psn ag will reduce the amount of available parallelism.
For recursive predicates with some input arguments, you must specify one of the ags magic, supmagic, msi and context (the compiler will reject the predicate if you don't). Without these
ags, the code generated would completely evaluate the relation concerned and then lter out the tuples that do not match the input arguments. As there may be an in nite number of irrelevant answers, it is important to prevent them from being generated in the rst place; any of these four ags will do that.
5 Compiling predicates together One of the main tools of the Aditi-Prolog compiler is the predicate call graph, a graph that has an edge for every call in a program directed from the calling predicate to the called predicate. A strongly connected component of this graph represents a set of mutually recursive predicates. The compiler uses the call graph for several purposes. When trying to detect unstrati ed uses of negation and aggregation, it collapses each strongly connected component into a single node, derives a totally ordered strati cation by topological sorting of the resulting directed acyclic graph, and checks negations and aggregations against this strati cation. When trying to generate code for each predicate, it checks whether the predicate is alone in its strongly connected component; if not, the compiler generates one procedure for the strongly connected component and one interface procedure for each predicate involved. The topic of this section is a third purpose: deciding whether several calls to the same procedure can share the same magic set. Consider the following program to look for cycles in the ancestor relation (it is not the best possible such program): ?- mode(cyc(b, f)). ?- flag(cyc, 2, magic). cyc(X, Y) :- anc(X, Y), anc(Y, X). cyc(X, Y) :- cyc(X, Z), anc(Z, Y). ?- mode(anc(b, f)). ?- flag(anc, 2, magic). anc(X, Y) :- par(X, Y). anc(X, Y) :- par(X, Z), anc(Z, Y).
In this program cyc calls anc but anc does not call cyc. Anc and cyc will therefore end up in dierent strongly connected components of the predicate call graph and therefore in dierent strata. By default, Aditi performs each call from a higher stratum to a lower stratum separately. When the called predicate is recursive and is compiled with the (possibly 12
supplementary) magic set optimization, this means that each call will specify its own magic seed and that Aditi will compute a magic set for each call. The compiler in fact generates RL code corresponding to cyc(X, Y) :- m_cyc(X), anc(X, Y), anc(Y, X). cyc(X, Y) :- m_cyc(X), cyc(X, Z), anc(Z, Y). m_cyc(X) :- cyc_seed(X). m_cyc(X) :- m_cyc(X). anc(X, Y) :- m_anc(X), par(X, Y). anc(X, Y) :- m_anc(X), par(X, Z), anc(Z, Y). m_anc(X) :- anc_seed(X). m_anc(Z) :- m_anc(X), par(X, Z).
The second rule for m cyc is redundant; it is here only for the sake of exposition. We can expect that many calls to anc will have overlapping magic sets, possibly even identical magic seeds. This will cause Aditi to compute some parts of the anc relation many times. This ineciency can be attacked in two ways. Remembering answers between calls would help: the cyc relation would still ask the RL code of anc to compute some part of the anc relation many times, but the second and later requests for a previously computed part would be answered from a cache of recent answers. Although this is a general mechanism, it has considerable overhead both in space and in time (looking queries up in a cache takes non-zero time), and it is not implemented in the current version of Aditi. The other approach is to compute just one magic set for all calls to the predicate in the lower stratum, in this case anc. Since the rules generating this magic set may now refer to the predicate in the higher stratum and/or to its magic set, this requires that the two procedures be evaluated together. Aditi uses a generalized version the second approach; it can evaluate more than two procedures together. Programmers can ask for this to be done by enclosing the predicates that should be evaluated together within a so-called module de nition. Module de nitions consist of the following constructs: De nes the start of a module. All predicates from this point onwards until the next endModule are in one module. entry(P , n ) De nes predicate P with arity n to be an entry point to the current module. A predicate de ned within a module can be called from outside the module only if it is declared to be an entry point. endModule De nes the end of a module.
?- startModule ?-
?-
Through the judicious use of entry declarations, modules can be used to control the visibility of predicate names. This was a factor in the choice of name for the facility, but the main motivation for its creation was control of magic sets. The following variant of the previous program illustrates the way the above constructs hang together: 13
?- startModule. ?- entry(cyc, 2). ?- mode(cyc(b, f)). ?- flag(cyc, 2, magic). cyc(X, Y) :- anc(X, Y), anc(Y, X). cyc(X, Y) :- cyc(X, Z), anc(Z, Y). ?- mode(anc(b, f)). ?- flag(anc, 2, magic). anc(X, Y) :- par(X, Y). anc(X, Y) :- par(X, Z), anc(Z, Y). ?- endModule.
When it sees this program, the Aditi-Prolog compiler will generate code corresponding to cyc(X, Y) :- m_cyc(X), anc(X, Y), anc(Y, X). cyc(X, Y) :- m_cyc(X), cyc(X, Z), anc(Z, Y). m_cyc(X) :- cyc_seed(X). m_cyc(X) :- m_cyc(X). anc(X, Y) :- m_anc(X), par(X, Y). anc(X, Y) :- m_anc(X), par(X, Z), anc(Z, Y). m_anc(Z) :- m_anc(X), par(X, Z). m_anc(X) :- m_cyc(X). m_anc(Y) :- m_cyc(X), anc(X, Y). m_anc(Z) :- m_cyc(X), cyc(X, Z).
This code does not set up separate seed facts for individual calls to anc; instead, answers to all calls to anc are computed simultaneously. Note that cyc, anc and m anc are now mutually recursive and thus must be computed together. In the absence of explicit module declarations, the compiler initially assigns a separate module to each predicate. Magic transformations then create new predicates that are part of the module of their parent predicate and are not (directly) accessible from outside the module. However, the compiler always merges two modules into one whenever a predicate in one module is mutually recursive with a predicate in the other module. It does this regardless of whether the mutual recursion existed in the original program or was introduced by the magic set transformation. Modules should satisfy the following restrictions:
All mutually recursive predicates should be in one module. Violations of this constraint
will produce working programs due to the automatic merging process, but that process can also prevent the detection of visibility errors between the modules concerned. 14
If p and r are in the same module, and p calls q calls r, then q should also be in the same
module. If q is in a dierent module, it will be able to call r even if r is not declared to be an entry predicate; this is a bug (misfeature?) in the current version of Aditi.
The magic and supplementary magic transformations will currently produce inecient code for entry points that are not in the topmost strata of the module, e.g. anc in the example above. This will be xed in later versions of Aditi. At the moment the problem can be avoided by making copies of the module concerned, renaming the predicates within it as necessary, and distributing the desired entry points among the copies in a way that does not trigger the ineciency.
6 Calling Aditi from NU-Prolog Aditi-Prolog is not a general-purpose programming language. Its use in a packaged application with a non-trivial user interface requires the assistance of a host language. The current Aditi implementation therefore includes a way to access Aditi from programs written in NU-Prolog. The Aditi/NU-Prolog interface allows two styles of accessing Aditi from NU-Prolog. The rst is a transparent mode where Aditi-Prolog predicates can be called just like NU-Prolog predicates, the only dierences being in the areas of initialization, cleanup and resource limits. The second style is similar to embedded SQL interfaces and allows more [nuts and bolts] control over the details of the communication between Aditi and NU-Prolog. Updates to Aditi relations are currently possible only with the second access method.
6.1 Preparing Aditi predicates for access from NU-Prolog Before an Aditi-Prolog predicate can be accessed from NU-Prolog, it must be declared to be external and thus so accessible. Consider this example program: ?- mode(stops(f,f,f)). stops(Origin, Destination, []) :flight(Origin, Destination). stops(Origin, Destination, [Stop|Stoplist]) :flight(Origin, Stop), stops(Stop, Destination, Stoplist), not in_list(Stop, Stoplist). ?- mode(in_list(f,b)). ?- mode(in_list(b,b)). ?- flag(in_list, 2, magic). in_list(Head, [Head|_Tail]). in_list(Item, [Head|Tail]) :in_list(Item, Tail).
15
As it stands, the de nitions in this program are internal, i.e. accessible only from inside Aditi (e.g. from the query shell). To make e.g. the stops/3 predicate external and thus accessible from NU-Prolog, the le containing this program (let's say it is \stops.al") must include the declaration: ?- aditi(stops(f,f,f)).
One can add this declaration to the program above, or one can use it to replace the mode declaration of stops, since an aditi declaration tells the compiler to create code for the named mode of the named predicate as well as to make that code callable from NU-Prolog. However, an aditi declaration with the same arguments as a mode declaration will not do any harm, and may be more readable. The predicate in list has two mode declarations and will therefore have two RL procedures created for it. By issuing one or two aditi declarations, one can make either or both accessible from NU-Prolog. (One cannot call such an external predicate \member" as that name would con ict with the name of a standard NU-Prolog predicate.) If one includes the declaration ?- aditi(in_list(b,b)).
in \stops.al", then NU-Prolog programs will be able ask queries on in list only if both arguments are ground at the time of the call. On the other hand, if the only aditi declaration for in list is ?- aditi(in_list(f,b)).
then calls to in list will need only the second argument to be ground; the rst can be a free variable, a ground term, or anything in between. Aditi will call in list in the mode fb, and return the set of possible values of the rst argument; the NU-Prolog interface will then lter out those values that do not unify with the value speci ed for the rst argument in the call. In general a call to an Aditi predicate from NU-Prolog is allowed if it has ground terms in all the bound positions in one of the aditi declarations of the called predicate. Calls that do not pass this test will cause an error message at runtime and fail. The Aditi interface code in NU-Prolog tests every call to an Aditi predicate to see whether it is suciently instantiated for one of the externally available modes of the called predicate. If NU-Prolog code calls an Aditi predicate that has several aditi declarations, these tests allow the interface to choose which RL procedure to call. In the case of in list, there can never be any ambiguity about which RL procedure to call even if \stops.al" includes aditi declarations for both modes. If the rst argument is ground, the interface calls the RL procedure implementing the bb mode, otherwise it calls the RL procedure implementing the fb mode. But suppose that stops had aditi declarations for the modes b and fbf, and that a call from NU-Prolog has ground terms in both of the rst two argument positions. Then the Aditi/NU-Prolog interface is free to choose either RL procedure. The choice will aect only eciency; after ltering, the set of returned answers will be the same. 16
If \stops.al" contains no aditi declarations for in list, then NU-Prolog code will not be able to call in list directly. However, the RL procedure implementing stops is of course still able to call the RL procedure implementing in list, so NU-Prolog can still call in list indirectly through stops. In general, an Aditi predicate is indirectly callable from NU-Prolog if it is a base relation, if it is a registered derived relation, or if it is de ned in the same le as a directly callable predicate. The last category is an implementation artifact. To make a base relation such as ight directly callable from NU-Prolog, it is not sucient to give an aditi declaration for it. For implementation reasons the NU-Prolog interface code must know whether an Aditi predicate it is accessing is derived or base, so one also has to associate the \base" ag with an exported Aditi base relation. This can be done by declarations such as ?- aditi(flight(f,f)). ?- flag(flight, 3, base).
Since we expect this to be a common pattern, Aditi-Prolog has a shorthand for declaring predicates to be externally accessible base relations. The two lines above can be replaced by ?- base(flight(f,f)).
Although base declarations give a mode, in the current Aditi implementation the only mode that makes sense for a base relation is the mode with all free arguments, so the compiler ignores modes in base declarations after extracting the arity of the relation concerned. Since the de nition of a base relation is not associated with any one Aditi-Prolog source le, a base relation can be declared to be external in any source le. Derived relations must be declared to be external in the le in which they are de ned. Given the Aditi-Prolog source le \stops.al" augmented with aditi and/or base declarations, and a NU-Prolog source le \appl.nl" that accesses the predicates exported from \stops.al", the commands required to build an executable are 1% apc stops.al 2% nc -o appl appl.nl stops.nl /users/aditi/lib/sgi/interface.no
apc is the Aditi-Prolog compiler; nc is the NU-Prolog compiler. The rst command takes \stops.al" as input and generates \stops.ro" and \stops.rm" as usual; but because \stops.al" declares some predicates to be external, it also produces the le \stops.nl" to hold the NUProlog de nitions of the external predicates. The bodies of these de nitions rely on a suite of library routines to send queries to a DAP and interpret the results. The second command therefore not only compiles the application source in \appl.nl" and the interface code in \stop.nl", but also links the resulting objects with the le containing the library's NU-Prolog object code to create the executable application \appl". In the example pathname of that library le, \/users/aditi" is the value of the ADITI HOME environment variable and \sgi" is the machine type; depending on the installation, these two pathname components may need replacement. 17
One can of course also test applications by invoking the NU-Prolog interpreter and then manually loading the required les. Aditi is distributed together with NU-Prolog. If your site already has NU-Prolog, make sure that the only version of NU-Prolog you use with Aditi is the one distributed with Aditi. The use of other NU-Prolog versions may lead to errors and inconsistencies.
6.2 Transparent access from NU-Prolog to Aditi The Aditi/NU-Prolog interface allows transparent access from NU-Prolog predicates to Aditi predicates. In most cases, the only change you will need make to your NU-Prolog code is to call the predicate \startAditi/1" to initialize NU-Prolog's Aditi interface. The initialization actions include creating a DAP and logging into Aditi. If all is successful, the call will bind its argument to the atom \ok"; if there is an error, the call will bind its argument to a string indicating the error. In the current version of Aditi, calling startAditi at NU-Prolog compile time by including a query such as ?- startAditi(Result).
in a NU-Prolog application does not work. startAditi should be called from main in the application or from the NU-Prolog interpreter. After a successful return from startAditi, the application is free to make queries on Aditi predicates that have been declared to be external and whose interface routines have been linked with the application. These queries have the same form as normal calls to NU-Prolog predicates, so the code doing the calling does not have to know that the predicate being called is de ned in Aditi instead of NU-Prolog. The predicate's de nition can thus be moved between NU-Prolog and Aditi without needing to change any of the calls to the predicate. For example, given the scenario sketched above, the NU-Prolog program \appl.nl" can include program fragments such as ..., get_origin(Orig), stops(Orig, Dest, Stops), ...
The interface predicate stops/3 will rst nd out which mode of the predicate is being called. In this case there is only one, f, so it will ask the DAP to evaluate the open query on stops/3. Once the DAP has done so, it will return the entire stops relation. The interface predicate will look for the rst tuple whose rst argument matches the value of Orig it was called with. If there are none, it will fail; otherwise, it will succeed with Dest and Stops bound to the other two arguments of that tuple. When NU-Prolog backtracks into the interface predicate, it will look for the next matching tuple and return the next answer for Dest and Stops, if any. Once it has returned all answers, stops/3 will fail, and the processing of this query will be complete. An application may have several active (i.e. partially processed) Aditi queries at any time, although as we discuss below, there is a limit on the overall number. These queries can all 18
be to the same Aditi predicate, all to dierent Aditi predicates, or anything in between. For example, ..., stops(Orig, Dest, Stops_to), stops(Dest, Orig, Stops_from), ...
would have two queries on stops active at the same time. The Prolog backtracking algorithm guarantees that the application will read all the answers for the second call before it reads the next answer for the rst call. If stops were compiled in the mode b as well as the mode f, the interface predicate would test whether the rst argument is ground or not at the time of the call. If it is ground, it would invoke b; if it is not, it would invoke f. Therefore depending on how the predicate was compiled (ie. modes and transformations used), constants in the query may or may not be used in the Aditi evaluation process. If they can be used, they are passed as arguments to the query evaluator, otherwise, they are applied as a lter at the Prolog level to the set of answers returned by Aditi. In general, the interface predicate will try the use the most instantiated modes rst, since Aditi indexing is much more ecient than NU-Prolog ltering. The inverse action to startAditi, that of closing down the DAP and cleaning up any temporary relations that still exist is performed by the predicate \exitAditi/0". This predicate can be called explicitly when an application has nished using Aditi, but if necessary it is invoked implicitly when the NU-Prolog interpreter exits. Even if the NU-Prolog interpreter is killed, the DAP will perform the required cleanup. An application may make several pairs of startAditi/exitAditi calls. Calls to Aditi predicates will only work between a call to startAditi and the next call to exitAditi. Calls made at other times will fail with an error message.
6.3 Explicit access from NU-Prolog to Aditi We expect transparent access to Aditi to be sucient for most NU-Prolog applications. However, there are reasons why programmers may wish to use the lower level explicit interface instead. First, it is the only way to perform updates. Second, it allows better control over the actions to be taken upon errors (such as resource exhaustion). Third, it allows the performance of some applications to be improved, e.g. by avoiding the copying of answer tuples for processing or by reusing Aditi's answers to queries. The speci c details of the interface predicates are covered in the next subsection. First we must introduce the three key concepts of the explicit Aditi interface: temporary relations, handles and cursors. Whenever Aditi executes a relational algebra operation, it stores the result in a temporary relation. These temporary relations can be input to further relational operations or they can be returned as the answer to a query. With the exception of open queries on permanent base relations (those created via newrel), all queries need some relational operations, so their answers will be computed into temporary relations. These temporary relations are deleted when exitAditi is called, possibly as part of the cleanup when NU-Prolog itself exits, unless \nodelete" has been set (see below). 19
NU-Prolog programs refer to Aditi relations via a data structure known as a handle. Programs can create handles in two ways. First, they can specify an atomic query on an Aditi relation (either base or derived) to the \dbQuery" predicate; this will return a handle on the temporary relation holding the result of the query. Second, they can specify the type (temporary or permanent), name and arity of an existing relation to the \relName" predicate, which will return a handle on that relation. Conversely, given a handle, one can nd out the type, name and arity of the relation it points to via the \relInfo" predicate. Note that a handle cannot refer to an Aditi derived relation directly; it can refer only to a temporary relation resulting from a query on a derived relation. Handles remain valid while the relations they point to exist, and there is no limit on their number. A cursor is a pointer to the next tuple in a relation. Cursors are created by either the \openHandle" predicate, which takes a handle as input and returns a reference to a newly opened cursor as output, or by the \openQuery" predicate, which takes an atomic query as input, evaluates it, and then calls openHandle on the resulting handle. In both cases the new cursor will point to the rst tuple in the relation. Several cursors may point to the same relation; they will advance through the relation independently. One can read the tuple a cursor points to via a call such as \readCursor(Cursor, VarList, Result)". Most of the time, this will succeed with VarList bound to a list whose elements are the attributes of the tuple and Result bound to the atom \ok". If at the time of the call VarList is already instantiated to a term that does not unify with a list of the attributes of the tuple, readCursor will scan ahead to nd a tuple that does unify. In either case, readCursor will cause Cursor to point to the rst tuple after the one returned in VarList. When readCursor nds that the relation contains no more tuples matching VarList, it will succeed leaving VarList unchanged but binding Result to the atom \eof". readCursor is designed to resemble similar facilities in embedded SQL environments. A program's source may contain several calls to readCursor specifying the same cursor; which call gets which tuple is determined by the order in which the calls get executed. To prevent backtracking from obscuring the order in which tuples are returned, an invocation of readCursor will succeed only once. To read further tuples from a cursor, one must invoke readCursor again, from a textually dierent call, through recursion or through a failure-driven loop (for the usual reasons, we do not recommend the last of these). The Aditi/NU-Prolog interface does provide a way to scan through the tuples of a relation via backtracking. With this method, the cursor is hidden to prevent the confusion that could result from multiple interacting backtracking tuple-readers. The invocation \readTuples(Handle, VarList)" opens a cursor for the relation speci ed by Handle, and returns the rst matching tuple in Varlist. On backtracking, readTuples returns the next matching tuple. This continues until the relation contains no more matching tuples; readTuples will then close the cursor (see below) and fail. If the relation pointed to by Handle is empty, the call to readTuples fails immediately. Cursors can only move forward through a relation, but one can reposition the cursor at the start of its relation via the predicate rewindCursor. Once a cursor is no longer required, it should be closed via the predicate closeCursor to deallocate the resources held by it. This is quite important, as in the current version of Aditi the number of cursors that can be open simultaneously is xed. The limit compiled into Aditi 20
is 128, but this can be lowered by a lower value for the MAXUSERFILES Aditi con guration parameter, which in turn is limited by maximum number of open le descriptors allowed by the underlying Unix implementation. Attempts to open a cursor when the maximum number of cursors are already open will fail. When the attempt is made via openHandle, the failure is reported by binding the Result argument to an error message, and can thus be handled by the application. When the attempt is made via readTuples or an implicit reference as in section 6.2, the failure will be silent except for the assertion of the fact \aditiError(Message)". Application programs that can ever possibly exceed the limit must therefore include tests for the existence of this fact at appropriate places (e.g. before reporting results that may be incorrect) if they are to be robust. As an example of the use of these predicates, consider this code for computing the average number of hops over all trips from a given city: avtriplen(From, Hops) :dbQuery(stops(From, To, Stops), Handle), openHandle(Handle, Cursor, Result), (Result = ok -> getsumcount(Cursor, Sum, Count), Hops is Sum / Count + 1 ; Hops is -1 ). getsumcount(Cursor, Sum, Count) :readCursor(Cursor, [From, To, Stops], Result), (Result = eof -> Sum is 0, Count is 0 ;Result = ok -> length(Stops, Len), getsumcount(Cursor, Sum1, Count1), Sum is Sum1 + Len, Count is Count1 + 1 ; Sum is 0, Count is -100000 ).
The code rst evaluates a query on the stops Aditi derived predicate, then opens a cursor on the resulting temporary relation. It then invokes a recursive predicate, which is the Prolog equivalent of a C/QSL program entering a loop. Each invocation of getsumcount looks at the next tuple and then calls itself recursively. From the sum and count returned by the recursive call, and the length of the list of stopping points of this tuple, getsumcount computes the sum and count that should be returned for this call. avtriplen then computes the average number of stops and adds one to arrive at the average number of hops per trip. 21
A query such as \avtriplen(sydney, Hops)" will return the answer \Hops = 1.8", since of the ve trips possible out of sydney in our toy database, two are direct ights, two have one stop and one has two stops. The query \avtriplen(City, Hops)" may be more interesting. It will leave the variable City unbound and return the answer \Hops = 1.625", the average length of the eight trips possible in our toy database. If \stops.al" has an aditi declaration only for the f mode of stops, then of course both queries will cause dbQuery to invoke stops in that mode. However, if \stops.al" also has an aditi declaration for the b implied mode of stops, then the rst query will cause dbQuery to invoke stops in that mode. If one replaced the two occurrences of Stops in getsumcount by [FirstjLater], readCursor would ignore any direct ights, which have an empty list of stops. Avtriplen would then compute the average number of hops in non-direct trips over all trips out of a given city or over all possible trips in the database. For example, the answer to \avtriplen(sydney, Hops)" would become \Hops = 2.333". If an application program wishes to update a permanent relation, it must rst nd two relations containing respectively the set of tuples to be deleted from the relation and the set of tuples to be inserted into the relation. The application should then make a call such as \dbUpdate(PermRel, DeleteRel, InsertRel)", which does its job in two steps. First it deletes from PermRel all copies of tuples that appear in DeleteRel, then it inserts into PermRel one copy of each tuple that appears in InsertRel. PermRel must be a permanent relation (temporary relations cannot be updated), while DeleteRel and InsertRel may each be a permanent relation or a temporary relation. Suppose PermRel contains the tuples , , and , and DeleteRel contains the tuple while InsertRel contains and . After processing deletions, PermRel will contain the tuples and . After processing insertions, PermRel will contain the tuples , , and . If the application wishes to insert or delete tuples that cannot be returned from queries on existing relations, it may use the predicates \insertTuples" and \deleteTuples" to specify directly the set of tuples to be inserted or deleted.
6.4 Interface predicates This section contains descriptions of the NU-Prolog predicates provided by the Aditi/NUProlog interface. Note that some of these predicates are a bit brittle; they do not handle invocation errors gracefully. startAditi(Status)
Tells the NU-Prolog interpreter to open a connection to Aditi. First it decides in what directory to look for Aditi components: it will use the value of the environment variable ADITI HOME if set, the directory \/aditi" otherwise. Second, it decides what Aditi database it should look up Aditi relations in (see below for details). Third, it starts a DAP process and uses it to log into Aditi. Fourth, it loads into the DAP process the 22
\.ro" les corresponding to the Aditi interface \.nl" les the NU-Prolog application was linked with. StartAditi should always be called with its argument a free variable; when so called, startAditi always succeeds. If an error occurs during the call, Status will be bound to a string containing an error message; otherwise, Status will be bound to the atom ok. Backtracking across a call to startAditi has no eect. exitAditi
Tells the NU-Prolog interpreter to close its connection to Aditi. Terminates the underlying DAP process. Unless nodelete is set (see below), it will also remove any temporary relations created by this DAP. This call always succeeds; backtracking across it has no eect.
database(Path)
When deciding what Aditi database to use as an environment, startAditi performs a call of this form. If you de ne a predicate database/1 in your program, this call will succeed, and startAditi will take the value of Path in the rst solution to be the pathname of the desired Aditi database. Path must be an atom or a string, and it must be a well-formed absolute pathname. If you do not have a predicate database/1 in your program, startAditi will attempt to use the value of the environment variable ADITIBASE if it exists; otherwise it will attempt to use the current directory. The chosen directory must contain an Aditi database created by the command newdb; if it doesn't, startAditi will return an error.
search(Path)
When deciding where to look for \.ro" les containing the de nitions of Aditi predicates, startAditi performs a call of this form. If you de ne a predicate search/1 in your program, this call will succeed, and startAditi will take the values of Path in all the solutions to be the list of directories to be searched (the order of the solutions is important). Each solution for Path must be an atom or a string, and it must be a well-formed pathname, absolute or relative. If you do not have a predicate search/1 in your program, startAditi will use as its search list the \rl" subdirectory of the chosen database, and then the current directory.
set(Var) unset(Var)
Set or unset a variable in the DAP, to take eect at the next query execution. The argument of these calls must be ground and must be one of the atoms \nodelete", \tmpincore" or \nodups"; any other value will cause the call to fail. Assignments to other DAP variables from NU-Prolog do not make sense. When set, nodelete speci es that all temporary relations should be kept until explicitly deleted by the user; when not set, all temporary relations are removed at the end of a query except the one containing the answer. When set tmpincore speci es that all temporary relations should be kept in main memory whenever possible; when not set, temporary relations are stored on disk. Tmpincore thus avoids disk I/O and speeds up query evaluation at the cost of requiring much larger shared memory segments. By default, nodelete is unset and tmpincore is set. 23
relName(Type, Name, Arity, Handle)
Creates a handle for the relation speci ed by Type, Name and Arity, which arguments must be ground at the time of the call. Type must be either of the atoms \perm" or \temp", Name must be a string (e.g. \p") and Arity must be an integer. For permanent relations, Name must be a simple relation name; for temporary relations, Name must be a full path name of the le containing the relation (e.g. \/users/aditi/tmp/DAP3442 0.df"). At the moment, temporary relations stored in multiple les (e.g. those with a B-tree index) are not accessible in this way. If the call violates any of the above requirements, it will fail. If it complies with them all, it will succeed, binding Handle to a valid relation handle.
relInfo(Handle, Type, Name, Arity)
Given a handle, this call returns the type, name and arity of the relation it corresponds to. Fails if Handle is not a valid handle, or if the other three arguments are already bound to inappropriate values.
dbQuery(Call, Handle)
Evaluates the Aditi query Call and returns a handle to the answer relation. Call must be a single atom (e.g. \ ight(From, To)") whose predicate is an exported Aditi predicate. If the predicate is not an exported Aditi predicate; or if the instantiation state of Call does not match any of the exported modes of the predicate, dbQuery will print an error message and fail. Otherwise, it succeeds and returns a valid handle.
dbUpdate(PermRel, DeleteRel, InsertRel, Result)
Updates the permanent relation pointed to by the handle PermRel. Delete and Insert must be handles; they can point to either permanent or temporary relations. First all occurrences of all tuples in DeleteRel are deleted from PermRel; then all tuples in InsertRel are inserted into PermRel. If the update succeeds, the call will bind Result bound to ok; otherwise, it will bind Result to an error message. The call itself always succeeds.
insertTuples(Handle, Tuples, Result)
Inserts into the permanent relation pointed to by Handle the list of tuples speci ed by the second argument. Each element of the list Tuples must itself be a list containing n ground terms where n is the arity of the relation being inserted into. If the update succeeds, the call will bind Result bound to ok; otherwise, it will bind Result to an error message. The call itself always succeeds.
deleteTuples(Handle, Tuples, Result)
Deletes from the permanent relation pointed to by Handle the list of tuples speci ed by the second argument. Each element of the list Tuples must itself be a list containing n ground terms where n is the arity of the relation being deleted from. If the update succeeds, the call will bind Result bound to ok; otherwise, it will bind Result to an error message. The call itself always succeeds.
openHandle(Handle, Cursor, Result)
Opens a cursor on the relation pointed to by Handle. If Handle is valid and a cursor 24
can be opened for the relation, the call succeeds binding Cursor to that open cursor and Result to ok. Otherwise, it succeeds binding Result to an error message. openQuery(Call, Cursor, Result)
Evaluates the Aditi query Call via dbQuery and if that is successful, returns a cursor on the answer relation via openHandle. If both calls succeed, then openQuery will itself succeed binding Cursor to the open cursor and Result to ok. If either call encounters an error, openQuery will succeed binding Result to an error message.
readCursor(Cursor, VarList, Result)
Reads from the position pointed to by Cursor the next tuple whose attributes unify with the variables in VarList. Tuples whose uni cation with VarList fails are skipped over and cannot be read again unless rewindCursor is called. The next call to readCursor will start looking at the next tuple after the one returned. If the call nds a matching tuple, it will succeed binding Result to ok. If there are no matching tuples, e.g. because they have all already been read or the length of VarList is not equal to the arity of the relation, the call succeeds binding Result to \eof". If some other error occurs, e.g. Cursor is not bound to a valid cursor, the call succeeds binding Result to an error message. Regardless of whether it nds an error or not, readCursor succeeds at most once; on backtracking it will fail.
readTuples(Handle, VarList)
Reads the tuples in the relation pointed to by Handle one by one. For each tuple it reads, it attempts to unify the attributes of the tuple with the variables in VarList. It reads successive tuples on backtracking, or if the uni cation fails (it may fail e.g. because Varlist contains repeated variables or constant and/or function symbols). The call succeeds whenever a uni cation succeeds and fails when there are no more tuples to be read. The call will also fail if Handle is not a valid handle or if the length of VarList is not equal to the arity of the relation.
rewindCursor(Cursor)
Repositions the given cursor to the start of its associated relation. This call always succeeds but does nothing if Cursor is not a valid cursor.
closeCursor(Cursor)
Closes the given cursor and deallocates any resources it holds. This call always succeeds but does nothing if Cursor is not a valid cursor.
closeAllCursors
Closes all open cursors and deallocates any resources they hold. This call always succeeds. This predicate is needed to handle situations where implicit cursors opened by readTuples cannot be closed because the program has cut past the backtrack point.
spyAditi nospyAditi
These calls exist to make it easier to debug NU-Prolog applications that access Aditi. They create or delete NU-Prolog spypoints on the NU-Prolog predicates listed in this section. Since NU-Prolog does not support spying on compiled predicates, these calls work only if the interface predicates are loaded into the NU-Prolog interpreter np in 25
source form. The source for the interface predicates is the le \interface.nl" in the same directory as the le \interface.no" is in.
7 Calling NU-Prolog from Aditi-Prolog There are many predicates for which a bottom-up set-at-a-time evaluation is not as ecient as a top-down tuple-at-a-time evaluation. Reconsider the program for the predicate stops. ?- mode(stops(f,f,f)). stops(Origin, Destination, []) :flight(Origin, Destination). stops(Origin, Destination, [Stop|Stoplist]) :flight(Origin, Stop), stops(Stop, Destination, Stoplist), not in_list(Stop, Stoplist). ?- mode(in_list(f,b)). ?- mode(in_list(b,b)). ?- flag(in_list, 2, magic). in_list(Head, [Head|_Tail]). in_list(Item, [Head|Tail]) :in_list(Item, Tail).
Bottom-up evaluation of the rules for stops will bene t from the database techniques used to perform the repeated joins between the flight relation and the stops relation being built. However, the rules for in list do not gain any such bene ts. Indeed, a call to in list with n lists of length m will result in the magic set being built within the computation of in list containing n m lists. The rules for in list can be evaluated more eciently using a tuple-at-a-time technique; the membership test should be performed for each list one at a time. The Aditi compiler can be instructed to use the NU-Prolog compiler by using the topdown ag. So, in the above example, the line ?- flag(in_list, 2, magic).
should be replaced with ?- flag(in_list, 2, topdown).
This gives a signi cant improvement in the speed of evaluation. The Aditi compiler will generate Aditi-RL code for the rules for stops and will generate NU-Prolog `WAM code' for the rules for in list. If the program source is stored in file.al, then the compiler will generate file.ro and file.no containing the RL and WAM code respectively. When making 26
queries on stops from the query shell, it is necessary to load both les, and this can can be achieved with a single command: `load file'. The mode declarations for in list are still necessary for the compiler to determine what modes in list can be called with. The modes must be chosen so that the rules for the top-down predicate will ground the free arguments. The compiler will not check that this condition is satis ed, but if the top-down call returns variables then this will be caught as a run-time error. A top-down predicate that is only called by other top-down predicates does not need mode declarations. The following points must be remembered when declaring a predicate to be top-down: a topdown predicate must ground its free arguments; a top-down predicate can not call a bottom-up (Aditi-Prolog) predicate or an Aditi base relation; and, since top-down rules are executed by NU-Prolog, they have a dierent operational semantics | no memoing is performed and so Aditi-Prolog rules that normally terminate may not terminate when they are declared to be top-down.
8 Acknowledgements Many people have contributed signi cantly to Aditi both by performing research and by developing software. We would like to acknowledge the support of Warwick Harvey, David Keegel, Kim Marriott, and Jerey Schultz in this respect. This research was supported by grants from the Australian Research Council through the Machine Intelligence Project, from the Victorian Department of Manufacturing and Industry Development, through the Collaborative Information Technology Research Institute, from the Australian Department of Industry, Technology and Commerce through the Key Centre for Knowledge-based Systems, and from the Australian Cooperative Research Center programme through the Center for Intelligent Decision Systems.
References [1] I. Balbin and K. Ramamohanarao. A generalization of the dierential approach to recursive query evaluation. Journal of Logic Programming, 4(3):259{262, 1987. [2] F. Bancilhon, D. Maier, Y. Sagiv, and J. Ullman. Magic sets and other strange ways to implement logic programs. Proceedings of the Fifth Symposium on Principles of Database Systems, pages 1{15, 1986. [3] C. Beeri and R. Ramakrishnan. On the power of magic. Proceedings of the Sixth ACM Symposium on Principles of Database Systems, pages 269{283, March 1987. [4] J. Harland, D. B. Kemp, T. S. Leask, K. Ramamohanarao, J. Shepherd, Z. Somogyi, P. J. Stuckey, and J. Vaghani. Aditi users' guide. Technical Report 92/26, Department of Computer Science, University of Melbourne, November 1992. 27
[5] J. Harland and K. Ramamohanarao. Experiences with a ights database. Technical Report 92/28, Department of Computer Science, University of Melbourne, November 1992. [6] D. B. Kemp, K. Ramamohanarao, and Z. Somogyi. Right-, left-, and multi-linear rule transformations that maintain context information. Proceedings of the Sixteenth International Conference on Very Large Data Bases, pages 380{391, August 1990. [7] G. Port, I. Balbin, and K. Ramamohanarao. A new approach to supplementary magic optimisation. Proceedings of the First Far-East Workshop on Future Database Systems, pages 89{104, April 1990. [8] R. Ramakrishnan, D. Srivastava, and S. Sudarshan. Rule ordering in bottom-up xpoint evaluation of logic programs. Proceedings of the Sixteenth International Conference on Very Large Data Bases, pages 359{371, August 1990. [9] D. Sacca and C. Zaniolo. Implementation of recursive queries for a data language based on pure horn logic. Proceedings of the Fourth International Conference on Logic Programming, pages 104{135, May 1987. [10] P. J. Stuckey, T. S. Leask, and K. Ramamohanarao. Exploiting parallelism in bottom-up computation in aditi. Proceedings of the ILPS '91 Workshop on Deductive Databases, October 1991. [11] J. Vaghani, K. Ramamohanarao, D. B. Kemp, Z. Somogyi, and P. J. Stuckey. Design overview of the aditi deductive database system. Proceedings of the Seventh International Conference on Data Engineering, pages 240{247, April 1991.
28