URL: http://www.elsevier.nl/locate/entcs/volume86.html 18 pages

Abstract Correction of First-Order Functional Programs

M. Alpuentea D. Ballisb S. Escobara M. Falaschib

S. Lucasa

a DSIC, Universidad Politécnica de Valencia, Camino de Vera s/n, Apdo. 22012, 46071 Valencia, Spain. Email: {alpuente,sescobar,slucas}@dsic.upv.es.

b Dip. Matematica e Informatica, Via delle Scienze 206, 33100 Udine, Italy. Email: {demis,falaschi}@dimi.uniud.it.

Abstract

Debussy is an (abstract) declarative diagnosis tool for functional programs which are written in OBJ style. The tool does not require the user to either provide error symptoms in advance or answer any question concerning program correctness. In this paper, we formalize an inductive learning methodology for repairing program bugs in OBJ-like programs, which is based on the so-called example-guided unfolding [6]. Correct programs are synthesized by unfolding and removing rules of the faulty program. Rules to be unfolded (deleted) are selected according to the examples, which can be automatically generated as an outcome by the Debussy diagnoser.

Key words: Abstract Correction, First Order Functional Programs, Inductive Learning, Program Transformation, Abstract Interpretation.

1 Introduction

This paper is motivated by the fact that the debugging support for functional languages in current systems is poor [28], and there are no general purpose, good semantics-based debugging tools available, which allow to debug a program w.r.t. a given formal specification. Traditional debugging tools for functional programming languages consist of tracers which help to display the execution [7,14,21,23] but which do not enforce program correctness adequately as they do not provide means for finding nor repairing bugs in the source code w.r.t. the intended program semantics. This is particularly dramatic for equational languages such as those in the OBJ family, which includes OBJ3, CafeOBJ and Maude.

Abstract diagnosis of functional programs [2] is a declarative diagnosis framework extending the methodology of [9], which relies on (an approxi-

©2003 Published by Elsevier Science B. V.

A^nuente et ai

mation of) the immediate consequence operator TR [17], to identify bugs in functional programs. Given the intended specification I of the semantics of a program R, the debugger checks the correctness of R by a single step of the abstract immediate consequence operator TK, where the abstraction function k stands for depth(k) cut [9]. Then, by a simple static test, the system can determine all the rules which are wrong w.r.t. a particular abstract property. The framework is goal independent and does not require the determination of symptoms in advance. This is in contrast with traditional, semi-automatic debugging of functional programs [20,22,27], where the debugger tries to locate the node in an execution tree which is ultimately responsible for a visible bug symptom. This is done by asking the user, who assumes the role of the oracle. When debugging real code, the questions are often textually large and may be difficult to answer.

In this paper, we endow the functional debugging method of [2] with a bug-correction program synthesis methodology which, after diagnosing the buggy program, tries to correct the erroneous components of the wrong code automatically. The method uses unfolding in order to discriminate positive from negative examples (resp. uncovered and incorrect equations) which are automatically produced as an outcome by the diagnoser. Informally, our correction procedure works as follows. Starting from an overly general program (that is, a program which covers all the positive examples as well as some negative ones), the algorithm unfolds the program and deletes program rules until reaching a suitable specialization of the original program which still covers all the positive examples and does not cover any negative one. Both, the example generation and the top-down correction processes, exploit some properties of the abstract interpretation framework of [2] which they rely on. Let us emphasize that we do not require any demanding condition on the class of the programs which we consider. This is particularly convenient in this context, since it should be undesirable to require strong properties, such as termination or confluence, to a buggy program which is known to contain errors.

We would like to clarify the contributions of this paper w.r.t. [1], where a different unfolding-based correction method was developed which applies to synthesizing multiparadigm, functional-logic programs from a set of positive and negative examples. First, the method for automatically generating the example sets is totally new. In [1] (abstract) non-ground examples were computed as the outcome of an abstract debugger based on the loop-check techniques of [3], whereas now we compute (concrete) ground examples after a depth-k abstract diagnosis phase [9] which is conceptually much simpler and allows us to compute the example sets more efficiently. Regarding the top-down correction algorithm, the one proposed in this paper significantly improves the method in [1]. We have been able to devise an abstract technique for testing the "overgenerality" applicability condition, which saves us from requiring program termination or the slightly weaker condition of termination (termination of context-sensitive rewriting [18]). Finally, we have

been able to demonstrate the correctness of the new algorithm for a much larger class of programs, since we do not even need confluence whereas [1] applies only to inductively sequential programs or canonical, completely defined systems (depending on the lazy/eager narrowing strategy chosen).

The debugging methodology which we consider can be very useful for a functional programmer who wants to debug a program w.r.t. a preliminary version which was written with no efficiency concern. Actually, in software development a specification may be seen as the starting point for the subsequent program development, and as the criterion for judging the correctness of the final software product. Therefore, a debugging tool which is able to locate bugs in the user's program and correct the wrong code becomes also important in this context. In general, it also happens that some parts of the software need to be improved during the software life cycle, e.g. for getting a better performance. Then the old programs (or large parts of them) can be usefully (and automatically) used as a specification of the new ones. For instance, the executability of OBJ specifications supports prototype-driven incremental development methods [15].

