Replace this file with prentcsmacro.sty for your meeting, or with entcsmacro.sty for your meeting. Both can be found at the ENTCS Macro Home Page.
Conditioned Slicing for First-Order Functional Logic Programs Diego Cheda1,2 DSIC Technical University of Valencia Valencia, 46022, Spain
Salvador Cavadini3 Facultad de Matem´atica Aplicada Universidad Cat´olica de Santiago del Estero Argentine
Abstract Program slicing is a method to isolate parts of a program that potentially affect the computed value at a point of interest, referred as the slicing criterion. While program slicing was found useful and widely investigated in the imperative programming paradigm, it didn’t receive much attention in the context of declarative programming. Conditioned slicing is a relevant kind of slicing because subsumes other slicing forms such as static and dynamic slicing. As far as we know, this kind of decomposition is not defined in the declarative paradigm. This paper proposes a definition and an algoritm for conditioned slices to be applied to first-order functional logic languages. Keywords: Program Slicing, Conditioned Slicing, Functional Logic Programming
1
Introduction
Program slicing is a technique for reduce program complexity by isolating parts of a program that potentially affect the computed value in some program point and/or variable of interest. Basically, a program slice consists of any subset of statements which are related to a slicing criterion. This technique was first defined by Weiser [?] —in the imperative programming paradigm— as a method for debugging. But it has been found useful for all those software engineering activities that involve program comprehension (e.g. testing, maintenance, reuse, etc.), because it reduces the size of the analyzed program, and allows concentrating solely on some relevant parts. 1
This work has been partially supported by the EU (FEDER) and the Spanish MEC/MICINN under grants TIN2005-09207C03-02, TIN2008-06622-C03-02, and Acci´on Integrada HA2006-0008. 2 Email:
[email protected] 3 Email:
[email protected] c 2008
Published by Elsevier Science B. V.
Cheda and Cavadini data Nat = Zero | Succ Nat data Triangle = Equilateral | Isosceles | Scalene data Pairs = Pair Nat Triangle main = triangle (Succ Zero) (Succ Zero) (Succ Zero) triangle x y z = Pair (sum x y z) (kind x y z) sum x y z = add x (add y z) add Zero x = x add (Succ x) y = (Succ (add x y)) kind x y z = if (x == y) then (if (y == z) then Equilateral else Isosceles) else (if (y == z) then Isosceles else Scalene) Fig. 1. Static slice w.r.t (main,Pair ⊥ >).
The large body of work on slicing imperative programs has not its counterpart at the declarative paradigm where the technique has received few attention [?,?,?,?,?]. We will use the program in Figure 1 to introduce the basic slicing techniques, static and dynamic, and to compare them against conditioned slicing. The program, written in multiparadigm language Curry [?], computes the perimeter of a triangle and its classification according to a given lengths of its sides, and return a pair with this information. In the static version of program slicing, the program inputs at run-time are unknown therefore the program simplification is performed taking account all possible inputs. For example, according to the static slicing approach defined in [?], the static slice of the program at Figure 1 w.r.t. the criterion (main,Pair ⊥ >) is the subprogram composed by the statements shown in black. The static slicing criterion is a pair (f unction, π) where f unction is a function to be sliced, and π [?] is used to denote the shape of a constructor term by ignoring part of its term structure. Two special symbols are used to denote which part of the f unction result is relevant: ⊥ denotes a subexpression of the value whose computation is not of interest, and > a subexpression which is relevant (this notation is based on the idea of liveness patterns [?]) Notice that the slice contains only those program parts that affect the result from the initial function main, and the classification of the triangle (second argument of the constructor Pair) while sentences in gray are not related to. By the contrary, in dynamic slicing the simplification is made taking into account a precise program input. That is, in order to be used, the technique requires a slicing criterion and the set of values used as program inputs for an specific program execution. For example, Figure 2 shows 4 the dynamic slice of the example program w.r.t. Equilateral,Pair ⊥ >), performed using the dynamic slic(main,Pair ing technique proposed in [?]. In this case, the slicing criterion is a tuple (f unction, partial value, π) where f unction is a function call with concrete argument values, partial value is partial value obtained as result from f unction call, and π denotes which part of the f unction result is relevant or not. Since the program control flow is known, dynamic slices are in general smaller than those obtained by static manner. 4
Like in the static slicing example, relevant program parts are in black, while discarded ones are in gray.
2
Cheda and Cavadini data Nat = Zero | Succ Nat data Triangle = Equilateral | Isosceles | Scalene data Pairs = Pair Nat Triangle main = triangle (Succ Zero) (Succ Zero) (Succ Zero) triangle x y z = Pair (sum x y z) (kind x y z) sum x y z = add x (add y z) add Zero x = x add (Succ x) y = (Succ (add x y)) kind x y z = if (x == y) then (if (y == z) then Equilateral else Isosceles) else (if (y == z) then Isosceles else Scalene) Fig. 2. Dynamic slice w.r.t. (main,Pair
Equilateral,Pair ⊥ >).
Conditioned slicing [?] is an intermediate approach between static and dynamic slicing. While in static slicing no information is known about program inputs and in dynamic slicing the precise input values are used, in conditioned slicing, the criterion is augmented with a first order logic formula that characterises a set of program inputs. Figure 3 is an example of this kind of slicing. The slicing criterion (f unction, φ, π) is a tuple containing the initial function call, a logical formula on the input variables, and a slicing pattern. In the example, the logical formula indicates that two of the length of sides must be equals. The program slice is composed of those parts of the program that can be (potentially) executed when a condition on its inputs holds true. Executions of the program and its conditioned slice, with inputs satisfying the given condition, are indistinguishable. The importance of conditioned slicing is due to its ability to provide a common slicing framework for both static and dynamic slices [?]. That is, conditioned slicing subsumes both techniques. The static slice can be computed using the conditioned slicing criterion as (f unction, T rue, π), and the dynamic slice as (f unction, φ, π) where φ is composed by concrete values on the inputs rather than a logical formula. For example, the static slice for Figure 1 can be represented by the conditioned slicing criterion as (main,True,Pair ⊥ >), and the dynamic slice for Figure 2 as (main,{x=(Succ Zero), y=(Succ Zero), z=(Succ Zero)},Pair ⊥ >). As far as we know, conditioned slicing has not been defined before in the declarative paradigm. The development of slicing techniques for the functional programming paradigm will help to provide better tools to help in debugging, testing, and comprehension tasks, facilitating the adoption of declarative languages [?]. 5 Therefore, the primary contributions of this paper are the following: •
We propose a definition of conditioned slicing for the domain of declarative programming.
5 Even there are ongoing works to produce better tools for functional programmers (e.g. Buddha [?], DDT [?], HOOD [?], Hat [?], among others), its use is not generalized due to problems such its use complexity, instability, immaturity (research tools), language coverage limitations, or simply, because user expectations are not fulfilled.
3
Cheda and Cavadini data Nat = Zero | Succ Nat data Triangle = Equilateral | Isosceles | Scalene data Pairs = Pair Nat Triangle main = triangle (Succ Zero) (Succ Zero) (Succ Zero) triangle x y z = Pair (sum x y z) (kind x y z) sum x y z = add x (add y z) add Zero x = x add (Succ x) y = (Succ (add x y)) kind x y z = if (x == y) then (if (y == z) then Equilateral else Isosceles) else (if (y == z) then Isosceles else Scalene) Fig. 3. Conditioned slice w.r.t. (main,x==y,Pair ⊥>).
(program)
R
::=
D1 . . . Dn
(functions)
D
::=
f (xn ) = e
(expressions)
e
::=
t | case t of {pk → ek }
(terms)
t
:: =
x | c(tn ) | f (tn )
(patterns)
p
::=
c(xn )
Fig. 4. Syntax of a simple first-order functional language. •
We propose a slicing technique suitable to be applied to first-order functional logic languages.
The rest of the paper is organised as follows. Section 2 recalls some preliminary concepts that will be used throughout the paper. Section 3 introduces our approach to conditioned slicing. Section 4 proposes a calculus to compute conditioned slices of first-order functional logic programs, and describes a first attempt to implement the technique. Section 5 presents the related work and compares the proposed conditioned slicing technique with other approaches. Finally, Section 6 concludes.
2
Preliminaries
For completeness, this section introduces some basic concepts of functional logic programming. A functional logic programming language [?] integrates the advantages of both functional (nested expressions, lazy evaluation and higher-order functions) and logic (built-in search, partial data structures, logic variables) programming paradigms. Figure 4 shows the syntax of a simple functional logic language (very similar to Curry). We will assume that an inductively sequential Term Rewriting System (TRS) [?] is a program 6 [?,?]. The syntax describes a program R as a sequence of function definitions Di . Each function is defined by only one rule such that the left-hand side has variables as argu6
A left-linear constructor-based TRS such that each function has a definitional tree.
4
Cheda and Cavadini
(fun eval)
f(tn ) =⇒id σ(e) if f(xn ) = e ∈ R, and σ = {xn 7→ tn }
(case select)
case c(tn ) of {pk → ek } =⇒id σ(ei ) if pi = c(xn ), c ∈ C, and σ = {xn 7→ tn }
(case guess)
case x of {pk → ek } =⇒σ σ(ei ) if σ = {x 7→ pi }, and i ∈ {1, . . . , k}
(case eval)
case t of {pk → ek } =⇒σ σ(case t0 of {pk → ek }) if t is not in head normal form and t =⇒σ t0 Fig. 5. Operational semantics (LNT Calculus).
ments, and the right-hand side is an expression. Expressions are built with terms and case expressions. Case expressions are used for pattern-matching where a pattern pk is are constructors of the type of x. Terms are variables (e.g., x, y, z), constructors (e.g., A, B, C), or function calls (e.g., f, g, h). Now, we will consider a simple program example to depict the language. Example 2.1 The Boolean function “less than or equal to” to be applied to natural numbers which are represented by terms built with constructor symbols Z[ero] and S[uccessor] (Peano numbers) can be defined in this language as: x ≤ y = case x of Z → true S(n) → case y of Z → false S(m) → n ≤ m Language semantics, shown at Figure 5, is based on the Lazy Narrowing with definitional Trees (LNT) calculus [?,?]. Concisely, we describe the semantics: fun eval: performs the unfolding of a function call. Variables are renamed in each evaluation step. case select: this rule implements the pattern matching and selects the appropriated branch of the case when the argument of the case is a constructor rooted term. case guess: if the argument of the case is a variable x, then it is possible to apply (non deterministically) any of the n defining rules for the case. case eval: if the argument of the case is a function call, then it must be evaluated. The transition relation e =⇒σ e0 is labeled with the substitution computed in the step. An LNT derivation, noted e0 =⇒∗σ en , is a sequence e0 =⇒σ1 . . . =⇒σn en , where σ = σn ◦ . . . ◦ σ1 , and it is successful when en is in head normal form. Example 2.2 Consider the program of Example 2.1 and the initial call Z ≤ y. The LNT calculus computes the following derivation: 5
Cheda and Cavadini
Z ≤ y=⇒id case Z of
(fun eval)
Z → true S(n) → case y of Z → false S(m) → n ≤ m =⇒id true
3
(case select)
Conditioned Slicing for First-Order Functional Logic Programs
In general, we will say that a slice is a subprogram obtained from the original program by deleting one or more terms from it. Here, we introduce the constructor symbol ⊥ to denote a deleted term in the right-hand side of a function definition. Symbolically, Definition 3.1 [Slice [?]] Given a program R = Dn with Di = (f (xm ) = e), R0 = Dn0 with Di0 = (f (xm ) = e0 ) is a slice of R, noted R0 R, iff e0 e where true if e0 = ⊥ or e0 = e true if e0 = case x of {pk 7→ e0k }, e0 e = e = case x of {pk 7→ ek }, and e0k ek for all k. f alse otherwise In the imperative paradigm, a conditioned slice is a subprogram that encodes all execution paths that can be produced by the execution of a program with inputs holding a first-order logic condition. Therefore, executions of the program and its conditioned slice, with inputs satisfying a given condition, are indistinguishable —i.e., they have the same behaviour—. Definition 3.2 [Conditioned Slicing Criterion] Given a program R, a slicing criterion for R is a tuple (f (xn ), φ, π) where f is a function symbol such that f (xn ) = e ∈ R, and φ is a first-order logical formula on the arguments x1 , . . . , xn of f , and π is a slicing pattern defined as follows [?]: π ∈ ⊥ | > | c(πk ) where c is a constructor symbol of arity k ≥ 0, ⊥ denotes a subexpression of the value whose computation is not relevant and > a subexpression which is relevant. Informally, a program R0 is a conditioned slice of R w.r.t. criterion (f (xn ), φ, π) iff R0 R such that all arguments xn of f satisfy φ, the execution of R and R0 are indistinguishable when executed with the same input satisfying φ, and only interested those part of the result v of the function call (f (xn ) that agreed to π. In order to satisfy that v has the shape of π, we need a concretization function γ such that, for a given π, returns the set of terms that can be obtained from π by replacing all occurrences of > and ⊥ by any constructor term. Moreover, we address a term t by its 6
Cheda and Cavadini
position p represented by a sequence of natural numbers 7 . The notation t|p denotes the subterm of t at position p. Definition 3.3 [Conditioned Slice] Given a program R, and a conditioned slicing criterion SC = (f (xn ), φ, π), R0 is a conditioned slice w.r.t. (f (xn ), φ, π), noted (R, SC), iff (i) R0 R, and (ii) xn satisfies φ, f (xn ) =⇒∗σ v with R, f (xn ) =⇒∗σ0 v 0 with R0 , where v and v 0 are values (i.e., in head normal form) such that v = v 0 , and σ = σ 0 , and (iii) v 0 ∈ γ(π) and for all subterm v|p and π|q / p = q and π|q 6= ⊥ where γ(π) is a concretization of π —i.e. v 0 has the shape of π—. A conditioned slice R0 is a subprogram of R where zero or more functions, terms or branches in case expressions were changed to ⊥ (or deleted) because they can not be part of any derivation that starts with an initial call f (xn ) satisfying φ, and only those relevant part of the result v from f (xn ) according to π are considered. Moreover, both R and R0 with the initial call f (xn ), xn satisfying φ, and the answer σ, have identical behaviour —i.e. both evaluate to the same result v—. Example 3.4 Recall the program R of Figure 2.1, (R, SC) with SC = (x ≤ y, φ, π) with φ = y > Z and π = > is R0 : x ≤ y = case x of Z → true S(n) → case y of Z → ⊥ S(m) → n ≤ m Notice that the case rule Z → false is replaced by Z → ⊥ , i.e. deleted, because when the program input satisfies φ there is not any possible derivation to which it may belong. That is, the expression is not needed to compute the result of the program given an initial call with y > Z. Example 3.5 Recall the program R of Figure 3, and its conditioned slicing (R, SC) with SC = (main, φ, π) with φ = {x == y}, and π = P air⊥>. All parts of the program in grey can be deleted because are not needed to compute the result of the program given the initial call main with φ = {x == y} —i.e., the triangle is isosceles—. Moreover, π = P air⊥> indicates that we are interested only in the second part of the pair —i.e., the classification of the triangle—.
4
Computing Conditioned Slices by Partial Evaluation
Pettorossi et al [?] establish a common methodology to program transformation techniques based on the similarities between them and identifies the basic steps of transformations: •
Symbolic computation,
•
search for regularities, and
7
denotes the root position
7
Cheda and Cavadini •
program extraction.
Program slicing is a kind of program specialisation and can be characterized by the above steps. The natural way to perform symbolic computation in multiparadigm languages is through narrowing because it is the underlying computation mechanism used in such languages. In particular, we are interested in building a finite narrowing tree —starting from an initial call— to represent all possible executions, and use it to guide the slicing process. Since an execution can be infinite, it is necessary to ensure that the (iterative) narrowing trees construction ends. Here arises the need of searching regularities while building narrowing trees by using some condition to conclude this phase (e.g., coverage analysis). Finally, in the program extraction stage a program is generated from the obtained narrowing trees. This narrowing-driven approach is an extension of the slicing technique introduced in [?]. Since Canfora et al [?] stays that a conditioned slice of a program is equivalent to the (forward) slice of its conditioned program, we propose a two phases slicing process in order to compute conditioned slices: •
We augment the language semantics to collect those part of the program that can be executed when a logical formula holds. Then, we symbolically execute the program from an initial function call using the augmented semantics, and considering a logical condition. The result of this process is a conditioned program that contain a set of parts that could be executed when the condition holds true. This extension is presented in the next section.
•
Once the program is simplified with respect to the condition on the inputs, we (forward) slicing the conditioned program with respect to a slicing pattern in order to obtain the conditioned slice [?].
We decompose our conditioned slicing criterion (f unction, φ, π) in two parts. For one hand, in the first phase of our approach, we consider a slicing criterion composed by (f unction, φ) to obtain a conditioned program. On the other hand, in the second slicing phase, we use (f unction, π) as slicing criterion to (forward) slice the conditioned program obtained in the first phase. 4.1
Extending the semantics to compute conditioned slices
First, in order to compute the slice w.r.t. the slicing criterion, we will augment the semantics of Figure 5 to collect the program position [?] of the right-hand side of each part of the program that can be executed when the logical formula φ holds —i.e., those part of the program that should not be deleted or replaced by ⊥ because are needed to perform an execution that satisfy φ—. The left and right-hand side of each rule in the semantics must be a tuple he, f, P, φi, where e is an expression to be evaluated, f is the function symbol that the expression e belongs to, P is a set that accumulates the program positions that should be deleted, and φ is the slicing condition. Figure 6 shows the program positions of the rules of program Example 2.1. Definition 4.1 [Program Position [?]] A program position is a pair (f, q) where f is a function symbol such that f (xn ) = e and q is the position of a subterm of e. 8
Cheda and Cavadini
case − of x1
Z → true2.1 y2.2.1
S(n) → case − of2.2 Z → false2.2.2.1
S(m) →≤2.2.2.2
S(n)2.2.2.2.1
S(m)2.2.2.2.2
Fig. 6. Program positions of the right-hand side of Example 2.1.
(fun eval)
hf (tn ), g, P, φi =⇒id hσ(e), f, pp ∪ P, φi if f (xn ) = e ∈ R, σ = {xn 7→ tn }, and if f ∈ SC and φ ⇒ σ(xn ) then pp = {(f, )} else pp = ∅
(case select)
hcase c(tn ) of {pk → ek }, g, P, φi =⇒id hσ(ei ), g, pp ∪ P, φi if pi = c(xn ), c ∈ C, and σ = {xn 7→ tn } then pp = {(g, 2.j) | j 6= i}
(case guess)
hcase x of {pk → ek }, g, P, φi =⇒σ hσ(ei ), g, pp ∪ P, φ0 i if σ = {x 7→ pi }, i ∈ {1, . . . , k}, and if g ∈ SC and φ ⇒ σ(x) then pp = {(g, 2.i)}, and φ0 = φ ∧ (x = pi ) else pp = ∅
(case eval)
hcase t of {pk → ek }, g, P, φi =⇒σ hσ(case t0 of {pk 7→ ek }), g, P, φi if t is not in head normal form and t =⇒σ t0 Fig. 7. Augmented operational semantics (based on LNT Calculus).
Figure 7 shows the augmented semantics used to collect the program positions of those parts of the program that are relevant w.r.t. the slicing criterion (f, φ). The augmented and the original semantics are very close. In fact, this extension preserves the standard behaviour, since we do not impose any restriction on the original semantics. 8 fun eval: performs the unfolding of a function call. When the unfolding function belongs to the slicing criterion and the predicate φ is satisfied by the argument values mapped by the substitution σ, we collect the root position of f —that means that the function can be executed—. case select: selects the appropriated branch of the case when the argument of the case is a constructor rooted, and collects program positions corresponding to the selected branch. case guess: if the argument of the case expression is a variable x, then it is possible to apply (non deterministically) any of the n defining rules for the case. When the unfolding function belongs to the slicing criterion and the predicate φ is satisfied by the values mapped by the substitution σ, we collect program positions of the selected branch —this 8
Since the augmented semantics does not restrict the standard semantics, a program executed under the extended semantics can loop too.
9
Cheda and Cavadini hx ≤ y, ≤, ∅, φi =⇒id hcase x of {Z→true;S(n)→case y of {Z→false; S(m)→n ≤ m}}, ≤, {(≤, )}, φi (function eval) =⇒{x7→Z} htrue, ≤, {(≤, ), (≤, 2.1)}, φi
(case guess)
hx ≤ y, ≤, ∅i =⇒id hcase x of {Z→true;S(n)→case y of {Z→false;S(m)→n ≤ m}}, ≤, {(≤, )}, φi (fun eval) =⇒{x7→S(x1 )} hcase y of {Z→false; S(m)→n ≤ m}, ≤, {(≤, ), (≤, 2.2)}, φ ∧ x = S(x1 )i
(case guess)
=⇒{y7→Z} hfalse, ≤, {(≤, ), (≤, 2.2)}, φ ∧ x = S(x1 ) ∧ y = Zi
(case guess)
hx ≤ y, ≤, ∅i =⇒id hcase x of {Z→true;S(n)→case y of {Z→false;S(m)→n ≤ m}}, ≤, {(≤, )}, φi (fun eval) =⇒{x7→S(x1 )} hcase y of {Z→false; S(m)→n ≤ m}, ≤, {(≤, ), (≤, 2.2)}, φ ∧ x = S(x1 )i =⇒{y7→S(x2 )} hx1 ≤ x2 , ≤, {(≤, ), (≤, 2.2), (≤, 2.2.2.2)}, φ ∧ x = S(x1 ) ∧ y = S(x2 )i ...
(case guess) (case guess)
Fig. 8. An example derivation with the augmented semantics.
means that it can be executed—. In other case, the rule is equal to the original semantics. Additionally, in order to increase the precision of the slice process, if the predicate φ is satisfied by the values mapped by the substitution, then we augment φ with x = pi . case eval: if the argument of the case is a function call, then it must be evaluated. In order to perform computations, we start from an initial state and the calculus computes (non deterministically) —by the application of rules of Figure 7— all successful derivations. The initial state I = hf (xn ), f, ∅, φi where f (xn ) belongs to SC = (f (xn ), φ), f is the current function symbol, and φ is the predicate to be satisfied. The final state F has the form he, g, P, φi where e is in head normal form, g is the current function symbol, P contains the program positions identified as needed by derivations with inputs satisfying φ. Example 4.2 Consider again the program R of Example 2.1, and the criterion SC = (x ≤ y, φ) with φ = y > Z, the derivations from hx ≤ y, ≤, ∅i are shown in Figure 8. The collected program position are {(≤, ), (≤, 2.1), (≤, 2.2), (≤, 2.2.2.2)}. These program positions corresponds to those parts of the program that can be executed when φ holds true. But {(≤, 2.2.2.1)} can be deleted because is not needed.
4.2
Conditioned slicing algorithm
At the beginning of Section 4, we succinctly drafted a basic idea of how to compute conditioned slices based on a three phases process. In this section we describe in more detail this algorithm based on the forward slicing method presented in [?]. Figure 9 presents a pseudocode of such process. 10
Cheda and Cavadini
Input: a program R, and SC = (f(xn ), φ) Output: sliced program S i = 0; Si = { f(xn ) }; repeat R0 = unfold(Si , R, φ); Si+1 = abstract(Si , R0 ); i = i + 1; until (Si = Si−1 ); return build slice(Si ,R); Fig. 9. Conditioned Slicing Algorithm.
Basically, given a program R and slicing criterion (f (xn ), φ) with xn (partially) instantiated, the algorithm proceeds by unfolding and obtaining narrowing trees of those parts of the program which are reachable from the initial call f (xn ) and satisfy φ. Then, the abstract operation ensures that the number of trees is finite by adding to the current set Si of function calls only those calls which are not yet considered. The unfold operator computes a finite narrowing tree starting from f (xn ). In order to stop the construction of narrowing trees, the process must decide whether a term must be evaluated in the next derivation step (or not) because it is not covered by others in the tree. We use the homeomorfic embbeding relation [?] to determine when a term is in another or not. Informally, s is embedded in t, s . t, if s can be obtained from t by deleting or “striking out” certain parts of t (i.e., operators). Definition 4.3 [Unfold] Given a set T of states, a program R, and a condition φ, the unfold function is defined as follows: S unf old(T, R, φ) = unf old0 (t, ∅) for each t ∈ T {ht0 , f 0 , S 0 , φ0 i} if ht, f, S, φi =⇒ ht0 , f 0 , S 0 , φ0 i and t0 . s where s ∈ D unf old0 = {ht0 , f 0 , S 0 , φ0 i}∪ (ht, f, S, φi, D) unf old0 (ht0 , f 0 , S 0 , φ0 i, D ∪ t0 ) if ht, f, S, φi =⇒ ht0 , f 0 , S 0 , φ0 i and t0 6 . s for each s ∈ D The abstract function takes the set Si of evaluated terms, and the set R0 of terms produced by the unfolding, to generate a new set Si+1 . This new set is a safe approximation of Si ∪ R0 such that the term t of each state ht, g, P, φi ∈ Si ∪ R0 is t−closed w.r.t. Si+1 . That is, each term t is an instance of another term t0 whose state belong to Si+1 . Definition 4.4 [Abstract] Given a set S of states, and an another set R of states to be added to S, the abstraction function is defined as follows: S abstract(S, R) = abstract0 (S, t) for each t ∈ R 11
Cheda and Cavadini
{t} if t 6 .t0 for each t0 ∈ S abstract0 (S, t) = ∅ if t . t0 and t is t-closed abstract0 (S, msg(t, t0 )) otherwise where msg(t, t0 ) is the most specific generalisation. 9 Once finished the unfold-abstract process, we have a set Si of states that includes all possible computations from the initial call f (xn ). Then, we can extract the conditioned slice of the program by deleting those program positions that do not satisfy the condition φ. Consider Example 3.4, the algorithm collects {(≤, ), (≤, 2.1), (≤, 2.2), (≤, 2.2.2.2)}, and deletes those program positions that not belongs to the collected set. Definition 4.5 [build slice] Given a set S of states, and a program R, the slice is built as follows: R if S = ∅ 0 f (xn ) = e otherwise build slice(S, R) = where f (xn ) ∈ R, e0 = deleteP rogP os(P P, R), P P = extractP rogP os(S) Function extractProgPos takes a set of states and extracts the component corresponding to the position of program; and function deleteProgPos takes a set of interested program positions P P and a program R, and deletes those program positions p of the program R such that p 6∈ P P . Clearly, there are two termination problems in the algorithm: •
Termination of the unfolding process (local termination), and
•
Termination of the repeat-until loop (global termination).
Both termination problems are approached by using the homeomorfic embedding relation. In the local termination problem we need to ensure that each (possibly infinite) narrowing derivation ends. Then, we use the homeomorfic embedding to decide whether a term t must be evaluated in the next derivation step. If t is covered by other term in the tree then the process is halted; otherwise t is evaluated. construction. In the global termination problem we must guarantee that the number of generated trees is finite. In order to do this, we also use the homeomorfic embbeding. In this case, the abstract function —that controls the global termination— decides when to add a new term to unfold, or to apply a generalisation, or to stop the narrowing tree building process (i.e., when there are no more functions to unfold).
9 A term t is a generalisation of t0 and t00 if both are instances of t. Moreover, t is the most specific generalisation if given any other generalisation s of t0 and t00 , t is an instance of s.
12
Cheda and Cavadini
4.3
Forward slicing
Once the program is simplified with respect to the condition on the inputs, we (forward) slice the conditioned program with respect to a slicing pattern in order to obtain the final conditioned slice. We use (f unction, π) as slicing criterion to (forward) slice the conditioned program obtained before. For example, in the program of Figure 3 the conditioned program obtained from the first phase contain all parts that can be executed when the inputs satisfies the condition x == y. But, we are only interested in the second part of the pair according to the slicing pattern P air>⊥ —i.e., the classification of the triangle rather than its perimeter—. Then we use a forward slicer [?] that return a program slice containing those parts of the original program which are reachable from the slicing criterion (main, P air>⊥). 4.4
Implementation
We have implemented a prototype version of a conditioned slicer that uses the technique proposed in this work. The slicer computes conditioned slices of programs written in a subset of Curry. The slicer itself is written using the PAKCS [?] implementation of Curry that provides a flat intermediate representation for programs, called FlatCurry, that we use to express patternmatching by case expressions to implement the extended semantics that collects potentially executed program points. The slicer was developed on the basis of the partial evaluator for Curry described in [?]. The prototype and some slice examples are available for downloading from http: //www.dsic.upv.es/˜dcheda/condslicing/
5
Related Work
We have presented the first definition and a calculus of conditioned slices of first-order functional logic programs. Both are inspired from their counterparts in the imperative paradigm of programming languages. The idea of slicing with a precondition was introduced in [?] —further developed in [?], [?] and [?]—. Precondition induced dead code is detected by symbolic execution of the program. The initial program state satisfying the precondition is propagated to all points in the program and then at each conditional program branch a test is made to check if it is possible to infer the condition’s Boolean value from the corresponding program state. The same approach was adopted in [?] with the addition of an automatic theorem prover to aid in the automation of condition checking. An equivalent method was proposed in [?] where strongest postcondition calculus is used to propagate the initial program state through the program. A less precise method for compute precondition slices is introduced in [?]. The technique avoids the use of theorem provers by applying abstract interpretation to reason about the effects of the precondition. This allows full automated preconditioned slicing at the price of precision. The above mentioned works can be used as a baseline to further develop conditioned slicing in the context of declarative programming. The slicing approach proposed in this paper has similarities to the partial evaluation algorithm proposed by Gallagher [?] and the narrowing-driven partial evaluation by Alpuente 13
Cheda and Cavadini
et al [?]. The relationship between slicing and partial evaluation was considered in elsewhere. Reps and Turnidge [?] informally show that program slicing performs a certain kind of program specialisation which is complementary to the specialisation operations performed by partial evaluation. More recently, Binkley et al [?] express both slicing and partial evaluation using a projection framework, and then formally compare the two techniques. They prove that —for terminating programs— a residual program produced by partial evaluation is semantically equivalent to a conditioned slice. However, for testing, comprehension, and debugging (and many other applications) it is very important to conserve the program syntax rather than only its semantics. Unlike partial evaluation, our approach is syntax and semantics preserving. As mentioned, our approach is an extension of the forward slicing technique introduced by Silva and Vidal [?]. This approach uses an extension of partial evaluation, but does not consider the use of a first-order logical condition as slicing criterion.
6
Conclusions
This work, as far as we know, is the first to define conditioned slice for first-order functional logic languages. Along with the definition, we provide an algorithm for conditioned slice extraction. The aim of this slicing method is to obtain, from a source program, a subprogram that encodes all possible derivations that can be produced by the execution of a program with inputs characterised by a first-order logic condition. This way, both programs are guaranteed to produce the same derivation sequence if both are executed with an input satisfying the first-order logic formula. Currently, as most of the proposed slicing techniques for declarative languages, our slicing method is not implemented, we developed a prototype that implements the technique proposed in this work. The slicer will allow us to test the practicality of the approach proposed in this work, and to test different alternatives to improve the precision of the technique. Additionally, we aim to increase the precision of our approach in order to obtain smaller slices by combining different approaches. A long term goal is to generalise our research in order to compute other kinds of slices (static, dynamic, quasi-static, etc.) like [?].
14