H.x = { f.x if b.x
{ H.(g.x) otherwise (i.e., ¬b.x)
where b, f, and g are functions that can be defined without reference to H.
For some types S and T, the signatures of these functions are
Note that either or both of the types S and T may themselves be cartesian products of types (e.g., S = S1 × S2) so that the above definition (of tail recursive function) should be understood to apply not only to functions of one argument but also to multi-argument functions. If, for example, we had S = S1 × S2 and we preferred to view H ---as well as f, b, and g--- as being two-argument functions, we could write the definition of H as follows:
H.x1.x2 = { f.x1.x2 if b.x1.x2
{ H.(g1.x1.x2).(g2.x1.x2) otherwise (i.e., ¬b.x1.x2)
(Here we have that g1 and g2
are such that g.x1.x2 = (g1.x1.x2, g2.x1.x2).)
The generalization of this to S = S1 × S2 × ... × Sk (k > 2) should be obvious.
What makes these definitions "tail" recursive is that, in the recursive case, the result is simply an application of the function being defined (with no other operators having to be applied thereafter). Thus, when evaluating the function at a given value, the recursive "call" is the very last thing you do (i.e., the tail). Compare this to, say, the standard (non-tail) recursive definition of the factorial function:
Fact.n = { 1 if n=0
{ n × Fact.(n-1) otherwise (i.e., n>0)
In evaluating Fact.k (for any k>0), we would (recursively) evaluate Fact.(k-1) and then multiply the result by k.
Exploring the function H described above, we find that
H.x = H.(g.x) (assuming ¬b.x)
= H.(g.(g.x)) (assuming ¬b.(g.x))
= H.(g.(g.(g.x))) (assuming ¬b.(g.(g.x)))
= ... ...
= H.(g[k].x) (assuming ¬b.(g[j].x) for all j satisfying 0≤j<k)
= f.(g[k].x) (assuming b.(g[k].x))
where by g[k].x we mean g.(g.(....(g.x)....)),
where g occurs k times.
(Formally, we define g[0].x = x and, for j≥0,
g[j+1].x = g(g[j].x).)
In other words, assuming that there exists some i≥0 for which
b.(g[i].x) holds, we find that
where K = (min i | 0≤i ∧ b.(g[i].x) : i).
This suggests the following iterative program for establishing y = H.X.
|[con X : S;
con K : int; { K = (MIN j | 0≤j ∧ b.(g[j].X) : j) }
var x : S;
var k : int;
var y : T;
x,k := X,0;
{ loop invariant I : x = g[k].X ∧ 0≤k≤K }
{ bound function t : K - k }
do k ≠ K → x,k := g.x, k+1
od
{ assertion: x = g[k].X ∧ k=K; hence, H.X = f.x }
y := f.x;
{ y = H.X }
]|
The code above suffers from the fact that it relies upon "knowing" (magically, apparently) the value of K. From the loop invariant (and our definition of K) it follows that the loop guard is equivalent to ¬b.x. By using this as our guard, we remove the program's dependence upon K.
Further observation of the code reveals that, except insofar as the loop invariant refers to it, the variable k is useless. It is worth investigating, then, whether we can restate the invariant so as not to mention k. It turns out that, indeed, we can, by observing that, as x assumes the values
on successive iterations of the loop, the property H.x = H.X is preserved. Hence, we can state the loop invariant as I : H.x = H.X and we can omit the variable k from the program altogether. Let us prove that this I is, indeed, a loop invariant. To do so, it suffices to prove that the truth of I is established by the initialization code and that its truth is preserved by an arbitrary iteration of the loop. (These correspond to proof obligations (i) and (ii) in the loop checklist.) That I is established by the initialization is obvious (a proof of {true} x:=X {H.x = H.X} is left to the reader); that I is preserved by each loop iteration is proved by showing the Hoare triple
which is equivalent to
Here is the proof:
Assume I (i.e., H.x = H.X) and ¬b.x
wp.(x := g.x).I
= < wp assignment law >
I(x := g.x)
= < defn of I >
(H.x = H.X)(x := g.x)
= < textual sub >
H.(g.x) = H.X
= < assumption ¬b.x; by defn. of H, ¬b.x implies H.x = H.(g.x) >
H.x = H.X
= < assumption >
true
Unfortunately, the bound function still refers not only to k but also to K. Although it is not as elegant as the original, the bound function can be described as
In other words, t is the number of (additional) times that g must be applied to x in order for the result to satisfy b. Of course, whether or not such a number exists depends upon g, b, and x. In the typical case, x will be an integer, b will be true when x is sufficiently close to zero, and g.x will be a number closer to zero than is x.
The final version of the program is as follows:
|[con X : S;
var x : S;
var y : T;
x := X;
{ loop invariant I : H.x = H.X }
{ bound function t : (MIN i | 0<=i ∧ b.(g[i].x) : i) }
do ¬b.x → x := g.x
od
{ assertion: H.x = H.X ∧ b.x ; hence, H.X = f.x }
y := f.x;
{ y = H.X }
]|
As a concrete example of a tail recursive function definition, we offer this:
H.n = { n if 0≤n≤1
{ H.(n-2) otherwise (i.e., n>1)
It should not take you long to recognize that this function, when applied to a natural number n, yields zero if n is even and one if n is odd.
In general, however, "natural" examples of tail recursive function definitions are not often encountered.
G.x = { f.x if b.x
{ h.x ⊕ G.(g.x) otherwise (i.e., ¬b.x)
where ⊕ : T × T → T is any associative operator
having an identity element e and where b, f,
and g are functions as described before.
Note: The right-hand side of the recursive case in the definition of G could have been G.(g.x) ⊕ h.x (even if ⊕ were not commutative/symmetric). This would simply mean that, in what follows, the two operands in every subexpression of the form a ⊕ b should be swapped. End of note.
Examples of pseudo-tail recursive function definitions:
Fact.n = { 1 if n=0
{ n × Fact.(n-1) otherwise (i.e., n>0)
Digit_Sum.n = { 0 if n=0
{ (n % 10) + Digit_Sum.(n / 10) otherwise
Occurs_In.x.b.n = { false if n=0
{ (b.(n-1) = x) ∨ Occurs_In.x.b.(n-1) otherwise
We now show that, for any function that can be defined via a pseudo-tail
recursive definition, there exists a "more general" function that can be
defined via tail recursion.
Let G be the function defined via the pseudo-tail recursive definition template above. Define H as follows:
Consider the two cases b.x and ¬b.x:
Case b.x:
H.x.y
= < defn of H >
y ⊕ G.x
= < defn of G; assumption b.x >
y ⊕ f.x
Case ¬b.x:
H.x.y
= < defn of H >
y ⊕ G.x
= < defn of G; assumption ¬b.x >
y ⊕ (h.x ⊕ G.(g.x))
= < associativity of ⊕ >
(y ⊕ h.x) ⊕ G.(g.x)
= < defn of H >
H.(g.x).(y ⊕ h.x)
What this establishes is that we may characterize H as follows:
H.x.y = { y ⊕ f.x if b.x
{ H.(g.x).(y ⊕ h.x) otherwise (i.e., ¬b.x)
But this has the format of a tail recursive definition. Hence, H is
tail recursive!
Taken together with the fact that G.x = e ⊕ G.x = H.x.e (recall that e denotes the identity element of ⊕), we have that G is directly definable in terms of a tail recursive function. Hence, to establish z = G.X, it suffices to construct a program that establishes the equivalent z = H.X.e. Such a program would be as follows:
|[con X : S;
var x : S;
var y,z : T;
x,y := X,e;
{ loop invariant I : H.x.y = H.X.e }
{ alternative (but equivalent) loop invariant I : y ⊕ G.x = G.X }
do ¬b.x → x,y := g.x, y ⊕ h.x;
od
{ assertion: (H.x.y = H.X.e) ∧ b.x; hence H.X.e = y ⊕ f.x }
{ alternative: (y ⊕ G.x = G.X) ∧ b.x; hence y ⊕ f.x = G.X }
z := y ⊕ f.x;
{ z = H.X.e; hence, z = G.X }
{ alternative: z = G.X }
]|
Let us do this for a concrete example. Take the function Digit_Sum defined
above:
Digit_Sum.n = { 0 if n=0
{ (n % 10) + Digit_Sum.(n / 10) otherwise
Following the procedure suggested above, we define
In particular, we have Digit_Sum'.n.0 = Digit_Sum.n, from which we derive (in a manner analogous to our analysis of H above) that
Digit_Sum'.n.m = { m + 0 if n=0
{ Digit_Sum'.(n / 10).(m + (n % 10)) otherwise
(Of course, we can omit the "+ 0" in the base case.)
The program we derive from this is as follows:
|[con N : nat;
var n : nat;
var m : nat;
var y : nat;
n,m := N,0;
{ loop invariant I : Digit_Sum'.n.m = Digit_Sum'.N.0 }
{ alternative: m + Digit_Sum.n = Digit_Sum.N }
do n ≠ 0 → n,m := n/10, m + (n % 10);
od
{ assertion: Digit_Sum'.n.m = Digit_Sum'.N.0 ∧ n=0;
hence, Digit_Sum'.N.0 = m + 0 (= m) }
{ alternative: m + Digit_Sum.n = Digit_Sum.N ∧ n=0; hence, m+0 = Digit_Sum.N }
y := m + 0; // obviously, the "+ 0" can be omitted
{ y = Digit_Sum'.N.0; hence, y = Digit_Sum.N }
]|
H.x = { f0.x if b0.x
{ f1.x if b1.x
{ H.(g0.x) if c0.x
{ H.(g1.x) if c1.x
where [b0.x ∨ b1.x ∨ c0.x ∨
c1.x] (i.e., for every x, at least one of
b0, b1, c0,
or c1 holds).
This particular example has exactly two base cases and two recursive cases, but it can be easily generalized to any number of each. Strictly speaking, this does not qualify as a tail recursive definition. However, it can be shown that such a definition can be transformed into an equivalent one that is tail recursive. (Exactly how this is accomplished is beyond the scope of this document.) What is important to us is how to transform a function definition such as this into a program that computes the defined function. Well, here it is:
|[con X : S;
var x : S;
var y : T;
x := X;
{ loop invariant I : H.x = H.X }
do c0.x → x := g0.x
[] c1.x → x := g1.x
od
{ assertion: H.x = H.X ∧ (b0.x ∨ b1.x) }
if b0.x → {H.x = H.X ∧ b0.x} y := f0.x {y = H.X}
[] b1.x → {H.x = H.X ∧ b1.x} y := f1.x {y = H.X}
fi
{ y = H.X }
]|
Copyright Robert McCloskey 2004-2009