The rest of the paper is organized as follows. Section 2 summarizes some preliminary definitions and notations. Section 3 recalls the abstract diagnosis framework for functional programs of [2]. Section 4 formalizes the correction problem in this framework. Section 5 illustrates the example generation methodology. Section 6 presents the top-down correction method together with some examples and correctness results. Section 7 concludes.

2 Preliminaries

Term rewriting systems provide an adequate computational model for first order functional languages. In this paper, we follow the standard framework of term rewriting (see [5,16]). For simplicity, definitions are given in the one-sorted case. The extension to many-sorted signatures is straightforward, see [24]. In the paper, syntactic equality of terms is represented by =. By V we denote a countably infinite set of variables and £ denotes a set of function symbols, or signature, each of which has a fixed associated arity. T(£, V) and T(£) denote the non-ground word (or term) algebra and the word algebra built on £ UV and £, respectively. T(£) is usually called the Herbrand universe (He) over £ and will be simply denoted by H. B denotes the Herbrand base, namely the set of all ground equations which can be built with the elements of H. A £-equation s = t is a pair of terms s,t E T(£, V), or true.

Terms are viewed as labelled trees in the usual way. Positions are represented by sequences of natural numbers denoting an access path in a term, where A denotes the empty sequence. We let depth(t) denote the depth of the tree (term) t. Given S C £ UV, OS (t) denotes the set of positions of a term t which are rooted by symbols in S. t\u is the subterm at the position u of t. t[r]u is the term t with the subterm at the position u replaced with r. By

y ente et al

Var(s) we denote the set of variables occurring in the syntactic object s, while [s] denotes the set of ground instances of s. A fresh variable is a variable that appears nowhere else.

A substitution is a mapping from the set of variables V into the set of terms T(£, V). A substitution 9 is more general than a, denoted by 9 < a, if a = 9y for some substitution 7. We write 9|-s to denote the restriction of the substitution 9 to the set of variables in the syntactic object s. The empty substitution is denoted by e. A renaming is a substitution p for which there exists the inverse p-1, such that pp-1 = p-1p = e. An equation set E is unifiable, if there exists 9 such that, for all s = t in E, we have s9 = t9, and 9 is called a unifier of E. We let mgu(E) denote the most general unifier of the equation set E [19].

A term rewriting system (TRS for short) is a pair (£, R), where R is a finite set of reduction (or rewrite) rules of the form A — p, A, p E T(£, V), A E V and Var (p) C Var (A). Term A is called the left-hand side (lhs) of the rule and p is called the right-hand side (rhs). We will often write just R instead of (£, R) and call R the program. For TRS R, r < R denotes that r is a new variant of a rule in R such that r contains only fresh variables. Given a TRS (£, R), we assume that the signature £ is partitioned into two disjoint sets £ := CWV, where V := {f | f (ti,.. .,tn) — r eR} and C := £\V. Symbols in C are called constructors and symbols in V are called defined functions. The elements of T(C, V) are called constructor terms, while elements in T(C) are called values. A pattern is a term of the form f (d) where f/n eV and d is a n-tuple of constructor terms. A TRS R is a constructor system (CS), if all lhs's of R are patterns. A TRS R is left-linear (LL), if no variable appears more than once in the lhs of any rule of R.

A rewrite step is the application of a rewrite rule to an expression. A term s rewrites to a term t via r < R, s —r t (or s —R t), if there exist u E O^(s), r = A — p, and substitution a such that s|u = Aa and t = s[pa]u. We say that S := t0 —ro t1 —ri t2... tn is a rewrite sequence from term t0 to

term tn.

When no confusion can arise, we will omit any subscript (i.e. s ^ t). A term s is a normal form, if there is no term t with s —t. t is the normal form of s if s —R t and t is a normal form (in symbols s —R t). We say that a TRS R is terminating, if there exists no infinite rewrite sequence t1 —R t2 —R ...

The narrowing mechanism is commonly applied to evaluate terms containing variables. Narrowing non-deterministically instantiates variables so that a rewrite step is enabled. This is done by computing mgu's. Formally, s t is a narrowing step via r < R, if there exist p E Os(s) and r = A — p such that a = mgu({A = s|p}) and t = s[p]pa.

3 Denotation of functional programs

In this section we first recall the semantic framework introduced in [2]. We will provide a finite/angelic relational semantics [10], given in fixpoint style, which associates an input-output relation to a program, while intermediate computation steps are ignored. Then, we formulate an abstract semantics which approximates the evaluation semantics of the program.

