Loops Invariants, Correctness, and Program Derivation in Perl

There is also a Java version of this lecture.


A loop invariant is a relation among program variables that is true when control enters a loop, remains true each time the program executes the body of the loop, and is still true when control exits the loop. Understanding loop invariants can help us analyze programs, check for errors, and derive programs from specifications.

A first example

Here is a simple example. This Perl subroutine named sum is a function that returns the sum of the elements in array @_, its argument. It uses the local variable $s to accumulate the sum and uses $i to index the array elements.

sub sum {
    for ($i = 0; $i < @_; $i++) {
        # $s is the sum of the first $i array elements
        # $s == $_[0] + .. + $_[$i-1]
        $s = $s + $_[$i];
    }
    return $s;
}

The comments in the loop body describe the invariant. The first comment describes the invariant in English: $s is the sum of the first $i array elements. The second comment expresses the invariant in a form similar to a Perl Boolean expression: $s == $_[0] + .. + $_[$i-1]. This form emphasizes that the invariant is an assertion, an expression that should always evaluate to true (the two dots .. used here to indicate the other terms in the sum are not legal Perl syntax).

The invariant expresses a relation between the three variables $s, $i, and @_ that remains true even when the values of $s and $i change: $s is always the sum of the first $i elements in @_. When control enters the loop, $i is 0 so $s is the sum of no array elements. When control exits the loop, $i is the length of @_, so $s is the sum of all of the array elements. This is the intended result.

This simple example shows why thinking about loop invariants can help us write programs. When the program exits a loop, the loop invariant expresses the intended value(s) of the variable(s) at that point. Therefore we can work backwards from the intended values to the loop invariant, and then to the code itself. This can be a systematic method for writing programs which can supplement the usual method of relying on intuition and lots of trial-and-error. Programs derived in this way can be surprising and unobvious --- the kind that make you wonder, "How did they come up with that?"

Making it clearer

It is necessary that the loop invariant be true on each iteration before executing the body of the loop, and again after executing the body. In our example, the body of the loop is the statement $s = $s + $_[$i], followed by $i++ (because the third expression in the for statement is actually executed at the end of the body of the loop, each time the body is executed). The first time the program executes the loop, $i == 0 and $s == 0 before executing the body, and $i == 1 and $s == $_[0] after executing the body. Both situations satisfy the invariant.

The for loop makes it a bit difficult to see what's going on because the statement which is actually executed at the end of the body of the loop ($i++ in our example) doesn't appear there, it appears in the for statement itself. For the purposes of understanding and deriving loops, it is often clearer to express a for loop as the equivalent while loop.

Here is how to transform a for loop to the equivalent while loop. This for loop

   for (s; p; u) {
      t;
   }

is equivalent to this while loop

   s;
   while (p) {
      t;
      u;
   }

So our example code can be transformed to

sub sum2 {
    my $i = 0; my $s = 0;
    while ($i < @_) {
        # $s is the sum of the first $i array elements
        # $s == $_[0] + .. + $_[$i-1]
        $s = $s + $_[$i];
        $i = $i + 1;
    }
    return $s;
}

Understanding loops

We need a few more definitions.

The guard is the boolean expression that determines whether to execute the body of the loop. The program executes the body of the loop as long as the guard is true. In a while loop, the guard is the boolean expression that occurs in the while statement (in a for loop, the guard is the second expression in parentheses in the for statement). In this example, the guard is $i < @_.

