Information Processing Letters 129 (2018) 16–24
Contents lists available at ScienceDirect
Information Processing Letters www.elsevier.com/locate/ipl
Equivalence checking of two functional programs using inductive theorem provers Moussa Demba Al Jouf University, College of Computer & Information Science, 2014 Sakaka, Saudi Arabia
a r t i c l e
i n f o
Article history: Received 6 October 2015 Received in revised form 26 June 2017 Accepted 6 August 2017 Available online 8 September 2017 Communicated by L. Viganò Keywords: Program correctness Inductive theorem proving Generalization Loop invariant Program schemas
a b s t r a c t In this paper, we present a new method for generating inductive hypothesis for proving an equivalence between two imperative programs: one recursive and one iterative. The basic premise is to convert both programs in the form of term rewriting system that is acceptable to inductive theorem provers and to generate inductive generalization lemmas that such a theorem prover can discharge. As finding inductive generalizations automatically is an undecidable problem, we will focus on a certain class of recursive programs that cannot be handled by existing tools. We develop criteria under which we can prove the equivalence of two programs. © 2017 Published by Elsevier B.V.
1. Introduction One of the major obstacles in achieving mechanical verification of iterative programs (tail-recursive ones in a declarative form) is to derive useful loop invariants. However, inferring suitable loop invariants, even for simple iterative programs, is far from being trivial. It is also wellknown that the verification of iterative programs requires loop invariants. By similarity, the proof of tail-recursive programs requires generalization lemmas or theorem generalization. Let P and P be two imperative programs where P is an iterative version of the recursive program P. Assuming that P is already known correct and one needs to prove that P is also correct w.r.t. the given program P: P and P are equivalent (denoted P P ). As most of the existing theorem provers are not designed for proving imperative programs, we assume that P and P are rewritten in the form of constructor-based term rewriting systems, say R and R respectively, that are acceptable to induc-
E-mail address:
[email protected]. http://dx.doi.org/10.1016/j.ipl.2017.08.002 0020-0190/© 2017 Published by Elsevier B.V.
tive theorem provers (ITPs). Many works have been done for transforming imperative programs into functional ones [9,6,17,14]. Instead of looking for a loop invariant to prove that P P , we look for a generalization lemma to prove that R R using an ITP. If one proves that R R , then P P follows immediately. In general, to prove R R, a particular property say π , referred to us as correctness property, is proved by induction. This correctness property characterizes the equivalence of R and R , that is if |= π (inductive validity), then R R . As it has been demonstrated that an inductive proof of a correctness property is problematic for inductive theorem provers [14,13,5,9]: an inductive proof of π will fail as the induction hypothesis cannot be used to simplify the induction conclusion. Generally, a user-supplied lemma, say φ , is needed in order to complete the proof of π . φ is called a generalization lemma of π , i.e. there is substitution σ s.t. φ σ = π : if |= φ then |= π . The proof of φ is generally straightforward and φ corresponds, somehow, to the loop invariant needed for proving equivalence of P and P . However, finding such a generalization lemma in the course of an inductive proof attempt in an automatic
M. Demba / Information Processing Letters 129 (2018) 16–24
Sum(s: list){ if(s==[]) return 0; return Car(s)+Sum(Cdr(s)); }
17
iterSum(s: list, acc: int){ y=s ; While(y!=[]){ acc = acc + Car(y); y = Cdr(y); } return acc; }
Fig. 1. Two imperative programs for computing the sum of the elements in a list s.
way requires considerable efforts and is a challenging domain of research. Many heuristics have been proposed for automatic generation of generalization lemmas [16,12,5, 8,10]. Such major heuristics require other user-supplied lemmas and lead, sometimes, to false conjectures (overgeneralization). In this paper we propose another method for generating generalization lemma that is correct by construction. 1.1. Motivating example Let us look at the imperative program Sum where for a list s = [s1 , s2 , ..., sn ], Sum(s) is defined as:
⎧ ⎪ ⎨0 n Sum(s) = ⎪ si ⎩
if s=[] otherwise
i =1
with [] is the empty list, and the program iterSum is an iterative version of Sum. We use the list functions Car and Cdr with Car (s) = s1 and Cdr (s) = [s2 , ..., sn ]. For the first call to the function iterSum, the accumulator parameter acc is initialized to the constant 0, i.e., iter Sum(s, 0) = Sum(s). (See Fig. 1.) Now, suppose we want to establish the correctness of the program iterSum with respect to the semantics of the program version Sum, i.e., iter Sum(s, 0) = Sum(s). Next, we need to find out an appropriate loop invariant of the while-loop. In our case, it is sufficient to prove the verification conditions (i)–(iii) are satisfied before, inside and at ` Hoare logic: the end of the loop body ala (i) pre-condition: acc = 0 if y = s (ii) loop body: acc+Sum(y) = Sum(s) if y = [], and (iii) post-condition: acc = Sum(s) if y = [] It is easy to check that the following equation is a loop invariant:
acc + Sum( y ) = iter Sum( y , acc )
(1)
To prove the iterative program iterSum, the user has to prove in some way the correctness of (1). However, finding such suitable transformations for an appropriate loop invariant discovery is usually difficult and requires intuition. Our approach consists first to rewrite the imperative programs Sum and iterSum into constructor-based termrewriting systems, say Sum and tailSum, respectively:
Sum
⎧ ⎪ Sum([]) ⎪ ⎪ ⎪ ⎨ Sum(a : x))
→0
→ add(a, Sum(x)) ⎪ add ( 0 , x ) → x ⎪ ⎪ ⎪ ⎩ add(s(x), y ) → s(add(x, y ))
⎧ ⎪ tailSum([], y ) →y ⎪ ⎪ ⎪ ⎨ tailSum(a : x, y ) → tailSum(x, add( y , a)) tailSum ⎪ add(0, x) →x ⎪ ⎪ ⎪ ⎩ → s(add(x, y )) add(s(x), y ) n times
where s(n) = n + 1 and sn (0) stands for s(s(....s(0)...)), [] is the empty list and: is a list constructor. Here, the function tailSum stands for tail-recursive function and corresponds to the while-loop, i.e., the iterSum function above, and the extra function add implements the addition. Verifying whether or not tailSum is correct with respect to the semantics of the function Sum (or the two versions are equivalent) is equivalent to check if the correctness property
π ≡ tailSum(xs, 0) = Sum(xs)
(2)
holds, i.e., |= ∀x.(tailSum(xs, 0) = Sum(xs)). To prove (2), an induction on x can be attempted using the principle of structural induction. For the base case, x is instantiated to the empty list [], and yields tailSum([], 0) = Sum([]) which can be simplified to true. In the induction step however, the conclusion is tailSum(a : y , 0) = Sum(a : y ) which can be unfolded using the recursive rules of tailSum and Sum to obtain
tailSum( y , add(0, a)) = add(a, Sum( y ))
(3)
But unfortunately, the proof fails as the induction hypothesis (2) cannot be used to simplify the conclusion (3). Indeed, the accumulator (second) argument of tailSum in the induction hypothesis (2) is initialized to a constant 0, while in the induction conclusion it is a term add(0, s(a)) , i.e. there is a problem of mismatching. This kind of theorems with accumulator variables (quite common in iterative programs) represents a major obstacle for ITPs. However, given the following generalization lemma instead, ITPs can prove it very smoothly. This kind of theorems with accumulator variable (quite common in iterative programs) represents a major obstacle for ITPs. Given the following generalization lemma instead, ITPs can prove it very smoothly.
φ ≡ tailSum(x, y ) = add( y , Sum(x))
(4)
Hereafter, we will refer (4) as generalization lemma. Note that (4) is a generalization of (2) and corresponds to (1). Now, to prove (2), it is sufficient to prove that (4) holds for all y, and any violation of (4) means that tailSum is not correct with respect to the semantics of Sum. Th proof of (4) can be established by induction on x. Unfortunately, speculating such suitable generalizations during a proof attempt is based mostly on user’s experience.
18
M. Demba / Information Processing Letters 129 (2018) 16–24
In this paper, we present a method for an automatic discovery of generalization lemmas for a restricted class of programs. 1.2. Contributions The contributions of this paper to the problem of program verification are as follows:
• We define schematic programs for proving concrete ones. • A schematic generalization lemma is computed directly from the given specification. Further, the schematic generalization can be instantiated and reused to obtain concrete generalization lemmas. • We define conditions under which schematic generalization lemmas are valid. • Clearly the user has no need to provide the correctness property. The rest of the paper is organized as follows. Section 2 presents some basic notions in rewriting, Section 3 describes the proposed method and give conditions under which the correctness of tail-recursive programs can be deduced. Finally, Section 4 presents related works and Section 5 concludes the paper. 2. Preliminaries We introduce some notions of term rewriting systems and for more details see [15]. Throughout this paper represents a set of function symbols, V a countably infinite set of variables and θ a substitution. Variables will be denoted x, y, z, u, v, w, v’, w’, etc., if needed with indices. T (, V) is the set of all terms built from and V. By V(t ) we denote the set of all variables occurring in a term t. A linear term is a term in which any variable appears at most once. A term rewriting system is a set of write rules of the form l → r such that l and r have the same sort, l is linear, l ∈ / V and V(r ) ⊆ V(l). We call l the left-hand side (lhs) and r the right-hand side (rhs) of a rule. We assume that is partitioned into three disjoint sets = C ∪ F ∪ , where C is the set of constructors, F is the set of defined function symbols, i.e., F = { f | f (t 1 , ..., tn ) → r ∈ R}, and is the set of pattern variables (disjoint from V). The elements of can be instantiated by constants, defined function symbols or variables. The terms in T (C, V) are called constructor terms. A TRS is constructor-based if for any rule l → r ∈ R, l is a pattern. Hereafter, we consider constructor-based term rewriting systems (or TRS for simplicity), and all TRS are supposed sufficiently complete and terminating. An equation or conjecture t = s is an element of T (, V) or true. A term t is an instance of another term s (or s is more general than t) if there is a substitution θ with sθ = t. This definition of generality can be extended to equations: an equation s1 = t 1 is more general than an equation s2 = t 2 (or s2 = t 2 is an instance of s1 = t 1 ) if there is a substitution θ with s1 θ = s2 and t 2 θ = t 2 . We consider special (indexed) constants 2i (i ≥ 1) called holes such that 2i ∈ / . A well-founded order on a set E of elements is a
transitive relation where there is no infinite decreasing chain e 0 e 1 ... with e i ∈ E. The binary relation is the reflexive closure of . We use the notation t 1 ≡ t 2 (resp. t 1 = t 2 ) to denote that the terms t 1 and t 2 are syntactically identical (resp. equivalent). 3. The proposed approach 3.1. Program schemas Intuitively, a program schema or template represents a class of possible computer (concrete) programs, which in turn are possible interpretations of the program schema. We group linear and non-linear recursive programs in a same program schema, and this is legitimate because a linear recursive TRS can be seen as a special case of nonlinear recursive TRS. We consider the following recursion schemes where R g is a tail-recursive form of R f . Here the function g implements a loop.
⎧ ⎪ f (u , c i ) → ni i = l, ..., l ⎪ ⎪ ⎪ ⎨ f (u , t ( v )) → h(k(u , w ), f (u , r )) j j j j Rf ⎪ j = l + 1, ..., n ⎪ ⎪ ⎪ ⎩ l p → r p p = n + 1, ..., m ⎧ ⎪ g (u , c i , acc ) → mi i = l, ..., l ⎪ ⎪ ⎪ ⎨ g (u , t ( v ), acc ) → g (u , r , k (u , w , h(acc , w ))) j j j j j Rg ⎪ j = l + 1 , ..., n ⎪ ⎪ ⎪ ⎩ l p → r p p = n + 1, ..., m
where u is a finite set of variables eventually empty, i.e., may be ∅. The rules l p → r p , p = n + 1, .., m represents the definition of the helper function h. For simplicity, we consider the last argument of the function g as the accumulator parameter. We suppose that all the functions and variables occurring in R f and R g are pattern variables. When matching a schematic program against a concrete one, we will have to find the instantiations of the pattern variables. A function f is called linear recursive if the number of recursive calls in its body equals 1. Otherwise, f is called non-linear recursive function. We suppose TRSs verify the following assumptions: 1. r j t j ( v j ). 2. acc ∈ /
n
j =l+1
V(u ∪ { v j , w j , w j , r j }).
3. h is a monoid, i.e.,
H=
h(x, h( y , z)) = h(h(x, y ), z) h(x, e ) = h(e , x) = x, for some identity element e
4. For non-linear recursive programs, we add the two conditions:
t j (v j ) w j w j is an instance of r j
where the first condition is motivated by recursion termination and the second to circumvent the problem of non-deterministic substitutions, see Remark 1.
M. Demba / Information Processing Letters 129 (2018) 16–24
In order to distinguish between linear and non-linear recursive functions, we define pattern variables k and k as follows: Definition 1 (Meanings of k and k ).
• For linear recursive TRS:
Remark 1. In the program Fibo, we have the two possible substitutions for w 3 and r3 : { w 3 → s(x), r3 → x} or { w 3 → x, r3 → s(x)} while in the program tf the only possible substitution is { w 3 → s(x), r3 → x}. To circumvent this problem of non-determinism, we demanded in the condition 1.4 above that w j must be an instance of r j . Now, we can show if the two programs R f and R g are equivalent or not, i.e., given the same input, the two versions produce the same result, under H. To do so, in general one needs to check whether
k (u , v ) ≡ v k (u , v , w ) ≡ w
• For non-linear recursive TRS:
19
π ≡ g (u , v , e ) = f (u , v )
k≡ f k ≡ g
Example 1 (Linear recursive program). Let us reconsider our programs Sum and tailSum from section 1.1. According to Definition 1, k(u , v ) = v and k (u , v , w ) = w. Matching the concrete program Sum (resp. tailSum) against the schematic program R f (resp. R g ) result in the following second-order substitution:
ϕ = { f → Sum, g → tailSum, u → ∅, n1 → 0, t 2 ( v 2 ) → a : x, h → add, w 2 → a, r2 → x,
m1 → y , acc → y , h → add, w 2 → a} we have Sum = ϕ (R f ) and tailSum = ϕ (R g ). In this example, the TRSs are linear recursive, therefore k(u , w j ) = w j and k (u , w j , h(acc , w j )) = h(acc , w j ). Example 2 (Non-linear recursive program). Let us consider the following functions which are both intended to implement the Fibonacci function:
⎧ → s(0) ⎪ ⎨ f ibo(0) F ibo f ibo(s(0)) → s(0) ⎪ ⎩ f ibo(s2 (x)) → add( f ibo(x), f ibo(s(x))) and the non-linear recursive forms
⎧ → s( y ) ⎪ ⎨ t f (0, y ) t f t f (s(0), y ) → s( y ) ⎪ ⎩ t f (s2 (x), y ) → t f (x, t f (s(x), y )) According to Definition 1, k(u , v ) ≡ f (u , v ) and k (u , v , w ) ≡ g (u , v , w ). We have All those two versions fall in our program schema using the following second-order substitutions:
ϕ = { f → f ibo, g → t f , c1 → 0, n1 → s(0), c2 → s(0), n2 → s(0), m1 → s( y ), m2 → s( y ), t 3 ( v 3 ) → s2 (x), h → add, u → ∅, w 3 → s(x), w 3 → 0, k → f , k → g , r3 → x, acc → y } and we have F ibo = ϕ (R f ) and t f = ϕ (R g ).
(5)
where e represents the identity element of the helper function h. This conjecture (5), referred to us schematic correctness property, characterizes the validity of R g w.r.t. R f . It is easy to see that all the correctness properties πi , i = 1, ..., 16 in Table 1 are instances of the schematic correctness property (5). Definition 2. Let R f and R g be two program schemas and
π their correctness property. We say that R f and R g are equivalent under H (denoted R f H R g ) iff |=H π , i.e., π is an inductive theorem. However, an inductive proof of the correctness property (5) by induction on v will fail in the induction step. For example, suppose one attempts to prove it by structural induction on the variable v. On the step cases, one gets:
g (u , r j , k (u , w j , h(e , w j ))) = h(k(u , w j ), f (u , r j )) j = l + 1, ..., n
(6)
To complete the proof of (5), that is the induction hypothesis, one needs to complete the proof of the induction conclusion (6). Note that the left-hand sides of (5) and (6) are not unifiable, the induction hypothesis is too weak to match the conclusion, i.e., the following equations cannot be solved:
g (u , v , e ) = g (u , r j , h(e , w j )) f (u , v ) = h(k(u , w j ), f (u , r j ))
This kind of problems are very common when proving properties of functions with accumulator variables. Actually, an inductive proof of (5) requires to use a generalization lemma. However, if this lemma g (x, y , z) = h( z, f (x, y )) is given, the proof of (5) becomes straightforward. Even for simple examples, as those in Table 1, conjecturing a lemma is a difficult task for automatic theorem provers. In this table, φi is the required (generalization) lemma for completing the proof of πi . The definitions of function symbols are given in appendix. In general, the needed lemmas φi are provided explicitly by the user. A challenging question is how to generate them automatically? Several generalization heuristics have been proposed to generate such lemmas [16,12,5,8, 10]. However, such major heuristics require other usersupplied lemmas and lead, sometimes, to false conjectures (over-generalization). In the next section, we will present a different technique for automatic generation of lemmas.
20
M. Demba / Information Processing Letters 129 (2018) 16–24
Table 1 The discovered lemmas φi together with the monoid conditions in H are enough to complete the proof of the theorems πi . Correctness properties
Discovered generalization lemmas
π1 : tp (x, y , s(0)) = power (x, y ) π2 : t f a(x, s(0)) = f act (x) π3 : qrev (x, []) = rev (x) π4 : tm(x, y , 0) = mult (x, y ) π5 : taillen(x, 0) = len(x) π6 : ttally (x, 0) = tally (x) π7 : tdup (x, y , []) = duplicate(x, y ) π8 : tsub(x, y , []) = subList (x, y ) π9 : tzip (x, y , []) = zip (x, y ) π10 : qm(x, y , 0) = mult (x, y ) π11 : te(x, 0) = exp (x) π12 : tailSum(x, 0) = Sum(x) π13 : tr (x, y , []) = rem(x, y ) π14 : t f (x, 0) = f ibo(x) π15 : tt (tree, []) = traverse(tree) π16 : tailSumtr (tree, 0) = sumtr (tree)
A schematic (generalization) lemma represents a class of possible lemmas, which in turn are possible interpretations of the schematic lemma: variables in a schematic lemma are pattern variables. In this section, we show that a needed generalization lemma can be obtained directly from the specification, the program R f in our case. Our starting point is the recursive rules of the TRS R f that are of the following form:
f (u , t ( v )) = h(k(u , w ), f (u , r ))
φ ≡ ∀acc . g (u , r , acc ) = h(acc , f (u , r ))
(7)
This conjecture (7) is henceforth called schematic (generalization) lemma, and is denoted φ . It is easy to see that all the lemmas φi , i = 1, ..., 16 in Table 1 are instances of the schematic lemma φ . Note that the variable acc is universally quantified and π is an instance of φ where acc=e. Therefore, if |=H φ , the validity of π follows. Let us show in which conditions φ is an inductive theorem. By applying the structural induction rule on r we get:
φ8 : tsub(x, y , z) = app ( z, subList (x, y )) φ9 : tzip (x, y , z) = app ( z, zip (x, y )) φ10 : qm(x, y , z) = add( z, mult (x, y )) φ11 : te(x, y ) = add( y , exp (x)) φ12 : tailSum(x, y ) = add( y , Sum(x)) φ13 : tr (x, y , z) = app ( z, rem(x, y )) φ14 : t f (x, y ) = add( y , f ibo(x)) φ15 : tt (tree, y ) = app (traverse (tree), y ) φ16 : tailSumtr (tree, y ) = add(sumtr (tree), y )
= h(h(acc , k(u , w j )), f (u , r j ))
j = l + 1, .., n
(11)
Now we can apply the induction hypothesis (7) in (11) to obtain
g (u , r j , k (u , w j , h(acc , w j )) = g (u , r j , h(acc , k(u , w j ))) j = l + 1, .., n
(12)
Next we simplify both sides
j = l + 1, .., n (13)
Here we distinguish two scenarios using Definition 1:
• Linear recursion: the conjecture (13) is simplified to w j = w j for all j = l + 1, .., n. For example, in the Example 1, we have w 2 → a and w 2 → a. • Non-linear recursion: in (13), k is replaced with f and k is replaced with g, and we apply the induction hypothesis (7) to obtain
g (u , w j , h(acc , w j )) = g (u , w j , acc ) j = l + 1, ..., n,
(14)
which can be simplified to true, under H, if w j → e. For example, in our Example 2, we have w → 0.
(8)
j = l + 1, .., n
(9)
And this terminates the proof of (7), and we have the following lemmas:
(10)
Lemma 1 (Linear recursive programs). Let R f and R g be two linear recursive TRSs. |=H φ if the following conditions hold: (i) h(acc , ni ) = mi , i = 1, ..., l, and (ii) w j = w j , j = l + 1, ..., n.
3
g (u , r j , k (u , w j , h(acc , w j )))
(8) can be simplified using the base cases of f and g to
mi = h(acc , ni )
φ6 : ttally (x, y ) = add( y , tally (x)) φ7 : tdup (x, y , z) = app ( z, duplicate(x, y ))
i = 1, .., l
= h(acc , h(k(u , w j ), f (u , r j )))
φ4 : tm(x, y , z) = add(mutl(x, y ), z) φ5 : taillen(x, y ) = add(len(x), y )
k (u , w j , h(acc , w j )) = h(acc , k(u , w j ))
The key idea is to generalize the term k(u , w ) with a newly fresh (meta-) variable, say acc, and to introduce a function g satisfying the property
φ2 : t f a(x, y ) = mult ( y , f act (x)) φ3 : qrev (x, y ) = app (rev (x), y )
g (u , r j , k (u , w j , h(acc , w j )))
3.2. Critics for discovering schematic lemma
g (u , c i , acc ) = h(acc , f (u , c i ))
φ1 : tp (x, y , z) = mult ( z, power (x, y ))
i = 1, .., l
While (9) can be rewritten using H to
M. Demba / Information Processing Letters 129 (2018) 16–24
Lemma 2 (Non-linear recursive programs). Let R f and R g be two non-linear recursive TRSs. |=H φ if the following conditions hold (i) h(acc , ni ) = mi , i = 1, ..., l, and (ii) w j = e, j = l + 1, ..., n. In order to deal with concrete TRS, Chiba et al. [4] proposed a mechanism for matching automatically a program schema to a concrete TRS. We use the algorithm proposed in [4] but relax the condition 2 of their definition 2 p. 3 to allow variables to be instantiated with constructor terms. Definition 3. A mapping function has the following conditions: 1. 2.
ϕ : ∪ V → T ( ∪ F, V)
where dom (ϕ ) = { p ∈ |ϕ ( p ) = p (t 1 , ..., tn )} domV (ϕ ) = {x ∈ V|ϕ (x) = x}.
and
c 1 → []
r2 → x
⎤
⎢ ⎥ ⎢ g → tailSum(21 , 22 ) n1 → 0 t 2 ( v 2 ) → a : x ⎥ ⎢ ⎥ ⎥ ϕ=⎢ u→ ∅ w 2 → a ⎢ h → add(21 , 22 ) ⎥ ⎢ ⎥ acc → y m1 → y ⎣ k(21 , 22 ) → 22 ⎦ k (21 , 22 , 23 ) → 23
e → 0
w 2 → a
This mapping is legitimate since all the conditions of Lemma 1 are satisfied. Therefore, the (concrete) generalization lemma:
ϕ (φ) ≡ tailSum( v , z) = add(z, Sum( v )) and
ϕ (H) =
The next example illustrates an example of non-linear recursion. Example 4. Let us consider the Fibonacci functions in Example 2. To prove that tf Fibo are equivalent, we need to prove that
(15)
add(x, add( y , z)) = add(add(x, y ), z) add(x, 0) = add(0, x) = x
Note that (15) corresponds to the loop invariant (1) needed for the verification of the iterative program iterSum. Now to check whether (2) is valid or not, we can show that we have |=ϕ (H) ϕ (φ), and this can be done by checking the conditions of Lemma 1 without attempting any inductive proof: ϕ (h(acc , n1 )) = ϕ (m1 ) and ϕ ( w 2 ) = ϕ ( w 2 ). This is justified because semantic entailment (|=) is invariant w.r.t. instantiations, i.e. |=H φ implies |=ϕ (H) ϕ (φ). This also proves that the proposed method is sound. Remark 2. For any pattern variable p ∈ , the information about the order of its arguments is determined by the order of arguments of ϕ ( p ), i.e., we allow parameter permutation. Theorem 1. Let R f and R g be two program schemas defining two functions f and g, respectively. If |=H φ then R f H R g .
(16)
is an inductive theorem, which is an instance of (5). Again we can try to find out a mapping function ϕ such that F ibo = ϕ (R f ) and t f = ϕ (R g ):
⎡
Example 3. Let’s go back to our running example Sum. We would like to prove that the two TRSs Sum and tailSum are equivalent. A mapping function ϕ is then defined such that Sum = ϕ (R f ), tailSum = ϕ (R g ) and
f → Sum(21 )
Proof. We prove the result by contraposition. Suppose that |=H φ and R f H R g . Since |=H ∀u , ∀ v , ∀acc ( g (u , v , acc ) = h(acc , f (u , v ))), then we have |=H ∀u , ∀ v ( g (u , v , e ) = h(e , f (u , v ))) for an identity element e of h. As h is a monoid, we have |=H ∀u , ∀ v ( g (u , v , e ) = f (u , v )), i.e., |=H π . Therefore, by Definition 2, R f H R g . 2
t f (x, 0) = f ibo(x)
ϕ ( p ) ∈ T (C ∪ F) for any p ∈ dom (ϕ ), ϕ (x) ∈ T (C, V) for any x ∈ domV (ϕ )
⎡
21
f → Sum(21 )
⎢ g → tailSum(21 , 22 )
ϕ=⎢ ⎣
h → add(21 , 22 ) t 2 ( v 2 ) → a : x
c 1 → []
r2 → x
n1 → 0
e → 0
u → ∅
w 2 → a
acc → y
⎤ ⎥ ⎥ ⎦
w 2 → a m1 → y
and the corresponding generalization lemma is:
ϕ (φ) ≡ t f (x, y ) = add( y , f ibo(x))
(17)
It is easy to show that |= t f (x, y ) = add( y , f ibo (x)) as all conditions of Lemma 2 are satisfied. Therefore, the two versions are equivalent. Several others examples that can be proved using this method are presented in [7,10]. 4. Related works Aubin in [1] tried to improve the method in [2] by transforming a conjecture in order to obtain the same constant on both sides using properties of extra functions. Once the same constant value is obtained on both sides, he generalizes it with a new variable. For the reverse example, Aubin supposes the property s(0) = mult (s(0), y ) is given by the user. By contrast, in our method we only demand the helper function h to be a monoid. As the heuristic proposed in [2], the one proposed by Aubin can also lead to incorrect conjecture. Castaing [3] tried to facilitate the methods proposed in [2,1] using the technique of i-matching or induction-matching. He recognized that only easy theorems can be proved by the technique of imatching. In [12], the authors tried to automatically discover the needed properties of extra functions using helper functions. For the Sum example, they discover the property tailSum(x, 0) = T ( Sum(x), 0) and generalize the constant 0 in both sides by a fresh variable. Here T is an unknown helper function and user has then to speculate the instantiation T=add. However, this technique can also leads to unsound generalization if T is not commutative. In [5] the authors claimed that their system HipSpec can discover automatically the required lemmas. They recognize also that
22
M. Demba / Information Processing Letters 129 (2018) 16–24
a number of useless lemmas are discovered. In our case, no extra lemmas are discovered. Those heuristics are, in practice, technically complex and tricky to implement as they sometimes use speculations as in [12]. The inductive theorem prover CLAM [10] can prove the properties presented here only if other lemmas were given by the user. All the previous works are guided by proof failure: they start proving the correctness property π , once the proof get stuck, they try to generate the suitable generalization lemma φ . Once φ is generated, the user has to prove by induction that it is valid. The proof of φ may requires other unknown lemmas. By contrast, our method starts by discovering the generalization lemma φ directly from the given theory. There is no need to attempt an inductive proof of φ , it is sufficient to check whether the conditions presented in Lemma 1 or Lemma 2 are satisfied or not. Our method essentially rely on the properties of the monoid conditions of the extra function h. 5. Conclusion In this paper, we have proposed a schema-based approach which forms a powerful and flexible technique to program verification. As shown in section 3, we have successfully applied it to several interesting practical examples whose proofs are problematic for automatic theorem provers. We have shown that correctness of an important class of computer programs can be mechanically verified using the associativity of relations. Fortunately, most of the relations encountered in the literature have this property. Using our approach we are able to prove all the theorems in [11] table 8 about append, reverse and qrev on lists: all 19 theorems are proved easily using the lemma φ3 (see Table 1) and assuming the conditions in H. It’s worth noting that our method can be applied to binary trees, examples 15 and 16, and nested recursive functions, example 14. The proposed method has the following advantages. First, our method is applied prior to any proof attempt, in contrast to existing methods that are guided by proof failure (then time consuming). Second, the problem of proving the validity of a program is reduced to the problem of checking simple equalities, see Lemmas 1 and 2. Third, no useless lemmas are discovered. We are investigating the possibility to implement our method in a suitable SMT solver. Appendix A. Example of TRS 1. The power function:
power (x, 0) → s(0) power (x, s( y )) → mult (x, power (x, y )) tp (x, 0, z) → z tp (x, s( y ), z) → tp (x, y , mult ( z, x))
2. The factorial function:
f act (0) → s(0) f act (s(x)) → mult (s(x), f act (x)) t f a(0, y ) → y t f a(s(x), y ) → t f a(x, mult ( y , s(x)))
3. The reverse function:
rev ([]) → [] rev (a : x) → app (rev (x), [a])) qrev ([], y ) → y qrev (a : x, y ) → qrev (x, app ([a], y )
4. The times function:
mult (0, y ) → 0 mult (s(x), y ) → add(mult (x, y ), y ) tm(0, y , z) → z tm(s(x), y , z) → tm(x, y , add( z, y ))
5. The length function:
len([]) → 0 len(a : x) → add(len(x), s(0)) taillen([], y ) → y taillen(a : x, y ) → taillen(x, add(s(0), y ))
6. The tally function:
tally (0) → 0 tally (s(x)) → add(s(x), tally (x)) ttally (0, y ) → y ttally (s(x), y ) → ttally (x, add( y , s(x)))
7. The duplicate function:
duplicate (0, y ) → 0 duplicate (s(x), y ) → app ( y , duplicate (x, y )) tdup (0, y , z) → z tdup (s(x), y , z) → tdup (x, y , app ( z, y ))
8. The function subList(L,n) returns the nth first elements of a list L:
⎧ ⎪ ⎨ subList (x, 0) → [] subList ([], y ) → [] ⎪ ⎩ subList (a : x, s( y )) → app ([a], subList (x, y )) ⎧ ⎪ ⎨ tsub(x, 0, z) → z tsub([], y , z) → z ⎪ ⎩ tsub(a : x, s( y ), z) → tsub(x, y , app ( z, [a]))
M. Demba / Information Processing Letters 129 (2018) 16–24
9. The zip function interleaves the elements of two given lists:
⎧ ⎪ ⎨ zip (x, []) → [] zip ([], y ) → [] ⎪ ⎩ zip (a : x, b : y ) → app ([(a, b)], zip (x, y )) ⎧ ⎪ ⎨ tzip (x, [], z) → z tzip ([], y , z) → z ⎪ ⎩ tzip (a : x, b : y , z) → tzip (x, y , app ( z, [(a, b)]))
10. The multiplication functions mult and quick mult (qm):
mult (x, 0) → 0 mult (x, s( y )) → add(x, mult (x, y )) qm(x, 0, z) → z qm(x, s( y ), z) → qm(x, y , add( z, x))
11. The exponentiation function:
exp (0) → s(0) exp (s(x)) → add(exp (x), exp (x)) te (0, y ) → y te (s(x), y ) → te (x, te (x, y ))
12. Consider the function that returns the sum of the elements of a given list:
sum([])
→0
sum(a : x) → add(a, sum(x)) t Sum([], y )
→y
t Sum(a : x, y ) → t Sum(x, add( y , a)) 13. Programs that delete all occurrences of an element from a given list:
⎧ ⎪ ⎨ rem(a, []) → [] rem(a, a : l) → app ([], rem(a, l)) ⎪ ⎩ rem(a, b : l) → app ([b], rem(a, l)) ⎧ ⎪ ⎨ tr (a, [], y ) → y ⎪ ⎩
tr (a, a : l, y ) → tr (a, l, app ( y , []))
tr (a, b : l, y ) → tr (a, l, app ( y , [b]))
14. The Fibonacci function:
⎧ ⎪ ⎨ f ibo(0) f ibo(s(0)) ⎪ ⎩ f ibo(s2 (x)) ⎧ ⎪ ⎨ t f (0, y ) t f (s(0), y ) ⎪ ⎩ t f (s2 (x), y )
→ s(0) → s(0) → add( f ibo(x), f ibo(s(x))) → s( y ) → s( y ) → t f (x, t f (s(x), y ))
Note that, the function tf is not tail-recursive but nested recursive.
23
15. The following function traverse implements a binary tree transversal and returns a left-to-right list of its leaves using the constructors Leaf and Branch, e.g., traverse ( Branch( Branch( Leaf (1), Leaf (2)), Leaf (3))) returns the list [1, 2, 3] and the function tt is the tailrecursive form:
⎧ ⎪ ⎨ traverse ( Leaf (x)) → [x] traverse ( Branch(tree 1 , tree 2 )) ⎪ ⎩ → app (traverse (tree1 ), traverse (tree2 )) ⎧ ⎪ ⎨ tt ( Leaf (x), acc ) → x : acc ⎪ ⎩
tt ( Branch(tree 1 , tree 2 ), acc )
→ tt (tree1 , tt (tree2 , acc ))
and we have the property tt (tree , []) = traverse (tree ). 16. Summing the leaves of a binary tree, so that sumtr ( Branch( Branch( Leaf (1), Leaf (2)), Leaf (3))) returns 6.
⎧ ⎪ ⎨ sumtr ( Leaf (x)) → x sumtr ( Branch(tree 1 , tree 2 )) ⎪ ⎩ → add(sumtr (tree1 ), sumtr (tree2 ))
⎧ ⎪ ⎨ tailSumtr ( Leaf (x), y ) → add(x, y ) tailSumtr ( Branch(tree 1 , tree 2 ), y ) ⎪ ⎩ → tailSumtr (tree1 , tailSumtr (tree2 , y )) and we have sumtr (tree ).
the
property
tailSumtr (tree , 0) =
References [1] A. Aubin, Mechanizing structural induction II: strategies, Theor. Comput. Sci. 9 (1979) 347–361. [2] S.M. Boyer, J.S. Moore, A Computational Logic, Academic Press, 1979, xiv-397. [3] J. Castaing, How to facilitate the proof of theorems by using induction-matching and by generalization, in: 9th International Joint Conference on Artificial Intelligence, 1985, pp. 1208–1213. [4] Y. Chiba, T. Aoto, Y. Toyama, Program transformation by templates based on term rewriting, in: PPDP 2005, ACM Press, 2005, pp. 59–69. [5] K. Claessen, M. Johansson, D. Rosen, N. Smallbone, Automating inductive proofs using theory exploration, in: CADE’13, in: Lect. Notes Comput. Sci., vol. 7898, 2013, pp. 392–406. [6] A. Filinski, Recursion from iteration, Lisp Symb. Comput. Intern. J. 7 (1994) 11–38. [7] J. Giesl, Context-moving transformations for function verification, in: Proceedings of the Ninth International Workshop on Logic-based Program Synthesis and Transformation, LOPSTR’99, in: Lect. Notes Comput. Sci., vol. 1817, 2000, pp. 293–312. [8] J. Heras, E. Komendantskaya, M. Johansson, E. Maclean, Proof-pattern recognition and lemma discovery in ACL2, in: K. McMillan, A. Middeldorp, A. Voronkov (Eds.), LPAR-19, in: Lect. Notes Comput. Sci., vol. 8312, 2013, pp. 389–406. [9] D. Insa, J. Silva, Automatic transformation of iterative loops into recursive methods, Inf. Softw. Technol. 58 (2015) 95–109. [10] A. Ireland, A. Bundy, Automatic verification of functions with accumulating parameters, J. Funct. Program. 9 (1999) 225–245. [11] M. Johansson, L. Dixon, A. Bundy, Conjecture synthesis for inductive theories, J. Autom. Reason. 47 (3) (2011) 251–289. [12] D. Kapur, N.A. Sakhanenko, Automatic generation of generalization lemmas for proving properties of tail-recursive definitions, in: 16th International Conference TPHOLs, in: Lect. Notes Comput. Sci., vol. 2758, 2003, pp. 136–154.
24
M. Demba / Information Processing Letters 129 (2018) 16–24
[13] J. McCarthy, Recursive functions of symbolic expressions and their computations by machine, Commun. ACM 3 (1960) 184–195. [14] M.O. Myreen, M.J.C. Gordon, Transforming programs into recursive functions, Electron. Notes Theor. Comput. Sci. 240 (2009) 185–200, Elsevier. [15] Terese, Term Rewriting Systems, Cambridge University Press, 2003.
[16] S. Vadera, Generalisation for Induction, Technical Report, UMCS93-6-8, 1993. [17] Q. Yi, Transforming loops to recursion for multi-level memory hierarchies, in: Proc. of the 21st ACM-SIGPLAN Conference on Programming Language Design and Implementation, 2000, pp. 169–181.