In order to formulate our semantics for term rewriting systems, the usual Herbrand base is extended to the set of all (possibly) non-ground equations [12,13]. Hy denotes the V-Herbrand universe which allows variables in its elements, and is defined as T(£, V)/=, where = is the equivalence relation induced by the preorder < of "relative generality" between terms, i.e. s < t if there exists a s.t. t = a(s). For the sake of simplicity, the elements of Hy (equivalence classes) have the same representation as the elements of T(£, V) and are also called terms. By denotes the V-Herbrand base, namely, the set of all equations s = t modulo variance, where s,t E Hy. A subset of By is called a V-Herbrand interpretation. We assume that the equations in the denotation are renamed in order to avoid that program variables occur in the denotation. The ordering < for terms is extended to equations in the obvious way, i.e. s = t < s' = t' iff there exists a s.t. a(s) = a(t) = s' = t'.

3.1 Concrete semantics

The considered concrete domain E is the lattice of V-Herbrand interpretations, i.e., the powerset of By ordered by set inclusion.

In the sequel, a semantics for program R is a V-Herbrand interpretation. Since in functional programming, programmers are generally concerned with computing values (ground constructor normal forms), the semantics which is usually considered is Semval(R) := {s = t \ s —n t,t E T(C)}. Sometimes, the rewrite sequence from term s to value t is called proof of the equation s = t.

Following [10], in order to formalize our evaluation semantics via fixpoint computation, we consider the following immediate consequence operator.

Definition 3.1 [2] Let I be a Herbrand interpretation, R be a TRS. Then,

TR(1) = {t = t \ t E T(C)} U{s = t \ r = t El,s —r}. The following proposition is immediate.

Proposition 3.2 [2] Let R be a TRS. The Tn operator is continuous on E.

Definition 3.3 [2] The least fixpoint semantics of a program R is defined as FVa|(R)= Tr T W.

Example 3.4 Suppose to roll a dice. If the dice face revealed after the roll is an odd one, you win a prize; otherwise you are not rewarded. The problem can be modeled by the following specification I (written in OBJ-like syntax):

obj GAMESPEC is

sorts Nat Reward Bool . op 0 : -> Nat . op s : Nat -> Nat . op prize : -> Reward . op true: -> Bool . op false: -> Bool . op sorry-no-prize : -> Reward . op playdice : Nat -> Reward . op win? : Bool -> Reward . op odd : Nat -> Bool . var X : Nat .

eq playdice(X) = win?(odd(X)) . eq win?(true) = prize . eq win?(false) = sorry-no-prize . eq odd(0) = false . eq odd(s(0)) = true . eq odd(s(s(X))) = odd(X) .

Face values are expressed by naturals; besides, specification I tells us that we win the prize at stake (expressed by the constructor prize), if the resulting value of the odd function is true, otherwise the value sorry-no-prize is computed.

The associated least fixpoint semantics is

Fva|(I) = {prize = prize, sorry-no-prize = sorry-no-prize,

true = true, false = false, 0 = 0, s(0) = s(0) ,...}U {odd(sn(0)) = true \ n is odd } U {odd(sn(0)) = false \ n is even } U

{win?(true) = prize,win?(false) = sorry-no-prize }U {win?(odd(sn (0))) = prize \ n is odd }U {win?(odd(sn(0))) = sorry-no-prize \ n is even } U {playdice(sn(0)) = prize \ n is odd} U {playdice(sn(0)) = sorry-no-prize \ n is even }

The following result establishes the equivalence between the (fixpoint) semantics computed by the Tn operator and the evaluation semantics Semval(R).

Theorem 3.5 (soundness and completeness) [2] Let R be a TRS. Then,

Sem val(R) = Fval(R).

Note that if e = (l = c) belongs to the (fixpoint) semantics S of R, then for each proof e — e\ — e2... en — (c = c) of e in R, ei belongs to

S, i = 1,...,n. That is, the semantics models all partial computations of equations in R.

3.2 Abstract semantics

Starting from the concrete fixpoint semantics of Definition 3.3, we give an abstract semantics which approximates the concrete one by means of abstract interpretation techniques. In particular, we will focus our attention on abstract interpretations achieved by means of a depth(k) cut [9], which allows to finitely approximate an infinite set of computed equations.

First of all we define a term abstraction as a function /k : (T(£, V), <) — (T(£, VUV), <) which cuts terms having a depth greater than k. Terms are cut by replacing each subterm rooted at depth k with a new variable taken from the set V (disjoint from V). depth(k) terms represent each term obtained by instantiating the variables of V with terms built over V. Note that /k is finite. We denote by T/k the set of depth(k) terms (T(£, VUV)/k). We choose as abstract domain A the set P({a = a' | a, a' E T/k}) ordered by the Smyth's extension of ordering < to sets, i.e. X <S Y iff V y E Y 3 x E X : x < y. Thus, we can lift the term abstraction /k to a Galois Insertion of A into E by defining

k(E) := {s/k = t/k | s = t E E} YA) := {s = t | s/k = t/k E A}

