Apr 11, 2000 - A goal, ancestor(john; mary), might succeed by invoking either the rst or ... See, for instance, Huet & Oppen 80] for a survey of the known tech-.
A Recursive Techniques Editor for Prolog Alan Bundy
Gerd Grosse April 11, 2000
Paul Brna
Abstract
We describe an editor geared to recursive Prolog procedures. It is similar to the structure editors built for many programming languages, except that instead of just ensuring the correctness of the syntax of the procedures built by the editor, the editor also ensures the correct use of recursion. By correct here we mean that the recursive procedure is guaranteed to terminate and to be well-de ned. Within these constraints we have tried to ensure that the range of procedures that can be built is as complete as possible. Key words and phrases. Prolog environments, programming techniques, struc-
ture editor, recursion, termination, rewrite rules.
1 Why a Recursion Editor? The general question being addressed in this paper is \what environmental tools can and should be provided to programmers to assist them to write programs?". Some of the standard answers to this question are: structure editors: to ensure that the user writes only syntactically correct programs; cross referencers: to alert the user to calls to unde ned procedures or to procedures that are de ned but not used; procedure stores: to enable users to reuse old procedures; and formal methods: to ensure that only correct programs are written. This paper proposes an answer that combines aspects of all these solutions in a form that allows them mutually to reinforce each other. We describe a recursion editor that not only enforces the syntactic correctness of each procedure, but also enforces some aspects of correctness. It keeps track of the calling pattern of procedures. New procedures are built either by instantiating and modifying standard procedure schemata or by editing old procedures. The role of the recursion editor is to help programmers avoid the introduction of bugs during the process of program writing. We have conducted a study of bugs in Prolog programs and proposed a multi-level classi cation of them, [Brna et al. 87]. At the level of Prolog code we classify bugs with respect to a collection of standard Prolog techniques; each dierent way of violating the de nition of the technique gives rise to a dierent kind of bug. Recursive programming techniques form a particularly rich sub-collection of these Prolog techniques. We are grateful for comments on this paper from Andy Bowles, Chee Kit Looi, Jan Newmarch, Helen Pain, Alan Smaill, Frank van Harmelen, Lincoln Wallen and Paul Wilks. The research reported here was supported by Alvey/SERC grant GR/D/44287, an SERC Senior Fellowship to student loan to the second. the rst author and a BAFOG
1
Recursive procedures are a rich source of bugs in any recursive programming language, cf [Kahney 82] for instance. Prolog is no exception to this general rule, and it has some special problems of its own; for instance, it is a common observation that procedures like append=3 which build recursive data-structures in the head of a clause, seem to cause special diculty. Thus a programming tool to assist Prolog programmers write correct recursive procedures is a natural rst choice for a tool based on programming techniques. The tool we describe is an editor whose commands only allow correct recursive procedures to be written. By correct here we mean that the recursion is guaranteed to terminate and is well-de ned, ie is neither under- nor over-de ned. The recursion editor described here has been partially implemented by the second author as part of his MSc project at Heriot Watt University, [Grosse 89]. This paper describes the editor as implemented and also proposes some additional editing commands that would make the recursion editor more powerful. It is not enough to provide only commands for building recursive procedures. It is also necessary to provide commands for non-recursive procedures, since these are necessary for de ning the non-recursive bits of otherwise recursive procedures. These non-recursive commands constitute a standard structure editor for Prolog (cf [Teitelman & Masinter 81] for Lisp).
2 Peter's Kinds of Recursion As a basis for the recursion editor we need an account of the various forms that recursion can take. One such account can be found in Peter's book, [Peter 67]. Peter describes most of the common kinds of recursion on the natural numbers that arise in recursive function theory. This account is readily adapted to the other recursive data-structures that arise in programming, eg lists. We have adopted it for this proposal. It is true that one could cover more kinds of recursion by opting for a completely general de nition of recursion based on the notion of a well-founded ordering, but then the programmer would have to de ne that ordering as part of the editing process or the editor would have to nd such an ordering. Requiring the programmer to provide the ordering would be a very heavy burden. It would also be very dicult to build an editor capable of nding such orderings. Automating this task is a hard and open research problem (see x12 for a discussion). However, the main reason for avoiding this approach is that it requires the programmer to produce candidate procedures without guidance and then test them for termination. On the other hand, the recursion editor provides considerable guidance in the production of new procedures by allowing the user to use existing procedures or procedure schemata as a starting point, and then allowing only manipulations of them that preserve termination and well-de nedness. Each of the kinds of recursion in Peter's book is guaranteed to terminate since a well-ordering can be inferred from its structure. So there is no need to de ne a procedure-speci c ordering. Since she covers many of the common kinds of algorithmic recursion that arise in programming, restricting the editor to these kinds still produces a useful tool. It could easily be extended, in a piecemeal fashion, to cover other kinds of recursion, as required. It can also be combined with the well-founded ordering approach to produce a hybrid tool which is more useful than either approach on its own (see x12). Peter's analysis implies that there are a number of dierent kinds of recursion. But, in fact, these dierent kinds can be readily combined, so her account is better seen as a series of orthogonal dimensions than as a disjoint classi cation. Each dimension gives a way of varying a basic primitive recursive schema so as to retain the two crucial properties of termination and well-de nedness. However, the task 2
of combining these dimensions was our major design challenge. Providing a set of schemata corresponding to each of Peter's kinds of recursion from which the Prolog programmer had to choose and instantiate, would have been a fairly straightforward exercise. Providing a set of editing commands corresponding to each of the dimensions, and allowing these commands to be combined in any way and still produce a valid recursive procedure, was a dicult piece of research. In x7 we will consider the following kinds of recursion given in Peter's book: multiple recursion, course of values recursion, simultaneous recursion and substituted parameter recursion. There are problems with her nested recursion, which are discussed in x11.3. We will not discuss her further kinds: trans nite recursion and higher degree recursion. These are not really kinds of recursion in the same spirit as the others. Trans nite recursion is a general purpose de nition of recursion in which one has to supply a well-founded ordering, ie it is the general purpose alternative approach rejected above. Higher degree recursion is recursion in higher order logic. This is not required for Prolog procedures, which are all rst order. Each of the kinds of recursion we discuss gave rise to an editing command. In addition, we needed some other commands so mundane as to have been omitted from Peter's book, but required to support the others. For instance, we have a range of non-recursive commands for making nested procedures, creating extra cases, etc. Also some new kinds of recursive command were required because we allow recursion over arbitrary data-structures instead of just natural numbers, as in Peter's book. As Peter shows, all the kinds of recursive procedure accepted by the recursion editor can be transformed to an equivalent primitive recursive procedure. However, this equivalence preserves input/output behaviour, but not the underlying algorithm or its complexity. Thus, from a programming point of view, it is worthwhile to provide an editor to build these extended kinds of recursion. It would not be enough to provide only a primitive recursive schema and insist that all recursive procedures be instantiations of this.
3 Logic vs Prolog It would have been nice if the editor could have worked directly with Prolog code, but there is a technical problem with doing this. Consider the Prolog procedure, even=1, for testing whether a natural number represented as a nested term, s(s( (0) )), is even.
even(0): even(s(s(X ))) : ? even(X ): What happens to the goal ? ? even(s(0))? This will not match the head of either
clause, so will fail. In Prolog this failure is conventionally interpreted as falsity. This convention is called the closed world assumption and is used to justify negation as failure. However, we want the editor to check that the procedure is de ned for all appropriate inputs. In the above situation we have no way of knowing whether the input s(0) has been considered and decided to be false, or whether it has been overlooked. Our solution to this and similar problems is that it should be factored out by forcing the user to program in `real' predicate logic rst, in which sets of, possibly conditional, equations or equivalences are produced instead of clauses, and then these should be translated into Prolog clauses by employing the closed world assumption. We will consider procedures represented either as constants, functions, predicates or connectives of the logic. We will call each (conditional) equation or equivalence a case, rather than a clause. Following Prolog convention, we will call 3
the left hand side of each equation or equivalence the head and the right hand side the body. Note that when we want to de ne a Prolog procedure, foo=n + 1 which takes n inputs and returns a unique output then we should represent it as an n-ary function and use = as the equivalence relation for the cases. The termination and wellde nedness of the resulting Prolog procedure will only be ensured for the mode foo(+; : : : ; +; ?). When we want to de ne a Prolog procedure, bar=n, which may be used in several modes or which is not guaranteed to return a unique output in some mode, then we should represent it as an n-ary predicate and use $ as the equivalence relation. The termination and well-de nedness of the resulting Prolog procedure will only be ensured for the mode bar(+; : : : ; +). The Prolog procedures foo and bar can still be used in other modes, but the properties of termination and well-de nedness may be lost. If an argument marked + is used in mode - then the procedure may be over-de ned, i.e. return multiple results, and it may fail to terminate. Often being multi-valued and/or non-terminating are desired properties. If an argument marked - is used in mode + then the procedure may be under-de ned, i.e. fail when it should succeed. This is seldom desired. Hence, in the rest of this paper we will use the standard terminology of logic, rather than Prolog. In this form the even=1 example will be:
even(0) $ true even(s(0)) $ false even(s(s(X ))) $ even(X ) The translation from sets of conditional equations or equivalences into Prolog is given in section x10. How do we know that these three cases cover precisely the appropriate inputs to even=1 in a non-overlapping way? To ensure this we will further assume that the procedures are all de ned in typed predicate logic and the argument positions of each procedure take only inputs from a particular type. For instance, the argument of even=1 will take only natural numbers. For each of these types we will assume that there is a 1-1 correspondence between the members of the type and the terms formed from the constructor functions of that type, eg the natural numbers will correspond to the set f0; s(0); s(s(0)); : : : g where s and 0 are the constructor functions for natural numbers. However, the editor will not need a speci c mechanism for de ning new types and checking the types of procedures' arguments. Rather the types will be de ned implicitly by the commands for creating, modifying and instantiating constructor functions. Initially, each recursive type, , is de ned by the schematic base constructor function, [, and the schematic step constructor function, ]( ; ). That is, [ is of type and if is of type then, for any , ]( ; ) is of type . Some of the commands we will see below will: increase the number of base and step constructor functions; add new and remove old recursive and parameter arguments; and instantiate them to particular constructor functions. Thus the user can de ne the natural numbers by having the constructor functions 0 and s() and the lists by having the constructor functions [] and [ j]. S/he can even construct weird hybrids, e.g. by using 0, [] and s().
4 Why Well-De ned and Terminating? We have designed the editor to enforce the well-de nedness and termination of procedures because non-terminating procedures and under- or over-de ned procedures 4
are a major source of Prolog bugs. By enforcing these properties we can eliminate such bugs. For instance, by ensuring that all procedures terminate we prevent the kind of bug illustrated by the following example:
gcd(X; X ) = X gcd(X; Y ) = gcd(X ? Y; Y ) where gcd(2; 3) = gcd(?1; 3) = gcd(?4; 3) = .
This example also illustrates the problem of over-de nedness. Note that gcd(2; 2) is de ned to be 2 by the rst case and gcd(0; 2) by the second. Which value is returned will depend on the vagaries of the interpreter and whether the value is being returned after backtracking or not. In other words, an over-de ned procedure consists of an inconsistent set of cases. Under-de nedness is illustrated in the following example:
X > Y ?! gcd(X; Y ) = gcd(X ? Y; Y ) X < Y ?! gcd(X; Y ) = gcd(Y; X ) which is unde ned for the situation where gcd has equal arguments, eg for gcd(2; 2). This will, in fact, prevent gcd giving a value for any combination of inputs. One might think that well-de nedness was only of interest when the procedure to be de ned was a function, ie when its output was guaranteed to exist and was unique. However, in the formalism we have de ned above, it is also a required property of procedures that are predicates. Consider, for instance, the predicate version of append=3:
append([]; L; L0]) $ L = L0 append([HdjTl]; L; []) $ false append([HdjTl]; L; [Hd0jTl0]) $ append(Tl; L; Tl0) Instead of omitting the second case here, as one would in Prolog, it is included, but is set equivalent to false. Under-de ning the predicate would indicate that a legitimate combination of inputs/outputs had not been considered. Similarly providing two cases for the same combination of inputs/outputs would indicate an inconsistency. That a predicate returns several distinct outputs for a goal, eg append(L1 ; L2; [a; b]), is represented by the matching of this goal or one of its subgoals against more than one case. Unfortunately, the recursion editor is not capable of de ning all terminating and well-de ned procedures. The basic design of the editor limits it to procedures that can be shown to terminate by induction on the recursive structure of their arguments. The incompleteness of the set of editing commands means that not even all such procedures can, in fact, be de ned. Similarly, the editor design limits it to procedures that can be shown to be well-de ned because of the syntactic structure of their arguments. Examples of terminating and well-de ned procedures that are beyond the scope of the editor are given in x11, along with a discussion of how these limitations might be overcome. Furthermore, there are Prolog procedures that are intentionally non-terminating or ill-de ned. For instance, any procedure designed to keep up an inde nite dialogue with the user, eg an operating system, is usually intended not to terminate. Similarly, the procedure, gensym which generates a new constant symbol each time it is called is designed to be over-de ned. And a procedure for division of real numbers may be under-de ned by omitting the case when the divisor is zero. Under-de ned procedures cause no problem since the body of the case can be given as false if the procedure is a predicate or ? if it is a function. Such cases will then either be 5
omitted when the procedure is translated into Prolog or will be de ned to give an error message. Non-terminating and over-de ned procedures just cannot be generated by the recursion editor, and must be dealt with in some other way, eg by an editor geared to some other set of techniques.
5 The Nature of the Editor Our decision to represent the various forms of recursion as orthogonal dimensions of variation on a basic schema argues for the following form for the recursion editor. The user will be presented with a basic schema or a previously generated procedure, and a menu of editing commands. The user will use these commands to edit the schema/old procedure into the new procedure. Each time a command is selected and executed the schema will be altered in a way that continues to guarantee its termination and well-de nedness. Executing a command consists of choosing the command required, from among those applicable, and then specifying its inputs. Inputs are speci ed by pointing to a particular procedure, case or sub-expression, according to the requirements of the command. In the current implementation the interface for this is rather primitive, but there are many possibilities for improvement. For instance, when a command is selected the user could be prompted for its inputs. The mouse could be used to point to the command inputs. These inputs could be highlighted, eg by being shown in reverse video. All the equivalent parts could be simultaneously highlighted in order to clarify the nature of the choice being made. For instance, if a variable is being renamed then when one occurrence in a case is pointed at then all the other occurrences in that case should also be highlighted. On the other hand, if it were a procedure being renamed then all occurrences in the schema could be highlighted. This would clarify, to the user, the scope of the renaming. In choosing the schemata that the user starts from we had to balance two opposing considerations. On the one hand the schema should be as simple as possible in order that its structure should be transparent and easy for the user to understand. On the other hand it should contain as many of the principal variations as possible in order to reduce the number of editing commands whose meaning the user has to remember, and that have to be applied in a given session. The compromise we describe below is to have two basic schemata: a recursive and a non-recursive one. For the recursive schema we have used essentially the same starting point as Peter, namely the schema for primitive recursion, but generalised to deal with the rich variety of data-structures found in computer programming. These decisions are not absolute. Indeed, there are discussions below about how they might be varied.
5.1 The Procedure Schemata
The version of the primitive recursion schema we have started with is:
* () ([; ) ) (]( ; ); ) * ) ( ; ; ; (; )) where:
is the recursive procedure being de ned; and are the procedures it is being de ned in terms of; is the recursion variable; is the parameter; 6
[ and ] are the constructor functions;
is the constructor parameter; * ) is the equivalence relation. This schema incorporates the following generalisations over the one in Peter's book. Firstly, the data-structure of natural numbers has been generalised to one in which the base constructor is [ rather than 0, and the step constructor is ] rather than s. Note that we have given ] two arguments: is the standard recursion variable, but is an additional parameter to represent constructors like [ j], that take non-recursive arguments. also needs to be a parameter of . Secondly, we have generalised the = relation to any equivalence relation, represented by the symbol * ). Thirdly, we have used Greek letters and music symbols to represent metavariables ranging over procedures, variables, etc, and will use Roman letters to represent actual procedures, variables, etc. The conventions are: { lower case Greek letters, with and without subscripts, range over procedures; { upper case Greek letters, with and without subscripts, range over variables; { ], with and without subscripts, ranges over step recursive constructor functions; { [, with and without subscripts, ranges over base recursive constructor functions, ie the empty data-structures; and * { ), with and without subscripts, ranges over equivalence relations. Fourthly, we have followed the Prolog convention that words starting with capital letters represent variables and those starting with lower case letters represent constants. This convention is followed both by the Greek letters above and the Roman letters that are used to instantiate them. Note that any procedure tting into the primitive recursion schema is guaranteed to terminate and to be well-de ned. These properties are guaranteed to be retained by any procedures formed by invoking the editing commands. By providing this schema and the commands to instantiate it we enable the user to de ne a procedure to calculate any primitive recursive function | by some algorithm. >From a programming point of view this is not enough. We also want the ability to design the most ecient algorithm for calculating each function. It is impossible to build a complete recursion editor,i.e. one that can build any algorithm, but we have tried to provide commands for most of the commonly occurring algorithmic techniques. For the non-recursive schema we have chosen the following very simple case: () * ) () where is the procedure being de ned and is some previously de ned procedure. As before is a parameter and * ) is an equivalence relation. The recursion editor has commands to create new copies of both this and the primitive recursive schema. These commands are called \Create non-recursive schema" and \Create recursive schema", respectively. On calling one of them a new schema is created with the name of its procedures and variables standardised apart from previous ones. 7
5.2 The General Form of Procedures
In order to describe the behaviour of the editing commands it is not enough to say what they do to these initial schemata. After a few commands have been executed they will have to operate on a partially developed procedure consisting of several elaborated schemata. We need to give a general de nition of a procedure in an initial, nal or intermediate stage of development, and describe the behaviour of the commands on that. Below we de ne a program schemata, ie a program constructed only of meta-symbols. The elaborated program schemata manipulated by the editor consist of such program schemata in which the meta-symbols have been partially instantiated with actual procedures, variables, etc. A program schemata is a set of procedure schemata. A procedure schemata for a procedure is a set of case schemata for . A case schemata for a procedure is a formula of the form:
Head * ) Body or
Condition ?! Head * ) Body where:
Head has the form (Arg1 ; : : : ; Argn ), where:
if i is a parameter argument position of then Argi is a parameter variable, i , or if i is a recursion argument position of then Argi is a recursion argument. is a predicate if and only if * ) is =. ) is $. is a function if and only if * A recursion argument has one of the forms: { [( 1 ; : : : ; n ) where each i is a constructor parameter variable; or { ](Arg1 ; : : : ; Argn ) where: if i is a parameter argument position of ] then Argi is a constructor
parameter variable, ; or if i is a recursion argument position of ] then Argi is either a recursion variable, , or a recursion argument.
If the ith argument of the head of a case is a recursion argument position and
a recursion variable is embedded in it then the case is a step case for that argument position. If the ith argument is a recursion argument and does not contain a recursion variable then it is a base case for that argument position. Body is a term or a formula according to whether * ) is = or $. Condition is a formula. A term is either a variable, a constant, ? or a function applied to some terms. A formula is either a predicate applied to some terms or a connective applied to some formulae. A connective is either ^, _ or :. 8
6 Non-Recursive Editing Commands In this section we give a brief description of each of the non-recursive commands. In the next section we describe the recursive commands.
6.1 Changing the Arity of Procedures
One of the rst things that the user would want to do is to change the arity of the various procedures. For instance, in the primitive recursive schema: ([; ) * ) () (]( ; ); ) * ) ( ; ; ; (; )) s/he might want to have two parameters, and 0 , rather than just one, or s/he might want not to depend on its second argument, . We will need to be able to distinguish between the various kinds of argument that each procedure can take, rather than just specify an arity number for each procedure, eg we will need to distinguish a binary that has two recursion arguments from one that has a recursion argument and a parameter. In fact, there are three1 kinds of command required: 1. one that refers to the argument position of a procedure, eg ( ; ; ; (; )) does not depend on its third argument, | which should produce ( ; ; (; )) (note that still depends on ); 2. one that refers to a particular variable, eg the whole schema requires no parameter | which should produce ( ; ; ()) (note that is removed from both and ). 3. and a third that refers to a particular type of variable, eg the whole schema requires an extra parameter 0 | which should produce ( ; ; 0 ; ; (; ; 0)) (note that 0 is added to and to ). Note that the rst command is global to all occurrences of the procedure in the schema, whereas the second and third commands are global to all occurrences of the variable in the schema. The rst and second commands are only required to remove arguments. The third command is required to add variables. The rst command cannot apply to the procedure being de ned, or , since removing a dependency here and not removing the corresponding dependency in the case bodies could result in an ill-de ned procedure. The rst command is required to remove whatever is in the argument position indicated | variable or complex expression. The second and third commands are restricted to the removal/addition of parameters. Applying them to recursion variables creates all kinds of diculties, for which we require two separate commands, explained in x7.1 and x7.2. The rst command is called \Remove dependency". On selecting this command, the user must indicate the argument position to be removed. All and only occurrences of that position in that procedure are removed. For instance, if the fourth occurrence of were indicated in the primitive recursive schema then only that occurrence of should be removed, unless appeared elsewhere in the program. The expressions to be removed are indicated below by an arrow. ([; ) * ) () (]( ; ); ) * ) ( ; ; ; (; ))
"
1 Although it would be possible to merge two or more of these commands and use their inputs to distinguish the dierent meanings.
9
The second command is called \Remove parameter". On selecting this command, the user must indicate the (constructor) parameter to be removed. All its occurrences will be removed. For instance, if the fourth occurrence of were indicated in the primitive recursive schema then all occurrences of will be removed. Again, the expressions to be removed are indicated below by an arrow.
([; ) * ) ( )
"
"
(]( ; ); ) * ) ( ; ; ; (; ))
"
"
"
The third command is called \Add parameters". On selecting this command, the user must indicate the position at which the new parameters are to be inserted. Below we indicate the insertion of a parameter between two existing arguments by indicating the comma between them and the insertion of a parameter at the end of a procedure by indicating the end bracket. To indicate that a constructor parameter is required the user should indicate an occurrence in a constructor function, otherwise a regular parameter will be inserted. For instance, if the user chose to add a new regular parameter to the primitive recursion schema then s/he might indicate the positions marked by arrows below.
([; ) * ) ()
"
"
(]( ; ); ) * ) ( ; ; ; (; ))
"
The user must also indicate the number, n, of new (constructor) parameters to be added. n new (constructor) parameters are then added in the indicated positions. For instance, if n were 1 in the example above the result would be:
([; ; 0 ) (]( ; ); ; 0 )
* ) (; 0 ) * ) ( ; ; ; 0 ; (; ; 0 ))
The provision of the \Add parameters" command suggests an alternative basic recursion schema to that given in x5.1. We could have omitted all regular and constructor parameters to get the schema:
([) * ) (]()) * ) (; ()) and then added them back in, as required, using \Add parameters". The advantage of this version is its simplicity. The disadvantage is that two principal variations are omitted from the schema and, hence, may not suggest themselves to the user.
6.2 Reordering Arguments
Having got the right arguments into the various procedures we might then want to reorder them, either for aesthetic reasons or because we want to instantiate a meta-procedure to be some known procedure which takes its arguments in a dierent order to the current one. We have chosen to do this by having a command which can move an existing argument to a new position. This can be applied repeatedly until the desired order is arrived at. The user selects the command \Move argument" from the menu and then indicates the argument to be moved and also the position it is to be moved to. This argument is moved to the new position in every occurrence of the procedure in the program. 10
For instance, suppose the user wants to reverse the position of the recursion argument and the parameter in in the primitive recursive schema:
([ ; ) * ) ()
" "
(] ( ; ); ) * ) ( ; ; ; (; ) )
"
" "
"
S/he should select \Move argument" and indicate any of the pairs of arrowed positions. All of these will then be moved producing the following schema:
* () (; [) ) (; ]( ; )) * ) ( ; ; ; (; ))
6.3 Instantiating the Schema
Another command that the user will probably want to use early is to rename all or some of the procedures and variables in the schema. For instance, should receive some mnemonic name corresponding to the procedure being de ned, and should receive some mnemonic names corresponding to the arguments of this procedure. We can see this as instantiating the meta-symbols ranging over procedures, variables, etc in the schema, with actual procedures, variables, etc. The user might also want to rename some already named symbols either because s/he has made a mistake or changed his/her mind or because s/he wants to use an old procedure as a starting point rather than start with a fresh schema. For instance, instantiating the primitive recursive schema:
([; ) * ) () (]( ; ); ) * ) ( ; ; ; (; )) with the following substitutions:
f= = * ); append=; []=[; [: : : j : : :]=]; Hd= ; Tl=g together with Lb = in the rst equation and Ls = in the second, gives:
append([]; Lb) = (Lb ) append([HdjTl]); Ls) = (Hd; Tl; Ls; append(Tl; Ls))
(1)
Note that the scope of the procedure renaming is global, whereas the scope of the variable renaming is local to each case. We have emphasised this above by instantiating dierently in each case, but even if had been instantiated to the same variable name in each case, the two variables would be distinct. A useful extension to the current implementation would be to indicate such scope by highlighting. Instantiation is eected by selecting the command \Rename", indicating the symbol to be renamed, and inputting its new name. The editor should ensure that the renaming is legitimate. This is currently only partially implemented. It makes the following checks: Variables must be renamed to variables and constants to constants, etc. Step constructors must not be nullary. Procedures instantiated to already de ned procedures must have the right number of arguments in the right order. It also does some limited type checking. 11
The equivalence relation, * ) must be the name of some known or to be de ned equivalence relation. Currently it is restricted to be = and $. The position of a function, predicate, or connective in a term or formula must respect the syntactic formation rules for terms and formulae given in x5 above. Dierent parameters in the head of a procedure must continue to have dierent
names. In addition, the editor should maintain a dependency graph of procedure names, similar to that created by a cross-reference program, but this is not yet implemented. Each de ned procedure, or , should be connected by an arrow to each of the procedures in terms of which it is de ned, , , or 2 . The graph should not be allowed to become cyclic except for specially sanctioned recursive procedures, ie the user should normally be prevented from creating a de nition that would make it cyclic. A procedure should label a leaf if and only if it is a constructor function or a system procedure, ie the user should be not allowed to de ne a constructor function or a system procedure, and the user should be prompted to de ne any other unde ned procedure in the graph. As an example of using \Rename",the user might instantiate and in the schema (1) above to the identity function and the list constructor function, respectively, which gives the following de nition:
append([]; Lb ) = Lb append([HdjTl]); Ls) = [Hdjappend(Tl; Ls)] Before renaming as the list constructor its unwanted dependencies should be removed with \Remove dependency", that is, its second and third arguments should be removed. If the user wants to produce a partial function, ie one left unde ned for some combination of appropriate inputs, then s/he should instantiate the body of the relevant case to be ?. This ensures that the case has been deliberately left unde ned rather than just overlooked. When translating the procedure into Prolog the editor will ensure that this case does not become part of the procedure.
6.4 Turning Procedures into Expressions
In the last section we discussed instantiating the body procedures: , and with procedure names but, in general, it is unlikely that just the right procedure would be available3. Instead we are more likely to want to instantiate the bodies as compound expressions. This is done by providing a command,\Insert procedure", to insert new procedures as extra arguments to the body and condition procedures. The input to the command is the position for insertion. A new procedure meta-symbol is then be invented and inserted into that position. The arguments of that procedure are the same as the original arguments of the procedure it was inserted into. The editor ensures that the conditions attaching to the old, inserted-into procedure4 are also applied to the new, inserted-in procedure. For instance, suppose the user is de ning a test that one list is a sorted version of another. Suppose s/he has de ned the case:
sort(L; SL) $ (L; SL)
"
2 3 4
A condition predicate: see x6.5 below. Especially in the case of , since then it would be pointless to de ne . Eg that it is not dependent on or .
12
then, with two calls of \Insert procedure" and the indication of the two arrowed positions, the procedure is converted to:
sort(L; SL) $ (L; SL; 1(L; SL); 2 (L; SL)) The user might then use repeated calls to \Remove dependency" and \Rename" to convert this to:
sort(L; SL) $ ordered(SL) ^ permutation(L; SL)
6.5 Introducing Conditional Cases
The recursive and non-recursive schemata given above provide a xed number of cases for each procedure. We need a command for creating new cases. This can be thought of as multiplying an existing case into several sub-cases, using a command \Multiply cases". On selecting this command the user must indicate one of the existing cases and input a positive integer, n, for the number of cases with which it should be replaced. The editor uses:
:1 ^ ^ :i?1 ^ i as the ith condition, where i for i < n is a predicate applied to the existing variables in the procedure. n is true and is omitted. The user can then edit these i s further, as required. For instance, suppose the user is de ning a procedure, prune, to remove each occurrence of an element from a list. If the user already has the case:
prune([HdjTl]); El) = (Hd; Tl; El; prune(Tl; El)) and wants to create two cases for when El is and is not equal to Hd then s/he would select \Multiply cases", indicate this case and input \2". The editor will then produce:
(Hd; Tl; El) ?! prune([HdjTl]); El) = (Hd; Tl; El; prune(Tl; El)) :(Hd; Tl; El) ?! prune([HdjTl]); El) = (Hd; Tl; El; prune(Tl; El)) The user might then use repeated calls to \Remove dependency" and \Rename" to convert this to:
Hd = El ?! prune([HdjTl]); El) = prune(Tl; El) Hd 6= El ?! prune([HdjTl]); El) = [Hdjprune(Tl; El)]
6.6 Undoing Editing Commands
Lastly, among the non-recursive commands, we consider the inevitable \Undo" command. Users are bound to call commands that they did not intend to or to call commands in the wrong order. Below we describe a simple version of \Undo" which keeps the history of previous commands (except \Undo"s) as a list and reverses the eect of the most recent one on each call of \Undo". This has not yet been implemented. Currently, commands can only be undone by executing their inverses, where these exist, which they do in most cases. We have tried to minimise the problem of executing commands in the wrong order by making them commutative, but it is not possible to achieve this in all situations, since some commands have preconditions which only other commands can achieve. For instance, before instantiating a schematic procedure name to an 13
already de ned procedure it is necessary to give it the right number and type of arguments in the right order. Some more sophisticated error avoidance and recovery assistance is proposed below, but none of these proposals have been implemented. Some problems of ill-ordered commands might be avoided by detecting and disallowing a premature command and prompting the user to select some other command, eg rejecting \Rename" and suggesting that the user select \Remove dependency", \Add parameters" or \Move argument" rst. More subtly, suppose the user wanted to use lists as trees and instantiated a ] with one constructor parameter and one recursion variable. It might be a long time before s/he realised that both arguments should have been recursion ones. S/he would want to change some earlier commands to give ] the correct argument types, but leave the later commands unaltered. A more sophisticated \Undo" command would enable back-up to the earlier state, allow the changes to be made, and then re-execute the later commands. Neither of these extensions have been implemented. It would be nice to display the history to the user graphically, eg as a tree where the commutative commands were represented as daughters of the same mother node. The user would then select with the mouse the command(s) to be undone, insert some new ones, and the editor would try and execute the new tree.
7 Recursive Editing Commands We now turn (at last) to the main point of this paper, which is to describe the recursive editing commands. Each of the sections and commands below corresponds to one of the orthogonal dimensions implied by Peter's classi cation. The names have changed because the natural mnemonic for the command is not usually the name that Peter chooses to describe the kind of recursion. The translation is given in table 1. Editing Command Add recursion argument Remove recursion argument Add constructor recursion arguments Remove constructor recursion argument Multiply base constructor Multiply step constructor Insert constructor function Insert parameter procedure Insert recursive procedure
Recursion Type Multiple none none none none none Course of values Substituted parameter Simultaneous
Table 1: The Translation between Editing Commands and Recursion Kinds
7.1 Adding New Recursion Arguments
In x6.1 we excluded the use of the \Remove parameter" or \Add parameters" commands from applying to new recursion variables because of complications that arise in the recursive case. In this section and the next we address these complications 14
and design commands to add and remove new recursion variables. In fact, these commands need to be construed as adding/removing new recursion arguments not recursion variables. If we add another recursion argument to a procedure then we need not only to give an extra argument but also to double the number of cases; each old case is replaced with one in which the new argument takes the base value and one in which it takes the step value. Schematically, each old case:
Condition ?! []][]] * ) [[]] where Condition may be empty and where ]][] is a typical recursion argument of in the head where is a corresponding recursion argument of in the body, is replaced by two new cases:
Condition ?! []][]; [0 ] * ) b [[; [0 ]] Condition ?! []][]; ]0 ( 0 ; 0 )] * ) s [ 0 ; 0 ; [; ]0 ( 0 ; 0 )]; []][]; 0 ]; [; 0 ]] for some new constructor functions, [0 and ]0 , recursion variable, 0 , and constructor parameter, 0. Note that the double symbol notation, e.g. , is intended to indicate a term which may be compound, as opposed to , which indicates a variable. The new arguments have been placed in last position to simplify the transformation, but could have been placed elsewhere. This transformation ensures that is de ned for all values of the new argument and gives the maximum scope for de ning the procedure, , while ensuring that it terminates. The new command is called \Add recursion argument". When selecting this command the user must indicate the position of the new recursion argument in the de ned procedure. For instance, suppose the user wants to write a predicate version of append and has already renamed the primitive recursive schema as follows:
append([]; L) $ (L) append([HdjTl]; L) $ (Hd; Tl; L; append(Tl; L))
"
append needs 3 arguments, 2 of which should be recursive. so the user should select the command \Add recursion argument" and indicate the arrowed positions. The editor will then modify the de nition of append as follows: append([]; L; [0 ) append([]; L; ]0 ( 0 ; 0 )) append([HdjTl]; L; [0) append([HdjTl]; L; ]0( 0 ; 0 ))
$ $ $ $
b (L) s (L; 0; 0 ; append([]; L; 0)) b (Hd; Tl; L; append(Tl; L; [0)) s (Hd; Tl; L; 0; 0 ; append(Tl; L; ]0( 0 ; 0 )); append([HdjTl]; L; 0); append(Tl; L; 0))
The user might then use repeated calls to \Remove dependency" and \Rename" to convert this to:
append([]; L; []) append([]; L; [Hd0jTl0]) append([HdjTl]; L; []) append([HdjTl]; L; [Hd0jTl0]) 15
$ $ $ $
L = [] L = [Hd0 jTl0] false append(Tl; L; Tl0)
Note that this form of construction produces four cases rather than the more normal two. The third case will subsequently be discarded during the translation to Prolog. It would be nice to be able to merge the rst two cases to make:
append([]; L; L0) $ L = L0 and it is worth considering a command to do this, but we have not explored this issue further here. To remove recursion arguments we have a command \Remove recursion argument". The only occasions on which this might be used is to remove a recursion argument that had previously been added or to remove the original recursion argument, thereby turning a recursive procedure into a non-recursive one5. In either case the need only arises if a mistake has been made, and the \Undo" command could have been used instead. It is, therefore, not worth discussing this command at length. It suces to say that its eect would be to shrink all the base and step cases for that argument position into one case. This command is not what is needed to merge the rst two cases of append=3 above, since it would also have the unwanted eect of merging the last two.
7.2 Adding New Constructor Recursion Arguments
There is another way of adding recursion arguments that increases the complexity of the step constructor without increasing the number of cases. It is needed, for instance, when recursing on lists considered as trees rather than as vectors. The idea is to add extra recursion arguments to a constructor function. The command for doing this is \Add constructor recursion arguments". The inputs for this command are a step constructor, ], the number of extra arguments, n, (a positive integer) and the positions at which they should be added. The command adds n extra variables, 1 ; : : : ; n , to each occurrence of ] and to the body of any case whose head has been altered. It also adds extra recursive calls to each body procedure, , in which is applied to i . Schematically, each old case:
Condition ?! [][]] * ) [[]] where Condition may be empty and where is a typical recursion argument of a typical occurrence of ], is replaced by a new case: Condition ?! [][; 1 ; : : : ; n ]] * ) [[]; [1 ]; : : : ; [n ]] For instance, suppose the user is de ning a procedure, flatten, to produce the fringe of a binary tree, and has so far de ned:
flatten([) = flatten(] ()) = (; flatten())
"
S/he could proceed by selecting \Add constructor recursive arguments", indicating the arrowed occurrence of ] and inputting 1. This would produce the schema:
flatten([) = flatten(](; 0)) = (; 0 ; flatten(); flatten(0)) 5 This command would, therefore, enable us to dispense with the non-recursive procedure schema, but this is not a particularly attractive option. Similarly, the command \Add recursion argument" would enable us to dispense with the recursive procedure schema.
16
which by later applications of \Add Parameter", \Rename" and \Remove dependency" could be instantiated to:
flatten(leaf (Label)) = [Label] flatten(tree(Left; Right)) = append(flatten(Left); flatten(Right)) We could also have a command \Remove constructor recursion argument" analogous to \Remove recursion argument". We will not discuss this in detail since it would probably not be very useful and has not been implemented.
7.3 Introducing Several Constructors
The \Add constructor recursion arguments" command was required because we are allowing recursion on a wide range of recursive data-structures and not just natural numbers. This extension of natural number recursion permits not just a variety of constructor functions with varying numbers of arguments in dierent procedures, but the same thing within a single procedure. To allow the user this exibility we will need two more commands: \Multiply base constructor" and \Multiply step constructor". The inputs for these commands are the base or step constructor to be duplicated and the multiplication factor, n, an integer greater than 1. The commands duplicate each case containing that constructor to create n cases, with a dierent copy of the constructor in each case. If a step constructor function is recursing in multiple steps (see x7.5) then this transformation is not guaranteed to preserve total de nedness. Therefore, we restrict it to the multiplication of step constructor functions that are recursing only in single steps. In practice, this means that this command must be called before the command \Insert constructor function" described in x7.5. Schematically, the old case:
Condition ?! [][]] * ) [[]] where Condition may be empty and where ][] is a typical (sub-)recursion argument of in the head in which is a corresponding recursion argument of in the body, is replaced by n new cases: Condition ?! []1 []] * ) 1 [[]] .. .
.. .
.. .
Condition ?! []n []] * ) n [[]] where ]1 to ]n are n new constructor functions. For instance, suppose the user was trying to build a procedure boole eval=1 which takes a propositional formula consisting of the connectives: :; $ and _ applied to the truth values true and false, and returns 1 or 0 according as it is true or false. Suppose the following stage has been reached:
boole eval([) = boole eval(]()) = (boole eval()) two base constructors and three step constructors are needed, modelled on the existing ones. First the command \Multiply base constructor" should be selected, [ indicated, and the factor \2" input. Then the command \Multiply step constructor" should be selected, ] indicated, and the factor \3" input. This will result in the schema: boole eval([1 ) = 1 17
boole eval([2 ) boole eval(]1 ()) boole eval(]2 ()) boole eval(]3 ())
= 2 = 1 (boole eval()) = 2 (boole eval()) = 3 (boole eval()) After applications of \Add constructor recursive arguments", \Rename", etc, these can be converted to: boole eval(true) = 0 boole eval(false) = 1 boole eval(:Fm) = boole eval(Fm) + 1 mod 2 boole eval(Fm1 $ Fm2 )) = boole eval(Fm1 ) + boole eval(Fm2 ) mod 2 boole eval(Fm1 _ Fm2 )) = boole eval(Fm1 ) boole eval(Fm2 )
7.4 Augmenting the Parameter
The primitive recursive schema given in x5.1 assumes that , the parameter of , is the same on both sides of the step case, but there is no reason for this to be the case. The parameter of in the body of the step case could be any procedure of which does not depend on 6 , and the recursion would still terminate. There could be several recursive calls of each applied to a dierent procedure of . To construct these more complex parameters the editor provides a command called \Insert parameter procedure". The input to this command is the parameter to be augmented, . The command duplicates the recursive call containing this occurrence of . In the new copy of the recursive call is replaced by a procedure of itself and the other variables of the step case. Schematically, the old case: Condition ?! []][ ; ]; ] * ) [ ; ; ; [: : : ; ]] where Condition may be empty and is a typical constructor parameter and is a typical recursion argument, is replaced by the case: Condition ?! []][ ; ]; ] * ) [ ; ; ; [: : : ; ]; [: : : ; ( ; ; )]] where is a new procedure. A common use of this facility is to de ne recursive procedures which use accumulators, eg rev2. Suppose the user had de ned rev2 as: rev2([]; ) = () rev2([HdjTl]; ) = (Hd; Tl; ; rev2(Tl; ))
"
S/he should call the command \Insert parameter procedure" to make into an accumulator. The occurrence of pointed to be the arrow should be indicated. The command will insert a new call in which is replaced by (Hd; Tl; ). This gives: rev2([]; ) = () rev2([HdjTl]; ) = (Hd; Tl; ; rev2(Tl; ); rev2(Tl; (Hd; Tl; ))) The user might then use repeated calls to \Remove dependency" and \Rename" to convert this to: rev2([]; L) = L rev2([HdjTl]; L) = rev2(Tl; [HdjL])) 6
In certain circumstances it can even depend on - see x11.3 below.
18
7.5 Incrementing the Step Size
In this and the next sub-section we describe editing commands that are highly desirable, but have not yet been implemented. Up to now we have assumed that the recursion variable jumps in single steps. Many procedures require jumps in larger steps. A command to enable this might be \Insert constructor function". Its inputs would be the place in the head of a case where a new constructor function is to be inserted and its name. With the insertion of this new constructor function various goals that would have matched this case will do so no longer. To keep the procedure well-de ned the command must create some new cases: typically these will be base cases. The insertion of the new constructor function in the head of the case also raises the possibility of an additional recursive call involving this constructor function in the body. This is included for the sake of completeness. Suppose the original case is given schematically by:
Condition ?! []][ [ ; ]]] * ) [ ; ; [[ ; ]]]
"
where Condition may be empty and where the arrow marks the place where the new constructor function is to be inserted and where [ ; ] is a corresponding recursion argument of in the body, containing constructor parameter, , and recursion variable, . This is replaced by the following new cases:
Condition ?! []][[[]] * ) b [] .. .
.. .
.. .
Condition ?! []][](: : : ; [ ; ]; : : :)]] * ) s [ ; ; [[ ; ]]; [](: : : ; [ ; ]; : : :)]] where there is one new case for each recursion argument [[, of the appropriate type, which is of the form [ ; ] but is not of the form ](: : : ; [ ; ]; : : :), and where and are typical variables not contained in [[. For instance, suppose the user were de ning the bonacci function and had instantiated the primitive recursive schema as follows:
fib(0) = fib(s( N )) = (N; fib(N ))
"
To make fib jump down in steps of 2 rather than 1, s/he should select \Insert constructor function", indicate the arrowed position and name the new constructor function \s". The editor would change the bonacci function as follows:
fib(0) = fib(s(0)) = b fib(s(s(N ))) = s (N; fib(N ); fib(s(N ))) since 0 is a natural number of the form N but not of the form s(N ). The user might
then use repeated calls to \Remove dependency" and \Rename" to convert this to:
fib(0) = s(0) fib(s(0)) = s(0) fib(s(s(N ))) = fib(N ) + fib(s(N )) 19
7.6 Introducing Mutual Recursion
So far we have only considered de ning a single recursive procedure. We need to allow the user to de ne any number of procedures, which together constitute a program. In particular, the user might want to de ne a number of procedures which are mutually recursive. To de ne mutually recursive procedures we need a command, \Insert recursive procedure", to introduce a call to a procedure which is, as yet, unde ned or which depends on . This is a recursive version of \Insert procedure", which does not impose the same conditions of non-dependence on the inserted procedure. will be introduced as an additional argument to , and will be analogous to the existing recursive call(s) to in . In fact, in order to guarantee termination of , must recurse on the same data-structures as . To emphasise this analogy and to simplify the provision of appropriate arguments to , we propose that the user be required to indicate an occurrence of which already provides the right arguments. This may involve the prior creation and subsequent deletion of such an occurrence of . The editor will note that is allowed to depend on . The editor will provide a new schema for , for the user to edit. If the user subsequently renames to a known procedure recursing on the same data-structures then this new schema will be deleted. Schematically, the old case:
Condition ?! []][]] * ) [ []]
"
where Condition may be empty and where is the recursion argument in the indicated occurrence of , is replaced by a new case:
Condition ?! []][]] * ) [[]; []] and a new schema:
.. .
.. .
.. .
.. .
.. .
.. .
Condition ?! []][]] * ) 0 [[]] in which the recursive structure of is initially modelled on that of , that is, each case of gives rise to a case of with the same conditions, head arguments and recursive calls. Note that any subsequent renaming of the constructor functions in either or will automatically rename those in or , respectively. The editor should prevent the application of \Multiple step (base) constructor" to either or from now on, since this might invalidate the guarantee of termination. For instance, suppose the user wants to de ne the predicates odd and even by mutual recursion and so far has de ned:
odd(0) $ false odd(s(X )) $ (X; odd (X ))
"
To introduce a recursive call to even the user should select \Insert recursive procedure" and indicate the arrowed occurrence of odd. The editor will then modify the odd schema to:
odd(0) $ false odd(s(X )) $ (X; odd(X ); (X )) 20
and create a new schema:
* 0 (0) ) (s(X )) * ) 0 (X; (X ))
Note that the editor can infer the right arguments for from those of the arrowed occurrence of odd and it can infer the recursive constructor functions from those for X in the schema for odd. It could also add odd(X ) as an additional argument of 0 , but we do not see any special advantage in this - odd and might, for instance, only be the rst two of a triple rather than a pair of mutual recursants. The user might then use another call to \Insert recursive procedure" on and repeated calls to \Remove dependency" and \Rename" to convert these schemata to: odd(0) $ false odd(s(X )) $ even(X ) even(0) $ true even(s(X )) $ odd(X )
8 Summary of Commands In the last two sections we have introduced a number of editing commands for creating logic programs by creating, augmenting and specialising some basic procedure schemata. These commands are summarised in table 2. In the rst section are those that apply to both recursive and non-recursive schemata. In the second section are those that apply only to recursive schemata. In the third section are those for which a special form of the general command is required for the recursive situation. General Commands Remove dependency Move argument Rename Multiply cases Undo n/a n/a Create non-recursive schema Add parameters ditto Remove parameter ditto Insert procedure ditto ditto
Recursive Commands n/a n/a n/a n/a n/a Multiply base constructor Multiply step constructor Create recursive schema Add recursion argument Add constructor recursion arguments Remove recursion argument Remove constructor recursion argument Insert recursive procedure Insert constructor function Insert parameter procedure
Table 2: Summary of Editing Commands
9 Being More Helpful The editor outlined above is at the machine code level, ie it provides a parsimonious set of commands to do the job. It could be a lot more helpful either by giving hints 21
on what commands to use and how, or by providing higher level commands or both. None of these extensions has yet been implemented.
9.1 Helpful Hints
There are a number of opportunities to ask loaded questions to help the user think about the task in the right way and hence call the right commands. Here are some examples. \Does this procedure really depend on this argument?" If not, then suggest a call of \Remove dependency". \Does the value of this procedure depend on some additional value?" If so, then suggest a call of \Add parameters" or \Add recursion argument". \What are the inputs to and outputs from ?" If dierent from those currently provided as arguments then suggest calls to \Remove parameters" and \Add parameters" as necessary. \What is the value of given the arguments it has in the head and the arguments that or have?" Suggests a call to \Rename" to instantiate or . \What is the name of ?" Suggests a call to \Rename" to instantiate . \Is a function or a predicate?" Suggests a call to \Rename" to instantiate * ). \What kind of object is ?" Suggests a call to \Rename" to instantiate [ and ]. After \Rename" has been called to instantiate to f , say, but before this call is executed: \Does have the right arguments in the right order for f ?" If not, suggests a call to \Remove dependency", \Move arguments", \Add parameters", etc. \Does the value of in the step case depend simply on its value at some earlier stage?" If the earlier stage is not the most recent one then this suggests a call of \Insert constructor function". \Is it helpful to recall when de ning (]( ; ); ) that we can assume we know (; ) for any value of ?" If so, then suggest call of \Insert parameter procedure". \Would it help to de ne in terms of some other procedure which is itself de ned in terms of ?" Suggests call to \Insert recursive procedure". In each of the examples above, the question to be asked could be determined by what the user has so far attempted, eg if the user has not instantiated some of the meta-symbols then the editor would pick those questions which might lead to suggestions as to how to do this. The answers determine not only which commands to call but often what the response to the prompts should be, eg which occurrences to indicate or what name to input. These should all be provided to the user as defaults after the response to the questions has been analysed.
22
9.2 Higher Level Commands
All the commands above are low level. It would be helpful to the user to package them together into more meaningful, high-level commands. For instance, we have implemented a command \Recursion variable is a list" which simultaneously instantiates [ to [], ] to [: : : j : : :], to Hd and to Tl7. This instantiates the standard primitive recursive schema as:
([]; ) ([HdjTl]; )
* ) () * ) (Hd; Tl; ; (Tl; ))
A similar high-level command exists for natural numbers. Another example would be the addition of new conjuncts. A command \Add conjunct" might take the case:
(]( ; ); ) $ ( ; ; ; (; )) and combine two calls of \Insert procedure" and calls of \Rename" and \Remove dependency" to produce:
(]( ; ); ) $ 1 ( ; ; ; (; )) ^ 2 ( ; ; ; (; )) The creation of new schemata to de ne already named procedures could be simpli ed as follows. The user would call one of the \Create (non-)recursive schema" commands and then point at an unde ned procedure in an existing schema. The editor should use the procedure pointed at to name the new procedure and provide it with the right argument structure. If the procedure is changed in the future, eg has its argument structure changed then the editor should alter the versions in both schemata. Previously de ned procedures might be saved by the editor, with the sequence of commands that de ned them, so that they could be used as templates for new procedures. The create schema commands could be augmented so that they allowed such an old procedure to be redisplayed. The user could then continue editing it where s/he left o before. Presumably the de ned procedure would be renamed along with other procedures. Further changes could be as extensive as desired.
10 Translating into Prolog Once we have completely de ned our procedure as a set of equivalences or equations then we need to translate them into Prolog clauses. This can be done with the aid of a checker and the following two compilers: one for predicates and one for functions. The checker ensures that the procedures are all fully de ned before allowing compilation to begin. The compilers are de ned by a series of rewritings which are given below. We also give some optional variations, which have not been implemented. Note that equivalent pure logic procedures may be compiled into non-equivalent Prolog procedures. This is because the compilation process introduces impure features like cut and negation as failure. These are interpreted procedurally and their interpretation can depend on the order of literals and clauses. The compilers preserve the original order of expression nesting and cases as much as possible, and this may determine the behaviour of the target Prolog procedure. We see no way to avoid this problem as long as the target language contains impure features. 7 Hd
and T l being standardised apart with subscripts, as necessary.
23
We are indebted to Allan Ramsay for the following example of this phenomenon. The equivalent pure logic procedures:
p(X ) $ q(X ) ^ :r(X ) p(X ) $ :r(X ) ^ q(X ) will be compiled into the procedurally non-equivalent Prolog procedures:
p(X ) : ? q(X ); not r(X ): p(X ) : ? not r(X ); q(X ):
(2) (3)
Suppose q=1 and r=1 are de ned by the unit clauses:
q(a):
r(b):
If p(X ) is called using clause (2) then it will succeed with a for X ; using clause (3) it will fail.
10.1 Translating Predicates
Since the predicate compiler is simplest, we give it rst. 1. Each case with one of the forms:
Head $ false or Condition ?! Head $ false is deleted. Alternatively, if is only to be used in mode (+; : : : ; +) then each false case could turned into an error message with false replaced by fail, e.g.
Head : ? write(0 error : : :0 ); nl; fail or Head : ? Condition; write(0 error : : :0 ); nl; fail 2. Conditional cases are rewritten from the form:
Condition ?! Head $ Body to the form:
Head $ Condition ^ Body Alternatively, if is only to be used in mode (+; : : : ; +) then a ! can be inserted between Condition and Body and any conjuncts of the form :Atom where Atom occurs in some condition of a previous, undeleted case with an identical head, can be omitted. In a committed choice, non-deterministic, logic programming language, eg Parlog, the guard would be inserted instead of !.
24
3. The following transformation is applied exhaustively. If is a function in the body, that is not a constructor function, and it is not nested inside another such function, then rewrite: Head $ Body(: : : (Arg1 ; : : : ; Argm ) : : :) into: Head $ (Arg1 ; : : : ; Argm ; V ar) ^ Body(: : : V ar : : :) where V ar is a new Prolog variable. 4. Cases are rewritten from the form: to the form:
Head $ Body1 ^ : : : ^ Bodyn
Head : ? Body1 ; : : : ; Bodyn : 5. Each case with a conjunct in the body of the form Body1 _ Body2 is replaced by two cases: one with a conjunct Body1 and one with a conjunct Body2 . 6. Each literal in the body of the case of the form :Atom is replaced by not Atom. 7. Optionally, any atom in the body of a case of the form V ariable = Term or Term = V ariable could be deleted provided each occurrence of V ariable in the case was replaced with Term. For example, suppose we have de ned append as a predicate of 3 arguments, ie: append([]; L; []) $ L = [] append([]; L; [Hd0jTl0]) $ L = [Hd0 jTl0] append([HdjTl]; L; []) $ false append([HdjTl]; L; [Hd0jTl0]) $ append(Tl; L; Tl0)
Applying the above compiler to this set of cases gives: append([]; []; []): 0 append([]; [Hd jTl0]; [Hd0 jTl0]): append([HdjTl]; L; [Hd0jTl0]) : ? append(Tl; L; Tl0): It would be nice to recognise that the rst two clauses can be collapsed into one, but this recognition is arbitrarily hard in general, and not strictly necessary for the procedure to work correctly, so we have not implemented it.
10.2 Translating Functions
Secondly, we give the compiler for translating functional cases into Prolog procedures. 1. Each case of the form: Head = ?
or
Condition ?! Head = ?
is deleted. Alternatively, if is only to be used in mode (+; : : : ; +; ?) then each ? case could be turned into an error message. 25
2. The equational part of the case: (Arg1 ; : : : ; Argn ) = Body is rewritten to: (Arg1 ; : : : ; Argn ; V ar) $ V ar = Body where V ar is a new Prolog variable. 3. Conditional cases are rewritten from the form: to the form:
Condition ?! Head $ Body
Head $ Condition ^ Body Alternatively, if is only to be used in mode (+; : : : ; +; ?) then a ! can be inserted between Condition and Body and any conjuncts of the form :Atom where Atom occurs in some condition of a previous, undeleted
case with an identical head, can be omitted. In a committed choice, nondeterministic, logic programming language, eg Parlog, the guard would be inserted instead of !. 4. The following transformation is applied exhaustively. If is a function in the body, that is not a constructor function, and it is not nested inside another such function, then rewrite: Head $ Body(: : : (Arg1 ; : : : ; Argm ) : : :) into: Head $ (Arg1 ; : : : ; Argm ; V ar) ^ Body(: : : V ar : : :) where V ar is a new Prolog variable. 5. Cases are rewritten from the form: to the form:
Head $ Body1 ^ : : : ^ Bodyn
Head : ? Body1; : : : ; Bodyn ; !: 6. Each case with a conjunct in the body of the form Body1 _ Body2 is replaced by two cases: one with a conjunct Body1 and one with a conjunct Body2 . 7. Each literal in the body of the case of the form :Atom is replaced by not Atom. 8. Optionally, any atom in the body of a case of the form V ariable = Term or Term = V ariable could be deleted provided each occurrence of V ariable in the case was replaced with Term. For instance, suppose we have de ned append as a function of 2 arguments, ie: append([]; Lb) = Lb append([HdjTl]; Ls) = [Hdjappend(Tl; Ls)]
Applying the above compiler to this set of clauses gives: append([]; Lb; Lb ) : ? !: append([HdjTl]); Ls; [HdjV ]) : ? append(Tl; Ls; V ); !: We have given Prolog compilers above. Similar compilers could be given into other recursive programming languages, eg Lisp, Pascal. 26
11 Limitations of the Editor In this section we survey the recursive procedures that are terminating and wellde ned yet cannot be generated by the recursion editor. Since proving termination of a recursive procedure is well known to be undecidable, and since we have chosen a design for the editor in which each command guarantees termination, then it is bound to be the case that for any version of the recursion editor there is a recursive procedure that it cannot produce. In particular, the commands described above limit the recursive procedures produced to structural recursions and ensure well-de nedness on purely syntactic grounds. Because of the incompleteness of the commands provided, the editor cannot even generate all structurally recursive procedures. The editor could always be extended piecemeal by the addition of new editing commands, but as argued above, the basic design of the editor ensures that there will always be limitations to its productive power.
11.1 Non-Structural Recursions
The most fundamental limitation on the editor is that the recursions are all structural, ie that the well-founded order on which the recursion is based is tied to some recursive data-structure. A classic example of a well-founded recursion which is not structural is the \ninety one" function, f 91=1:
X > 100 ?! f 91(X ) = X ? 10 X 100 ?! f 91(X ) = f 91(f 91(X + 11)) This will terminate, but only after the recursive calls of f 91=1 have increased the
value of the recursion argument above 100. As a result this could not be built by our editor.
11.2 General Course of Values Recursions
The editor cannot even cope with all structural recursions. This and the next section describe examples of structurally recursive procedures not de nable by the editor as currently implemented | or even with the extensions to it we have proposed. In x7.5 we proposed a command, \Insert constructor function", for producing course of values recursions. This would work by increasing the step size by some xed amount. Unfortunately, there are examples of course of values recursion where the step size is variable. An example is the procedure gcd=2 for calculating greatest common divisor.
gcd(X; X ) = X X < Y ?! gcd(X; Y ) = gcd(Y; X ) X > Y ?! gcd(X; Y ) = gcd(X ? Y; Y ) The step size in the third case is Y , which varies from recursive call to recursive call. Alan Smaill8 has suggested that one could allow general course of values by allowing a list of all recursive calls in the body of the case. This is suciently general and will deal with gcd=2 above, but is a rather ugly and potentially inecient solution. Note that calls to \Insert parameter procedure" can make this list inde nitely long. 8
Private communication.
27
11.3 Nested Recursion
It is possible to allow the occurrence of calls to the recursive procedure within arguments of and for still to terminate. Obviously, if is to terminate, these nested calls to must have recursive arguments smaller than9 those in the head of the case. But this is not enough as the following example illustrates.
(0) = s(s(0)) (s(X )) = ((X )) Here (s(0)) = ((0)) = (s(s(0))) = ((s(0))) = . The nested occurrence of must be such that the outer occurrence of also has a recursion argument smaller
than the head one. It is possible to think of lots of situations that do terminate, and one could provide commands for each of these situations. But this would be rather unsatisfactory; it would be nicer to gure out a general analysis and provide a single command. Unfortunately, we have been unable to do this without resorting to the notion of an unspeci ed well-founded ordering | and this is just the situation we were trying to avoid, as mentioned in x2. So failing a general analysis, here is one suggestion. One could certainly extend the command \Insert parameter procedure" to allow the procedure substituted to be , provided its recursion argument were the same as some other one in the body of the case. This would permit us to go from:
(0; Y ) = Y (s(s(X )); Y ) = (s(X ); Y ) + (X; Y )
"
"
to:
(0; Y ) = Y (s(s(X )); Y ) = (s(X ); Y ) + (X; (s(X ); Y )) by indicating the arrowed occurrences of and Y .
To see that this extended \Insert parameter procedure" command does not cover all situations, consider Ackermann's function. This has the step case:
ack(s(X ); s(Y ); Z ) = ack(X; ack(s(X ); Y; Z ); Z ) in which a recursive argument is replaced by a suitable occurrence of the recursive function.
11.4 Variable Base Cases
In all the editing commands above we have ensured well-de nedness by simple syntactic criteria requiring no knowledge of the domain other than each procedure is well-typed and that the members of each type are in 1-to-1 correspondence with the terms formed from the constructor functions for that type. However, sometimes procedures can be seen to be well-de ned by appeal to further knowledge from the domain. In general, this may involve an arbitrary amount of inference, and so wellde nedness is undecidable. This section contains an example of a procedure that is well-de ned but cannot be generated by the proposed editor. In the de nition of the general form of procedures given in x5.2, we have assumed that a recursion argument in the head of a case has to be dominated by a constructor 9
For some ordering.
28
function. This excludes recursions in which the base case is a recursion variable whose value is restricted by some condition. For instance, consider the following base case from a procedure, vars in=1, to form a list of the propositional variables in a propositional formula. (4) prop var(Fm) ?! vars in(Fm) = [Fm] where prop var(Fm) means that Fm is a propositional variable. A typical step case of vars in=1 might be: vars in(Fm1 _ Fm2 ) = union(vars in(Fm1 ); vars in(Fm2 )) Compare this version of vars in with the over-de ned version obtained by replacing case (4) above with: formula(Fm) ?! vars in(Fm) = [Fm] where formula(Fm) means that Fm is a formula. This is clearly unacceptable since vars in(p _ q), for instance, has two values: [p _ q] and [p; q], according to which of combination of the overlapping cases is used. But why is the original version acceptable? Only because prop var is never true of any compound formula, e.g. :prop var(p _ q). To show that the original version is not over-de ned it would be necessary to prove this fact and hence show that the cases do not overlap. In general, such inferences could be entirely open ended, and could appeal to arbitrary amounts of domain speci c knowledge. This particular problem could be handled by extending the basic primitive recursive schema to include a case for arguments which are not of the right type, eg: %() ?! (; ) * ) (; ) ([; ) * ) () (]( ; ); ) * ) ( ; ; ; (; )) where the editor would have to ensure that % was instantiated to a predicate which tested that was not a recursion argument formed from applications of [ and ], and it would have to update this test as the form of the recursion arguments of were changed. In this case % would be prop var. Note that the recursion editor does allow variables in the constructor parameter positions of base cases. For instance, the base case list to int([Digit]) = Digit in a procedure to convert lists of digits to integers, is allowed, because Digit is the parameter of the list constructor. It would be introduced by a call of \Insert constructor function".
11.5 Non-Algorithmic Procedures
Some common Prolog procedures are either not terminating, not well-de ned, or not either. Consider, for instance, the following de nition of ancestor=2. ancestor(A; D) : ? father(A; D): ancestor(A; D) : ? mother(A; D): ancestor(A; D) : ? ancestor(A; I ); ancestor(I; D): ancestor=2 does not always terminate, eg an ancestor=2 goal will re-call the third clause inde nitely if there are no clauses for mother=2 and father=2. 29
To show that ancestor=2 is well-de ned requires inference. For instance, suppose it were represented as the following set of cases.
father(A; D) ?! ancestor(A; D) $ true mother(A; D) ?! ancestor(A; D) $ true ancestor(A; I ) ^ ancestor(I; D) ?! ancestor(A; D) $ true A goal, ancestor(john; mary), might succeed by invoking either the rst or the third case | and even the second if the de nition of mother=2 violates the normal meaning. But the procedure returns the same result, true, in all cases, so it is not over-de ned. However, it would be easy to de ne a similar procedure that was over-de ned, eg by adding the term:
son(A; D) ?! ancestor(A; D) $ false then, if in some science ction scenario someone was their own father, the set of cases would be inconsistent. To see that ancestor=2 is not under-de ned we cannot appeal to the coverage by the constructor terms, but must appeal to some knowledge about the ways in which one person can be an ancestor of another. It may not be possible to avoid under-de nition for open-ended predicates like friend(X; Y ). There is a certain irony implied by the above example; non-terminating and/or ill-de ned, but none-the-less useful, procedures are more common in Prolog than any other programming language we can think of. Hence, the recursion editor would probably be more useful for other recursive programming languages than it is for Prolog. Procedures that are either not terminating or not well-de ned are beyond the scope of this paper, since the whole point of the tool proposed here is to ensure these properties.
12 Overcoming These Limitations The inability of the editor to generate all terminating and well-de ned procedures is a necessary feature of its design. An alternative design would be to allow the user to construct any set of (conditional) equations/equivalences and then try to prove them well-de ned and terminating. If the theorem prover for proving these properties was complete, then all terminating, well-de ned procedures could, in theory, be proved so, although the problem is semi-decidable and resource limitations would severely restrict what was possible in practice. This is the alternative approach, based on well-founded orderings, that was rejected in x2. It has been adopted, for instance, by Walther, [Walther 88]. The termination proving part of the task is essentially that of proving termination of sets of (conditional) rewrite rules. There is a signi cant literature on this problem. See, for instance, [Huet & Oppen 80] for a survey of the known techniques. It is a semi-decidable problem, but some heuristic techniques can prove a lot of the simple cases. The well-de nedness proving part of the problem can also be implemented as a semi-decidable theorem proving problem, although a lot of simple cases might be handled by the syntactic checks embodied in our editor. It might be useful to have this facility as an alternative mode of the editor for use by experienced people who feel constrained by the limitations of the standard mode. In `relaxed' mode, all the editing commands would be available, but the checks made by the editor during calls of \Rename", \Insert procedure", etc, would be turned o, allowing the user to construct any (conditional) rewrite rule. Then, at user request, a set of rewrite rules could be tested for termination and well-de nedness. 30
A set of rules that has been shown to be terminating and well-de ned, might then be generalised and adopted by the editor as a new schema. All the existing editing commands could then be applied to it, to elaborate it along the various dimensions described above. This hybrid approach could enable a fruitful marriage of both design philosophies. For instance , suppose the user had de ned the following cases: prop var(Fm) ?! vars in(Fm) = [Fm] vars in(:Fm) = vars in(Fm) vars in(Fm1 _ Fm2 ) = union(vars in(Fm1 ); vars in(Fm2 )) in relaxed mode and then had them proved terminating and well-de ned. S/he might then use \Rename", \Add parameters", and \Insert procedure to edit them into: prop var(Fm) ?! eval(Fm; Env) = value(Fm; Env) eval(:Fm; Env) = not(eval(Fm; Env)) eval(Fm1 _ Fm2 ; Env) = or(eval(Fm1 ; Env); eval(Fm2 ; Env))
13 Conclusion In this paper we have described an editor for building recursive and non-recursive Prolog procedures. This is done in two stages: rst pure logic procedures are built using the editing commands; and second these are compiled into Prolog. The idea of the editor is not only to ensure syntactically correct procedures by allowing only editing operations that preserve correct syntax, but also to ensure the termination and well-de nedness of recursive procedures, while allowing as complete a range of procedures to be generated as possible. We have described a range of commands with these properties. Each command is described by a schematic description of its input and output and illustrated with an example. Most of the commands have been implemented and tested. We have also suggested ways of giving advice to a user on which commands to use and the provision of more powerful, high-level commands. It remains to give a more formal de nition of each of the commands and to prove that these commands preserve termination and well-de nedness. The editor might also be extended with further commands with the same properties. The recursion editor has been partially implemented in approximately 5000 lines of Sicstus Prolog, [Grosse 89]. The interface is simple. The current procedure is displayed in a window and keyboard commands are used to select commands and enter their inputs. The editor has been used to develop several simple Prolog procedures up to about 200 lines, taking a few cpu seconds in the worst case. More large scale testing must wait on a more complete implementation and improvement of the interface. An editor of this kind is necessarily incomplete in the range of procedures it can generate. We have proposed coupling it with a theorem prover that could prove arbitrary sets of cases terminating and well-de ned. The editor could then be used in a \relaxed" mode to generate syntactically correct cases for subsequent testing. Procedures generated in this way could then become the schemata for subsequent manipulation for the editor. We hope that the recursion editor will help Prolog users to write correct recursive procedures, and that the overhead of using it will be repaid by a reduction in time spent debugging incorrect procedures. We further hope that the restricted range of procedures that the editor can generate will prove wide enough to be useful to Prolog programmers. 31
References [Brna et al. 87]
P. Brna, A. Bundy, H. Pain, and L. Lynch. Programming tools for Prolog environments. In J. Hallam and C. Mellish, editors, Advances in Arti cial Intelligence, pages 251{264. Society for the Study of Arti cial Intelligence and Simulation of Behaviour, John Wiley and Sons, 1987. Previously, DAI Research Paper No 302. [Grosse 89] G. Grosse. Towards a recursive techniques editor. Unpublished M.Sc. thesis, Dept of Computer Science, Heriot-Watt University, 1989. [Huet & Oppen 80] G. Huet and D. C. Oppen. Equations and rewrite rules: A survey. In R. Book, editor, Formal languages: Perspectives and open problems. Academic Press, 1980. Presented at the conference on formal language theory, Santa Barbara, 1979. Available from SRI International as technical report CSL-111. [Kahney 82] H. Kahney. An In-Depth Study Of The Cognitive Behaviour Of Novice Programmers. Unpublished PhD thesis, Human Cognition Research Laboratory, The Open University, Milton Keynes, 1982. [Peter 67] R. Peter. Recursive Functions. Academic Press, 1967. Translated by Istvan Foeldes. [Teitelman & Masinter 81] W. Teitelman and L. Masinter. The interlisp programming environment. IEEE Computer, pages 25{33, 1981. [Walther 88] C. Walther. Argument-bounded algorithms as a basis for automated termination proofs. In R. Lusk and R. Overbeek, editors, 9th International Conference on Automated Deduction, pages 602{621. Springer-Verlag, 1988. Revised version to appear in AI Journal.
32