**7. A declarative debugging tool of missing answers in** T OY

In this section, we discuss the implementation in the T OY system of a tool based on the debugging method presented in the previous section. The current prototype only supports the Herbrand constraint domain H, although the same principles can be applied to other constraint domains D.

We summarize first the normal process followed by the T OY system when compiling a source program P.*toy* and solving an initial goal *G* with respect to P. During the compilation process the system translates a source program P.*toy* into a Prolog program P.*pl* including a predicate for each function in P. For instance the function even of our running example is transformed into a predicate

even(N,R,IC,OC):- ... code for even ... .

where the variable N corresponds to the input parameter of the function, R to the function result, and IC,OC represent, respectively, the input and output constraint store. Moreover, each goal *G* of P is also translated into a Prolog goal and solved with respect to P.*pl* by the underlying Prolog system. The result is a collection of answers which are presented to the user in a certain sequence, as a result of Prolog's backtracking.

If the computation of answers for *G* finishes after having collected finitely many answers, the user may decide that there are some missing answers (*incompleteness symptom*, in the 22 Will-be-set-by-IN-TECH

*aca* fDiff (2:2:1:G) → *F*<sup>2</sup> ⇒ (*F*<sup>2</sup> �→ ⊥) asserts that the *undefined value* ⊥ is the only possible result for the function call fDiff (2:2:1:G), while the user expects also the result 2. Therefore, the user will judge this *aca* as invalid. The node where it sits (enclosed within a double box in Fig. 7) has no children and thus becomes buggy, leading to the diagnosis of fDiff as incomplete. This particular incompleteness symptom could be mended by placing the third rule for fDiff within the program. Our last result is a refinement of Theorem 7. It

guarantees that declarative diagnosis with *ANPT*s used as *CT*s leads to the correct detection

**Theorem 8. (***ANPT***s Lead to the Diagnosis of Incomplete Functions)** *As in Theorem 7, assume that an incompleteness symptom has been observed for a given CFLP*(D)*-program* P *as explained in Definition 4, with intended interpretation* IP*, admissible initial goal G, and finite disjunction of*

*and the ANPT constructed from any NPT witnessing this derivation, has some buggy node. Moreover,*

In this section, we discuss the implementation in the T OY system of a tool based on the debugging method presented in the previous section. The current prototype only supports the Herbrand constraint domain H, although the same principles can be applied to other

We summarize first the normal process followed by the T OY system when compiling a source program P.*toy* and solving an initial goal *G* with respect to P. During the compilation process the system translates a source program P.*toy* into a Prolog program P.*pl* including a predicate for each function in P. For instance the function even of our running example is transformed

even(N,R,IC,OC):- ... code for even ... .

where the variable N corresponds to the input parameter of the function, R to the function result, and IC,OC represent, respectively, the input and output constraint store. Moreover, each goal *G* of P is also translated into a Prolog goal and solved with respect to P.*pl* by the underlying Prolog system. The result is a collection of answers which are presented to the

If the computation of answers for *G* finishes after having collected finitely many answers, the user may decide that there are some missing answers (*incompleteness symptom*, in the

*<sup>i</sup>*∈*<sup>I</sup> Si, computed by an admissible goal solving system. Then* P<sup>−</sup> �*CNPC*(D) *<sup>G</sup>* ⇒ *D,*

<sup>P</sup> *which is incomplete with respect to the user's intended*

Fig. 7. *CT* for the declarative diagnosis of missing answers

**7. A declarative debugging tool of missing answers in** T OY

user in a certain sequence, as a result of Prolog's backtracking.

of incomplete program functions.

*each such buggy node points to an axiom* (*f*)−

*answers D* =

*interpretation* IP*.*

constraint domains D.

into a predicate

terminology of Definition 4) and type the command /missing at the system prompt in order to initiate a *debugging session*. The debugger proceeds carrying out the following steps:

1. The object program P.*pl* is transformed into a new Prolog program PT. *pl*. The debugger can safely assume that P.*pl* already exists because the tool is always initiated *after* some missing answer has been detected by the user. The transformed program P<sup>T</sup> behaves almost identically to P, being the only difference that it produces a suitable *trace* of the computation in a text file. For instance here is a fragment of the code for the function even of our running example in the transformed program:

```
1 % this clause wraps the original predicate
2 even(N,R,IC,OC):-
3 % display the input values for even
4 write(' begin('), write(' even,'), writeq(N), write(','),
5 write(R), write(', '), writeq(IC), write(').'), nl,
6 % evenBis corresponds to the original predicate for even
7 evenBis(N,R,IC,OC),
8 % display an output result
9 write(' output('), write(' even,'), writeq(N), write(','),
10 write(R), write(', '), writeq(OC), write(').'), nl.
11 % when all the possible outputs have been produced
12 even(N,R,IC,OC):-
13 nl, write(' end(even).'), nl,
14 !,
15 fail.
16 evenBis(N,R,IC,OC) :- ... original code for even ... .
```
As the example shows, the code for each function now displays information about the values of the arguments and the contents of the constraint store at the moment of invoking any user defined function (lines 4-5). Then the predicate corresponding to the original function, now renamed with the Bis suffix, is called (line 7). After any successful function call the trace displays again the values of the arguments and the result, which may have changed, and the contents of the output constraint store (lines 9, 10). A second clause (lines 12-15) displays the value end when the function has exhausted its possible outputs. The clause fails in order to ensure that the program flow is not changed. The original code for each function is kept unaltered in the transformed program except for the renaming (evenBis instead of even in the example, line 16). This ensures that the program will behave equivalently to the original program, except for the trace produced as a side-effect.


1. Producing the transformed PT. *pl* from P.*pl* is proportional in time to the number of functions of the program, and does require an insignificant amount of system memory

<sup>145</sup> A Semantic Framework for the Declarative Debugging

2. The computation of the goal for PT. *pl* requires almost the same system resources as for P.*pl* because writing the trace causes no significant overhead in our experiments. 3. Producing the *CT* from the trace is not straightforward and requires several traverses of the trace. Although more time-consuming due to the algorithmic difficulty, this process

4. The most inefficient phase in our current implementation is the graphical interface. Although it would be possible to keep in memory only the portion of the tree displayed at each moment, our graphical interface loads the whole *CT* in main memory. We plan to improve this limitation in the future. However the current prototype can cope with *CT*s

5. As usual in declarative debugging, the efficiency of the tool depends on the computation tree size, which in turn usually depends on the size of the data structures required and not

A different issue is the difficulty of answering the questions by the user. Indeed in complicated programs involving constraints the *aca*s can be large and intricate, as it is also the case with other debugging tools for *CLP* languages. Nevertheless, our prototype works reasonably well in cases where the goal's search space is relatively small, and we believe that working with such goals can be useful for detecting many programming bugs in practice. Techniques for simplifying *CT*s should be worked out in future improvements of the prototype. For instance, asking the user for a concrete missing instance of the initial goal and starting a diagnosis

We have presented a logical and semantic framework for the declarative diagnosis of wrong and missing computed answers in *CFLP*(D), a generic scheme for Constraint Functional-Logic Programming over a given constraint domain D which combines the expressivity of lazy *FP* and *CLP* languages. The diagnosis technique of wrong answers represents the computation which has produced a wrong computed answer by means of an abridged proof tree whose inspection leads to the discovery of some erroneous program rule responsible for the wrong answer. The logical correctness of the method can be formally proved thanks to the connection between abbreviated proof trees and program semantics. The method for missing answers relies on computation trees whose nodes are labeled with *answer collection assertions* (*aca*s). As in declarative diagnosis for *FP* languages, the values displayed at *aca*s are shown in the most evaluated form demanded by the topmost computation. Following the *CLP* tradition, we have shown that our computation trees for missing answers are abbreviated proof trees in a suitable inference system, the so-called *Constraint Negative Proof Calculus*. Thanks to this fact, we can prove the correctness of our diagnosis method for any admissible goal solving system whose recollection of computed answers can be represented by means of a proof tree in the constraint negative proof calculus. As far as we know, no comparable result was previously

containing thousands of nodes, which is enough for medium size computations.

since each predicate is transformed separately.

session for the instantiated goal might be helpful.

available for such an expressive framework as *CFLP*.

**8. Conclusions and future work**

on the program size.

only keeps portions of the trace in memory at each moment.

of Wrong and Missing Answers in Declarative Constraint Programming

explained in Section 6, as responsible for the missing answers. The current implementation of the prototype is available at http://gpd.sip.ucm.es/rafav/.


Fig. 8. Snapshots of the prototype of missing answers

Fig. 8 shows how the tool displays the *CT* corresponding to the debugging scenario discussed in Section 2. The initial goal is not displayed, but the rest of the *CT* corresponds to Fig. 7, whose construction as *ANPT* has been explained in Section 6. When displaying an *aca <sup>f</sup> tn* → *<sup>t</sup>* ✷ *<sup>S</sup>* ⇒ *<sup>i</sup>*∈*<sup>I</sup> Si*, the tool uses list notation for representing the disjunction *<sup>i</sup>*∈*<sup>I</sup> Si* and performs some simplifications: useless variable bindings within the stores *S* and *Si* are dropped, as in the *aca* displayed as gen 2 1 -> A ==> [A = 2:1:\_] in Fig. 7; and if *t* happens to be a variable *<sup>X</sup>*, the case {*<sup>X</sup>* �→ ⊥} is omitted from the disjunction *<sup>i</sup>*∈*<sup>I</sup> Si*, so that the user must interpret the *aca* as a collection of the possible results for *X* other than the undefined value ⊥. The tool also displays the underscore symbol \_ at some places. Within any *aca*, the occurrences of \_ at the right hand side of the implication ⇒ must be understood as different existentially quantified variables, while each occurrence of \_ at the left hand side of ⇒ must be understood as ⊥. For instance, 1 // \_ -> A ==> [A = 1] is the *aca* 1 // ⊥ → *A* ⇒ {*A* �→ 1} as displayed by the tool. Understanding the occurrences of \_ at the left hand side of ⇒ as different universally quantified variables would be incorrect. For instance, the *aca* 1 // ⊥ → *<sup>A</sup>* ⇒ {*<sup>A</sup>* �→ <sup>1</sup>} is valid with respect to the intended interpretation IPfD of PfD, while the statement ∀*X*. (1 // *X* → *A* ⇒ {*A* �→ 1}) has a different meaning and is not valid in IPfD.

In the debugging session shown in Fig. 8 the user has selected the *Divide & Query* strategy (Silva, 2006) in order to find a buggy node. The lower part of the left-hand side snapshot shows the first question asked by the tool after selecting this strategy, namely the *aca* fDiff 1:2:2:1:\_ -> A ==> [A = 1]. According to her knowledge of IPfD the user marks this *aca* as invalid. The strategy now prunes the *CT* keeping only the subtree rooted by the invalid *aca* at the previous step (every *CT* with an invalid root must contain at least one buggy node). The second question, which can be seen at the right-hand side snapshot, asks about the validity of the *aca* fDiff 2:2:1:\_ -> A ==> [] (which in fact represents fDiff 2:2:1:⊥ → *<sup>A</sup>* ⇒ {*<sup>A</sup>* �→ ⊥}, as explained before). Again, her knowledge of IPfD leads the user to expect that fDiff 2:2:1:⊥ can return some defined result, and the *aca* is marked as invalid. After this question the debugger points out at fDiff as an incomplete function, and the debugging session ends.

Regarding the efficiency of this debugging method our preliminary experimental results show that:

24 Will-be-set-by-IN-TECH

Fig. 8 shows how the tool displays the *CT* corresponding to the debugging scenario discussed in Section 2. The initial goal is not displayed, but the rest of the *CT* corresponds to Fig. 7, whose construction as *ANPT* has been explained in Section 6. When displaying an *aca*

and performs some simplifications: useless variable bindings within the stores *S* and *Si* are dropped, as in the *aca* displayed as gen 2 1 -> A ==> [A = 2:1:\_] in Fig. 7; and if *t*

that the user must interpret the *aca* as a collection of the possible results for *X* other than the undefined value ⊥. The tool also displays the underscore symbol \_ at some places. Within any *aca*, the occurrences of \_ at the right hand side of the implication ⇒ must be understood as different existentially quantified variables, while each occurrence of \_ at the left hand side of ⇒ must be understood as ⊥. For instance, 1 // \_ -> A ==> [A = 1] is the *aca* 1 // ⊥ → *A* ⇒ {*A* �→ 1} as displayed by the tool. Understanding the occurrences of \_ at the left hand side of ⇒ as different universally quantified variables would be incorrect. For instance, the *aca* 1 // ⊥ → *<sup>A</sup>* ⇒ {*<sup>A</sup>* �→ <sup>1</sup>} is valid with respect to the intended interpretation IPfD of PfD, while the statement ∀*X*. (1 // *X* → *A* ⇒ {*A* �→ 1}) has a different meaning and is

In the debugging session shown in Fig. 8 the user has selected the *Divide & Query* strategy (Silva, 2006) in order to find a buggy node. The lower part of the left-hand side snapshot shows the first question asked by the tool after selecting this strategy, namely the *aca* fDiff 1:2:2:1:\_ -> A ==> [A = 1]. According to her knowledge of IPfD the user marks this *aca* as invalid. The strategy now prunes the *CT* keeping only the subtree rooted by the invalid *aca* at the previous step (every *CT* with an invalid root must contain at least one buggy node). The second question, which can be seen at the right-hand side snapshot, asks about the validity of the *aca* fDiff 2:2:1:\_ -> A ==> [] (which in fact represents fDiff 2:2:1:⊥ → *<sup>A</sup>* ⇒ {*<sup>A</sup>* �→ ⊥}, as explained before). Again, her knowledge of IPfD leads the user to expect that fDiff 2:2:1:⊥ can return some defined result, and the *aca* is marked as invalid. After this question the debugger points out at fDiff as an incomplete

Regarding the efficiency of this debugging method our preliminary experimental results show

happens to be a variable *<sup>X</sup>*, the case {*<sup>X</sup>* �→ ⊥} is omitted from the disjunction

*<sup>i</sup>*∈*<sup>I</sup> Si*, the tool uses list notation for representing the disjunction

*<sup>i</sup>*∈*<sup>I</sup> Si*

*<sup>i</sup>*∈*<sup>I</sup> Si*, so

of the prototype is available at http://gpd.sip.ucm.es/rafav/.

Fig. 8. Snapshots of the prototype of missing answers

*<sup>f</sup> tn* → *<sup>t</sup>* ✷ *<sup>S</sup>* ⇒

not valid in IPfD.

that:

function, and the debugging session ends.

explained in Section 6, as responsible for the missing answers. The current implementation


A different issue is the difficulty of answering the questions by the user. Indeed in complicated programs involving constraints the *aca*s can be large and intricate, as it is also the case with other debugging tools for *CLP* languages. Nevertheless, our prototype works reasonably well in cases where the goal's search space is relatively small, and we believe that working with such goals can be useful for detecting many programming bugs in practice. Techniques for simplifying *CT*s should be worked out in future improvements of the prototype. For instance, asking the user for a concrete missing instance of the initial goal and starting a diagnosis session for the instantiated goal might be helpful.