Now we can derive the optimal abstract version of TR simply as TK := k o Tn o y and define the abstract semantics of program R as the least fixpoint of this (obviously) continuous operator, i.e. FVa\(R) := TK T u. Since /k is finite, we are guaranteed to reach the fixpoint in a finite number of steps, that is, there exists a finite natural number h such that TK T u = TK T h. Abstract interpretation theory assures that TK T u is the best correct approximation of Semval(R). Correct means FVal(R) <S ft(Semval(R)) and best means that it is the maximum w.r.t. <S.

By the following proposition, we provide a simple and effective mechanism to compute the abstract fixpoint semantics.

Proposition 3.6_[2] For k > 0, the operator TR : T/k x T/k — T/k x T/k has the property TR(X) <S TR(X) vj.r.t. the following operator:

TR(X) = k(B) U {a(u[/]p)/k = t | u = t E X,p E O^uv(u),

l — r < R,a = mgu(ur)}

where B = {t = t | t E T(C)}.

Definition 3.7 [2] The abstract least fixpoint semantics of a program R is defined as FVa\(R) = T u.

Proposition 3.8 (Correctness) [2] Let R be a TRS and k > 0.

Ainuente et al

(i) FUR) <S K(Fval(R)) <S Fvai(R).

(ii) For every e G FF^ai(R) such that Var(e) n V = 0, e G Fval(R).

Example 3.9 Consider again the specification in Example 3.4. Its abstract least fixpoint semantics for k = 3 becomes

