A Slicer for Functional Logic Programs
|
|
|
| ||||||||||||||||||||||||||||||||||||||||||
|
|
|
| ||||||||||||||||||||||||||||||||||||||||||
|
|
|
| ||||||||||||||||||||||||||||||||||||||||||
|
|
Modern functional logic languages like Curry and Toy combine the most important features of functional logic languages, e.g., lazy evaluation and non-determinism. Due to the complexity of their operational semantics, following the actual trace of a computation is often useless. In the context of (purely) functional lazy languages like Haskell, this drawback is overcome by introducing higher-level models that hide the details of lazy evaluation (e.g., Hood, Freja, Buddha, and Hat). Many of these approaches are based on the construction of a redex trail, a directed graph which records copies of all values and redexes reducible expressions) of a computation, with a backward link from each reduct to the parent redex that created it.
Recently, Brassel et al introduced an instrumented version of an operational semantics for the kernel of lazy functional logic languages that returns, not only the computed values and bindings, but also a trail of the computation. As in the Augmented Redex Trail approach, this trail can be used to perform tracing and algorithmic debugging, as well as to observe data structures through a computation. Moreover, the correctness of the computed trail is formally proved, which amounts to say that it contains all (and only) the reductions performed in a computation. While tracing-based debugging provides a powerful tool for inspecting erroneous computations, the task still remains complex. A well-known debugging technique for imperative programs is based on slicing [1], a method for decomposing programs by analyzing their data and control flow. Roughly speaking, a program slice consists of those program statements which are (potentially) related with the values computed at some program point and/or variable, referred to as a slicing criterion. Program slices are usually computed from a program dependence graph that makes explicit both the data and control dependences for each operation in a program. Program dependences can be traversed backwards or forwards (from the slicing criterion), which is known as backward or forward slicing, respectively. Additionally, slices can be dynamic or static, depending on whether a concrete program's input is provided or not. A complete survey on slicing can be found, e.g., in [2]. When debugging programs, we usually start from a particular computation that outputs an incorrect value. In this situation, dynamic slicing may help the programmer to find the location of the bug by extracting a program slice containing only the sentences whose execution influences the incorrect value. Therefore, we are mainly interested in dynamic slicing which only reflects the actual dependences of the erroneous execution, thus producing smaller -more precise- slices than static slicing. To the best of our knowledge, there is no dynamic slicing tool for lazy languages like Haskell or Curry. Therefore, our original motivation was to fill this gap with a formal technique for dynamic slicing in lazy functional logic languages (nevertheless, the basic ideas also apply to pure lazy functional languages like Haskell). Rather than starting from scratch, our technique relies on a slight extension of redex trails. Basically, we extend the redex trail model in order to also store the location, in the program, of each reduced expression. Then, we compute a dynamic slice by first gathering the reachable nodes of the redex trail -according to the slicing criterion- and, then, deleting those program expressions which are not related to the collected nodes. From our point of view, dynamic slicing may provide a complementary -rather than alternative- approach to tracing. A clear advantage of our approach is that one can enhance existing tracers with slicing capabilities with a modest implementation effort, since the same data structure -the redex trail- is used for both tracing and slicing.
|
| ||||||||||||||||||||||||||||||||||||||||||
|
|
|
| ||||||||||||||||||||||||||||||||||||||||||
|
|
The slicer is divided in the following modules: Tracer: Firstly, a program is executed with an incorporated instrumented interpreter which, as a side effect, stores the redex trail of the computation in a file. The tracer is implemented in Haskell and accepts lazy, first-order, functional-logic programs that can be traced either backwards or forwards. Here, we have extended the original tracer in order to also store program positions in the nodes of the redex trail. The extension was not difficult and can also be done with other tracers based on redex trails. Viewer: Once the computation terminates (or it is aborted if it does not terminate), the viewer reads the file with the redex trail and allows the user to navigate through the entire computation. The viewer is implemented in Curry, a conservative extension of Haskell with features from logic programming including logical variables and non-determinism. In this case, no extension was required. Slicer: Given a redex trail and a slicing criterion, the slicer outputs a set of program positions that uniquely identify the associated program slice. The slicing tool is implemented in Curry, and includes an editor that shows the original program and, whenever a slice is computed, it highlights the expressions belonging to the computed slice (see figure below).
The Slicing Tool Specializer: For a given initial call, the specializer
extracts an executable specialized version of the program w.r.t.
this call. This specialized version can be further simplified with a
post-process of amorphous slicing. |
| ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
We have conducted some experiments in order to measure the performance of the tool. Next table summarizes the results of the experiments:
The meaning of the columns is the following: Benchmark: It is the name of the benchmark used in the experiment (these benchmarks belong to the Curry's benchmarks collection). [1] Mark Weiser [2] Frank Tip |
|