The variant is a numeric expression that measures how much work is left to do. Usually the variant does not occur explicitly in the code, you must infer it. Quite often the variant is simply the number of loop iterations that remain to be done. In our example, the variant is @_ - $i (the number of elements following $i.

Now we have all the concepts we need to understand loops. A loop is correct if the five conditions in this checklist are all true:

  1. Initialization: the invariant is true before entering the loop
  2. Invariance: the body of the loop preserves the invariant
  3. Progress: the body of the loop decreases the variant.
  4. Boundedness: when the value of the variant falls below some threshold, the guard becomes false.
  5. Exit: the negation of the guard and the invariant describe the goal.

We can confirm all five conditions in our example:

  1. Initialization: $i == 0 && $s == 0 before entering the loop
  2. Invariance: executing the body of the loop gives $i == 1 && $s == $_[0], then $i == 2 && $s == $_[0] + $_[1], etc.
  3. Progress: when we execute the body of the loop, the statement $i = $i + 1 decreases the variant @_ - $i.
  4. Boundedness: when the value of the variant falls below 1, the guard $i < @_ becomes false.
  5. Exit: The negation of the guard $i == @_ and the invariant $s == $_[0] + .. + $_[@_-1] describe the goal.

Exercises

Before we go on, let's check our understanding of invariants. For each of these code samples, see if you can express the loop invariant in English and in symbols (as a Perl boolean expression, or as near to one as you can write).

This code computes the power xn (that is, x**n) by repeated multiplication:

	  
sub power{
    my $x = shift;  my $n = shift;
    my $p = 1; my $i = 0; 
    while ($i < $n) {
	$p = $p * $x;
	$i = $i + 1;
    }
    return $p;
}

The following code performs integer division by repeated subtraction. It divides numerator $n by divisor $d, returning the integer quotient $q and also calculating remainder $r. If the numerator is 4 and the divisor is 2, the quotient is 2; moreover, if the numerator is 5 and the divisor is 2, the quotient is 2 also. To understand this code, it is useful to understand this definition for integer division that relates relates the outputs $q and $r to the inputs $n and $d:   $r < $d && $n = $q*$d + $r

sub quotient {
    my $n = shift; my $d = shift;
    my $q = 0, my $r = $n;
    while ($r >= $d) { 
	$r = $r - $d;
	$q = $q + 1;
    }
    return $q;
}

These exercises are a little contrived. As we shall see, it is usually more effective to start with the loop invariant and derive the code from that.

Deriving loops

We can derive loops by considering the general pattern that relates the code in the loop to the assertions. Here I is the invariant, P is the guard, S is the code that precedes the loop and Tis the code in the body of the loop:

   S
   # I 
   while (P) {
      # I 
      T
      # I 
   }
   # !P && I 

This shows that the assertion !P && I will be true when code exits the loop. This is called the postcondition (because it is true after executing the loop).

Let's look again at integer division. Now we know enough to derive the code from the definition $r < $d && $n == $q*$d + $r. We want this definition to match the postcondition !P && I. We notice that !P in the postcondition matches r < d in the definition, and the invariant I matches $n == $q*$d + $r. We can derive the guard P by negating $r < $d to obtain $r >= $d. Filling in the assertions, we now have:

   S
   # $n == $q*$d + $r 
   while ($r >= $d) {
      # $n == $q*$d + $r 
      T
      # $n == $q*d + $r 
   }
   # $r < $d && $n == $q*$d + $r 

Now we have to derive the code fragments S and T. The basic idea is to count how many times we can subtract the divisor $d from the numerator $n. The quotient $q is the counter, and remainder $r is what remains after the subtraction.

Our first job is to write S, the startup code that initializes q and r. S has to establish the invariant I. It seems reasonable to start with my $r = $n; my $q = 0;. Substituting these values into I, we obtain $n == 0*$d + $n which is true. Checking S against I in this way gives us confidence that our code is correct.

Our next job is to write T, the body of the loop. T must preserve the invariant I. It seems reasonable to try $r = $r - $d; $q = $q + 1. So, for example, after the first time we execute the loop body we'll have $r == $n - $d && $q == 1 (why?). Plugging these values back into I we obtain $n == 1*$d + $n - $d which is true. And so on, each time we execute the body of the loop. Again, checking T against I shows that our code is correct. We have derived

    my $r = $n; my $q = 0;
    # $n == $q*$d + $r
    while ($r >= $d) {
        # $n == $q*$d + $r 
	$r = $r - $d;
	$q = $q + 1;
        # $n == $q*$d + $r 
    }
    # $r < $d && $n == $q*$d + $r
}

If you wish, you can use Perl coding tricks to express this in a more compressed form:

   for ($r = $n, $q = 0; $r >= $d; $q++) { $r -= $d; }

Exercises

Consider the code for xn (that is, x**n) in the previous exercise. Write the boolean expressions for the guard P and the invariant I. Annotate the code with assertions and confirm to your satisfaction that the assertions are correct.

A boolean expression that defines the integer square root $r of non-negative integer $n is $r*$r <= $n && $n < ($r+1)*($r+1). Derive a function sqrt that takes argument $n and returns its root $r (the easiest algorithm simply counts up to $r).

More advanced examples

Here is the binary search annotated with its loop invariant and some other assertions.
sub bsearch {
    my ($x, $a) = @_;            # search for x in array a
    my ($l, $u) = (0, @$a - 1);  # lower, upper end of search interval
    my $i;                       # index of probe
    while ($l <= $u) {
        # Loop invariant:
        # if $x is in @$a, index of $x must be in the range $l .. $u
	$i = int(($l + $u)/2);
        # now $i is also in the range $l .. $u, it might be index of $x
	print($i, "\n");
	if ($a->[$i] < $x) {
	    # Assertion:
            # index of $x must be in the range $i+1 .. $u
	    $l = $i+1; # make progress
	}
	elsif ($a->[$i] > $x) {
	    # Assertion:
            # index of $x must be in the range $l .. $i-1
	    $u = $i-1; # make progress
	} 
	else {
	    # Assertion:	
	    # index of $x is $i
	    return $i; # found
	}
    }
    # if $x is in @$a, it must be in the range $l .. $u, but $l > $u
    # so $x must not be in @$a at all
    return -1;         # not found
}
Here is the maximum section sum annotated with its invariant.
sub bentley4 {                           # @_ is Bentley's array X
    my ($maxsofar, $maxendinghere);
    for ($i = 0; $i < @_; $i++) {
        # Loop invariant:
        # maxsofar is max. section sum anywhere in array up to $i
        # maxendinghere is max. section sum in array, where section ends at $i
	$maxendinghere = max(0.0, $maxendinghere + $_[$i]);
	$maxsofar = max($maxsofar, $maxendinghere);
    }
    return $maxsofar;
}

References

Bentley derives binary search using invariants and assertions in Writing Correct Programs, Communications of the ACM 26(12) 1040 - 1045, Dec. 1983, also in Programming Pearls, Addison-Wesley, Chapter 4 (both editions, 1986 and 2000). Here are Bentley's notes (PS, PDF).

My favorite book on program derivation is Programming in the 1990s: An Introduction to the Calculation of Programs by Edward Cohen, Springer 1990. Cohen derives the maximum section sum in section 10.5. There are other good books by Dijkstra, Gries, and Kaldewaij.


Jon Jacky, jon@u.washington.edu