F-Vji(I) = {prize = prize, sorry-no-prize = sorry-no-prize, true = true, false = false, 0 = 0 }U {s*(0) = s* (0) I i = 1,...3 } U {s4 (X) = s4 (X) }U {odd(0) = false,odd(s(0)) = true,odd(s2(0)) = false,

odd(s3(X)) = true, odd(s3(X)) = false }U {win(true) = prize,win(false) = sorry-no-prize, win(odd(0)) = sorry-no-prize,win(odd(s(0)) = prize, win(odd(s2(X)) = sorry-no-prize, win(odd(s2(X)) = prize }U {playdice(O) = sorry-no-prize, playdice(s(0)) = prize, playdice(s2(X)) = sorry-no-prize, playdice(s2(X)) = prize }

4 The Correction Problem

The problem of repairing a faulty functional program can be addressed by using inductive learning techniques guided by appropriate examples. Roughly speaking, given a wrong program and two example sets specifying positive (pursued) and negative (not pursued) equations respectively, our correction scheme aims at synthesizing a set of program rules that replaces the wrong ones in order to deliver a corrected program which is "consistent" w.r.t. the example sets [6]. More formally, we can state the correction problem as follows.

4.1 Problem formalization

Let R be a TRS, I be a specification of the intended semantics of R, E + and E- be two finite sets of equations such that

(i) E + Ç Semva|(I);

(ii) E- Ç (Semva|(R) \ Sem^ÇT)) = 0.

The correction problem consists in constructing a TRS Rc satisfying the following requirements

(i) E + C Semva\(Rc);

(ii) E- n SemVal(Rc) = 0.

Equations in E+ (resp. E-) are called positive (resp. negative) examples. The TRS Rc is called correct program w.r.t. E+ and E-. Note that, by construction, positive and negative example sets are disjoint, which permits to drive the correction process towards a discrimination between E + and E-.

4.2 Deductive and Inductive Learners

The automatic search for a new rule in an induction process can be performed either bottom-up (i.e. from an overly specific rule to a more general) or top-down (i.e. from an overly general rule to a more specific). There are some reasons to prefer the top-down or backward reasoning process to the bottom-up or forward reasoning process [11]. On the one hand, it eliminates the need for navigating through all possible logical consequences of the program. On the other hand, it integrates inductive reasoning with the deductive process, so that the derived program is guaranteed to be correct. Unfortunately, it is known that the deductive process alone (i.e. unfolding) does not generally suffice for coming up with the corrected program (unless the program is "overly general", i.e. it covers all the positive examples) and inductive generalization techniques are necessary [11,25,26].

In [1], we presented a general bottom-up inductive generalization methodology along with a top-down inductive correction method. The former transforms a given program into a program which is "overly general" w.r.t. an example set, so that the latter can be applied. Therefore, in the sequel we only formalize an efficient top-down correction methodology, which we prove to be correct for a wider class of TRSs than the method which could be naively obtained by particularizing [1] to the functional setting.

5 How to generate example sets automatically

Before giving a constructive method to derive a correct program, we present a simple methodology for automatically generating example sets, so that the user does not need to provide error symptoms, evidences or other kind of information which would require a good knowledge of the program semantics that she probably lacks.

In the following, we observe that we can easily compute "positive" equations, i.e. equations which appear in the concrete evaluation semantics Semval(1), since all equations in fal(1) not containing variables in V belong to the concrete evaluation semantics Semval(1), as stated in the following lemma.

Lemma 5.1 Let I be a TRS and EP := {e^ E .fal(J) A Var(e) n V = 0}. Then, EP C Semval(1).

Now, by exploiting the information in F^D and Fval(R), we can also generate a set of "negative" equations which belong to the concrete evaluation semantics Semval(R) of the wrong program R but not to the concrete evaluation semantics Semval(l) of the specification T.

Lemma 5.2 Let R be a TRS, T be a specification of the intended semantics and En := {e\e E FvKal(R) A Var (e) nV = 0A FvKal(J) {e}}. Then, En C (Semval(R) \ Sem^^T)).

Starting from sets EP and EN, we construct the positive and negative example sets E + and E- which we use for the correctness process, by considering the restriction of EP and EN to examples of the form l = c where l is a pattern and c is a value. By considering these "data" examples, the inductive process becomes independent of the extra auxiliary functions which might appear in T, since we start synthesizing directly from data structures. The sets E + and E- are defined as follows.

E + = {l = c \ f (ti,. ..,tn) = c E EP A f (ti,. ..,tn) = l is a pattern A

A c ET(C) A f E £r}

E- = {l = c \ l = c E En A l is a pattern A c eT(C)}

where £R is the signature of program R.

In the sequel, the function which computes the sets E + and E-, according to the above description, is called ExGen(R,T).

6 Program correction via example-guided unfolding

In this section we present a basic top-down correction method which is based on the so-called example-guided unfolding [6], which is able to specialize a program by applying unfolding and deletion of program rules until coming up with a correction. The top-down correction process is "guided" by the examples, in the sense that transformation steps focus on discriminating positive from negative examples. The accuracy of the correction improves as the number of positive and negative examples increase as it is common to the learning from examples approach.

In order to successfully apply the method, the semantics of the program to be specialized must include the positive example set E + (that is, E + C Semval(R)). Programs satisfying this condition are called overly general (w.r.t.

The over-generality condition is not generally decidable, as we do not impose program termination [1]. Fortunately, when we consider the abstract semantics framework of [2] we are able to ascertain a useful sufficient condition to decide whether a program is overly general, even if it does not terminate. The following proposition formalizes our method.

Proposition 6.1 Let R be a TRS and E + be a set of positive examples. If,

for each e E E + , there exists e E F^R) s.t.

(i) e' < e;

(ii) Var(e') nV = 0;

then, R is overly general vj.r.t. E +.

Now, by exploiting Proposition 6.1, it is not difficult to figure out a procedure OveRLYGeNeRAL(R,E) testing this condition w.r.t. a program R and a set of examples E, e.g. a boolean function returning true if program R is overly general w.r.t. E and false otherwise.

6.1 The unfolding operator

Informally, unfolding a program R w.r.t. a rule r delivers a new specialized version of R in which the rule r is replaced with new rules obtained from r by performing a narrowing step on the rhs of r.

Definition 6.2 Given two rules ri = Ai — pi and r2, we define the rule unfolding of ri w.r.t. r2 as Ur2 (ri) = {Aia — p' | pi p'}.

Definition 6.3 Given a TRS R and a rule r < R, we define the program unfolding of r w.r.t. R as follows UR (r) = Ru LUe^ Ur' (r)) \ {r}.

Note that, by Definition 6.3, for any TRS R and rule r < R, r is never in Ur (r).

Definition 6.4 Let R be a TRS, r be a rule in R. The rule r is unfoldable w.r.t. R if Ur(r) = R\{r}.

Now, we are ready to prove that the "transformed" semantics, obtained after applying the unfolding operator to a given program R, still contains the semantics of R. In symbols, Semval(R) C Semva\(UR(r)), where r is an unfoldable rule. We call this property unfolding correctness. The following definition is auxiliary.

Definition 6.5 Let t E t(£ U V) and L be a symbol not in £. The shell of t, in symbols shell (t), is defined as follows

, ni \ \ f (shell (ti),..., shell (t„)) if t = f (ti,...,tn), where f eD .shell (t) = <

I -L otherwise

A program R is well-framed, if for each A — p E R, OD(shell(p)) = OD(p). The following theorem establishes the correctness of unfolding even for non-confluent programs, provided that they are well-framed. Note that well-framedness is much less demanding and easy to check.

Theorem 6.6 (unfolding correctness) Let R be a well-framed left-linear CS, r < R be an unfoldable rule and R' = UR(r). Let e = (l = c) be an equation such that l E T(£, V) and c E T(C). Then, if e E Semval(R), then e E

Ainuente et ai

Sem val(R/).

Finally, some important program properties such as well-framedness and program termination are preserved through unfolding.

6.2 The top-down correction algorithm

Basically, the idea behind the basic correction algorithm is to eliminate rules from the program in order to get rid of the negative examples without losing the derivations for the positive ones. Clearly, this cannot be done by naively removing program rules, since sometimes a rule is used to prove both a positive and a negative example. So, before applying deletion, we need to specialize programs in order to ensure that the deletion phase only affects those program rules which are not necessary for proving the positive examples. This specialization process is carried out by means of the unfolding operator of Definition 6.3. Considering this operator for specialization purposes has important advantages. First, positive examples are not lost by repeatedly applying the unfolding operator, since unfolding preserves the proper semantics (see Theorem 6.6). Moreover, the nature of unfolding is to "compile" rewrite steps into the program, which allows us to shorten and distinguish the rewrite rules which occur in the proofs of the positive and negative examples.

Figure 1 shows the correction algorithm, called TDCorrector, which takes as input a program R and a specification of the intended semantics T, also expressed as a program. First, TDCorrector computes the example sets E+ and E- by means of ExGen, following the method presented in Section 5. Then, it checks whether program R is overly general following the scheme of Proposition 6.1, and finally it enters the main correction process.

In order to ensure correctness of the algorithm, we require k to be greater than or equal to the maximum depth of the terms occurring in E+.

This last phase consists of a main loop, in which we perform an unfolding step followed by a rule deletion until no negative example is covered (approximated) by the abstract semantics of the current transformed program Rn. This amounts to saying that no negative example belongs to the concrete semantics of Rn. We note that the while loop guard is easy to check, as the abstract semantics is finitely computable. Note the deep difference w.r.t. the algorithm of [1], where decidability is ensured by requiring both confluence and (^-termination) of the program.

During the unfolding phase, we select a rule upon which performing a program unfolding step. In order to specialize the program w.r.t. the example sets, we pick up an unfoldable rule which occurs in some proof of a positive example by using a fair selection strategy. More precisely, we non-deterministically choose rules which appear first in proofs of positive examples. Those rules can be easily computed retrieving them from the fixpoint semantics Fval by a further step of the TK operator by means of the auxiliary function first, which

procedure TDCorrector(R, T) (E + , E-) — EXGEN(R, T) if not OverlyGeneral(R,E+) then Halt k — 0; Rk —R

while 3 e- E E- : F^(Rk) {e-} do

R —— {r E Rk \ r is unfoldable A r E first (E+)} if R = 0 then

SELECT(r, R)

Rk+i — URk(r); k — k + 1 end if

for each r E Rk do

if OVERLYGENERAL(Rk \{r} ,E+) then

Rk—Rk\{r} end if end for end while Rc — Rk end procedure

Fig. 1. The top-down correction algorithm. is formally defined as follows.

Definition 6.7 Let R be a TRS and E be an example set. Then, we define first(E) := J {r \ e E Tfp}(Fal(R))}.

Once unfolding has been accomplished, we proceed to remove the "redundant" rules, that is, all the rules which are not needed to prove the positive example set E +. This can be done by repeatedly testing the overgenerality of the specialized program w.r.t. E + and removing one rule at each iteration of the inner for loop. Roughly speaking, if program Rk \ {r} is overly general w.r.t. E+, then rule r can be safely eliminated without losing E+. Then, we can repeat the test on another rule. Let us consider an example.

Example 6.8 The following OBJ program R is wrong w.r.t. the specification T of Example 3.4.

obj GAME is sorts Nat Reward . op 0 : -> Nat . op s : Nat -> Nat . op prize : -> Reward . op sorry-no-prize : -> Reward . op playdice : Nat -> Reward . var X : Nat .

eq playdice(s(X)) = playdice(X) . (1) eq playdice(O) = prize . (2)

eq playdice(O) = sorry-no-prize . (3)

Note that program R is non-confluent and computes both values prize and sorry-no-prize for any natural sn(0), n> 0. By fixing k = 3, we compute the following least fixpoint abstract semantics for R.

FFUR) = {prize = prize, sorry-no-prize = sorry-no-prize, true = true, false = false, 0 = 0 }U {s*(0) = s*(0) | i = 1,...3 } U {s4 (X) = s4 (X) }U {playdice(0) = prize,playdice(0) = sorry-no-prize, playdice(s(0)) = prize,playdice(s(0)) = sorry-no-prize, playdice(s2(0)) = prize,playdice(s2(0)) = sorry-no-prize, playdice(s3(X)) = prize,playdice(s3(X)) = sorry-no-prize}.

Considering the abstract fixpoint semantics .^(1) computed in Example 3.9 and following the methodology of Section 5, we obtain the example sets below:

E+ = {playdice(s(0)) = prize,playdice(0) = sorry-no-prize} E- = {playdice(s(0)) = sorry-no-prize,playdice(0) = prize}.

Now, since program R fulfills the condition for overgenerality expressed by Proposition 6.1, the algorithm proceeds and enters the main loop. Here, program rule (1) is unfolded, because (1) is unfoldable and belongs to first(E+). So, the transformed program is

eq playdice(s(s(X))) = playdice(X) . (4)

eq playdice(s(0)) = prize . (5)

eq playdice(s(0)) = sorry-no-prize . (6)

eq playdice(0) = prize . (7)

eq playdice(0) = sorry-no-prize . (8)

Subsequently, a deletion phase is executed in order to check whether there are rules not needed to cover the positive example set E + . The algorithm discovers that rules (6), (7) are not necessary, and therefore are removed producing the correct program which consists of rules (4), (5) and (8).

Note that the above example cannot be repaired by using the correction method of [1].

6.3 Correctness of algorithm TDCoRRecTOR

In this section, we prove the correctness of the top-down correction algorithm TDCoRRecTOR, i.e., we show that it produces a specialized version of R which is a correct program w.r.t. E + and E-, provided that R is overly general w.r.t. E + . A condition is necessary for establishing this result: no negative/positive

couple of the considered examples must be proven by using the same sequence of rules.

Let us start by giving an auxiliary definition and some technical lemmata.

Definition 6.9 Let R be a TRS and E be a set of examples. The unfolding succession US(R) = R0, Ri,... of program R vj.r.t. E is defined as follows:

(lAi?- (r) where re is unfoldable and re first (E) R ( ) i f J ( )

Ri otherwise

The next results state that we are always able to transform a program R into a program R' by a suitable number of unfolding steps, in such a way that any given proof of an example in R can be mimicked by a one-step proof in R'. In the following we denote the length of a rewrite sequence S by \S\ .

Lemma 6.10 Let R be a vjell-framed left-linear CS, E be a set of examples and r E first(E) be an unfoldable rule such that R' = UR(r). Let t = c E E, where t is a pattern and c is a value. Then,

(i) if S is a rewrite sequence from t to c in R, then there exists a rewrite sequence S' from t to c in R';

(ii) if r occurs in S, then \S'\ < \S\.

Lemma 6.11 Let R be a vjell-framed left-linear CS, E be an example set and t = c E E, where t is a pattern and c is a value. Let t ~^P1 ... c, n > 1. Then, for each unfolding succession US(R) wj.r.t. E, there exists Rk occurring in US(R) such that t c, r* E Rk.

The following result immediately derives from Claim (i) of Proposition 3.8 .

Lemma 6.12 Let R be a TRS and e an equation. Then, if FFVa\(R) <S {e}, then e E Semval(R).

The following definition is auxiliary. We say that the pair of positive an negative example sets (E +, E-) is discriminable in R [6], if there are no e+ E E+ and e- E E- which can be proven by using the same sequence of rules of R. This property can be checked by using standard tools for proving program termination.

Now we are ready to prove the partial correctness and the termination of the algorithm.

Theorem 6.13 Let R be a well-framed left-linear CS and T be a specification of the intended semantics of R. Let E + and E- be the example sets generated by ExGen(R, T).

(i) If (E+, E-) is discriminable in R, then the algorithm TDCorrector(R, T) terminates.

(ii) If R is overly general wj.r.t. E + and TDCorrector(R, T) terminates,

then the computed program Rc is correct w.r.t. E + and E .

We note that well-framedness can be dispensed in exchange for requiring complete definedness (CD), i.e the property that functions are defined on all possible values of their arguments, as in [4] by simply delaying the deletion phase and performing a virtual deletion instead. This suffices to ensure that the CD property is preserved during the transformation.

7 Conclusions

In this paper, we have proposed a methodology for synthesizing (partially) correct functional programs written in OBJ style, which complements the diagnosis method which was developed previously in [2]. Specifications of the intended semantics, expressed as programs, are used to carry out the diagnosis as well as the correction. This is not only a common practice in logic as well as equational (or term rewriting) languages, but also in functional programming. For example, in QuickCheck [8], formal specifications are used to describe properties of Haskell programs (written as Haskell programs too) which are automatically tested.

Our methodology is based on a combination, in a single framework, of a diagnoser which identifies those parts of the code containing errors, together with a deductive program learner which, once the bug has been located in the program, tries to repair it starting from evidence examples (uncovered as well as incorrect equations) which are essentially obtained as an outcome of the diagnoser.

This method is not comparable to [1] as it is lower-cost and it works for a much wider class of TRSs. In particular, it is able to repair well-framed non-confluent programs, while [1] describes a methodology which only corrects inductively sequential (respectively, canonical and completely defined) programs according to the chosen lazy (respectively, eager) narrowing strategy. This is not only theoretically more challenging, but also convenient in our framework, where it is not reasonable to expect that confluence holds for an erroneous program (even if program confluence was the programmer's intention).

We are currently extending the implementation of the diagnosis system DeBussy[2] (available at http://www.dsic.upv.es/users/elp/soft.html)

with a correction tool which is based on the proposed abstract correction methodology, and use it for an experimental evaluation of the system.

References

[1] M. Alpuente, D. Ballis, F. J. Correa, and M. Falaschi. Automated Correction

of Functional Logic Programs. In PierPaolo Degano, editor, 12th European

Symposium on Programming, ESOP 2003, volume 2618 of Lecture Notes in

Artificial Intelligence, pages 54-68. Springer, 2003.

[2] M. Alpuente, M. Comini, S. Escobar, M. Falaschi, and S. Lucas. Abstract Diagnosis of Functional Programs. In M. Leuschel, editor, International Workshop on Logic Based Program Development and Transformation (LOPSTR'02), volume 2664 of Lecture Notes in Computer Science, pages 116. Springer, 2003.

[3] M. Alpuente, M. Falaschi, and F. Manzo. Analyses of Unsatisfiability for Equational Logic Programming. Journal of Logic Programming, 22(3):221-252, 1995.

[4] M. Alpuente, M. Falaschi, G. Moreno, and G. Vidal. A Transformation System for Lazy Functional Logic Programs. In A. Middeldorp and T. Sato, editors, Proc. of the 4th Fuji International Symposyum on Functional and Logic Programming, FLOPS'99, Tsukuba (Japan), pages 147-162. Springer LNCS 1722, 1999.

[5] F. Baader and T. Nipkow. Term Rewriting and All That. Cambridge University Press, 1998.

[6] H. Bostrom and P. Idestam-Alquist. Induction of Logic Programs by Example-guided Unfolding. Journal of Logic Programming, 40:159-183, 1999.

[7] O. Chitil, C. Runciman, and M. Wallace. Freja, Hat and Hood - A Comparative Evaluation of Three Systems for Tracing and Debugging Lazy Functional Programs. In Proc. of IFL 2000, volume 2011 of Lecture Notes in Computer Science, pages 176-193. Springer, 2001.

[8] K. Claessen and J. Hughes. QuickCheck: A Lightweight Tool for Random Testing of Haskell Programs. Fifth ACM SIGPLAN International Conference on Functional Procgrammincg (ICFP'00), 35(9):268-279, 2000.

[9] M. Comini, G. Levi, M. C. Meo, and G. Vitiello. Abstract diagnosis. Journal of Logic Programming, 39(1-3):43-93, 1999.

[10] P. Cousot. Constructive Design of a Hierarchy of Semantics of a Transition System by Abstract Interpretation. Theoretical Computer Science, 277(1-2):47-103, 2002.

[11] N. Dershowitz and U. Reddy. Deductive and Inductive Synthesis of Equational Programs. Journal of Symbolic Computation, 15:467-494, 1993.

[12] M. Falaschi, G. Levi, M. Martelli, and C. Palamidessi. Declarative Modeling of the Operational Behavior of Logic Languages. Theoretical Computer Science, 69(3):289-318, 1989.

[13] M. Falaschi, G. Levi, M. Martelli, and C. Palamidessi. A Model-Theoretic Reconstruction of the Operational Semantics of Logic Programs. Information and Computation, 103(1):86-113, 1993.

[14] A. Gill. Debugging Haskell by Observing Intermediate Data Structures. In Graham Hutton, editor, 2000 ACM SIGPLAN Haskell Workshop, volume 41 of Electronic Notes in Theoretical Computer Science. Elsevier, 2001.

[15] J. A. Goguen and G. Malcom. Software Engineering with OBJ. Kluwer Academic Publishers, Boston, 2000.

[16] J.W. Klop. Term Rewriting Systems. In S. Abramsky, D. Gabbay, and T. Maibaum, editors, Handbook of Logic in Computer Science, volume I, pages 1-112. Oxford University Press, 1992.

[17] J.W. Lloyd. Foundations of Logic Programming. Springer-Verlag, Berlin, 1987. Second edition.

[18] S. Lucas. Termination of (Canonical) Context-Sensitive Rewriting. In Proc. of RTA'02, volume 2378 of Lecture Notes in Computer Science, pages 296-310. Springer, 2002.

[19] M. J. Maher. Equivalences of Logic Programs. In J. Minker, editor, Foundations of Deductive Databases and Logic Programming, pages 627-658. Morgan Kaufmann, Los Altos, Ca., 1988.

[20] H. Nilsson. Tracing piece by piece: affordable debugging for lazy functional languages. In Proceedings of the 1999 ACM SIGPLAN Int'l Conf. on Functional Programming, pages 36-47. ACM Press, 1999.

[21] H. Nilsson. How to look busy while being as lazy as ever. Journal of Functional Programming, 11(6):629-671, 2001.

[22] H. Nilsson and P. Fritzson. Algorithmic Debugging for Lazy Functional Languages. Journal of Functional Programming, 4(1):337-370, July 1994.

[23] J. T. O'Donell and C. V. Hall. Debugging in Applicative Languages. Lisp and Symbolic Computation, 1(2):113-145, 1988.

[24] P. Padawitz. Computing in Horn Clause Theories, volume 16 of EATCS Monographs on Theoretical Computer Science. Springer-Verlag, Berlin, 1988.

[25] A. Pettorossi and M. Proietti. Transformation of Logic Programs: Foundations and Techniques. Journal of Logic Programming, 19,20:261-320, 1994.

[26] A. Pettorossi and M. Proietti. Transformation of Logic Programs. In Handbook of Logic in Artificial Intelligence, volume 5, pages 697-787. Oxford University Press, 1998.

[27] J. Sparud and H. Nilsson. The architecture of a debugger for lazy functional languages. In M. Ducasse, editor, Proceedings of AADEBUG'95, Saint-Malo, France, May 1995.

[28] P. Wadler. Functional Programming: An angry half-dozen. ACM SIGPLAN Notices, 33(2):25-30, 1998.