0% found this document useful (0 votes)
0 views208 pages

Studies in Automatic Programming Logic

The document is a collection of studies focused on automatic programming logic, edited by Nils J. Nilsson. It includes contributions from Zohar Manna, Richard Waldinger, Shmuel Katz, and Karl Levitt, discussing topics such as program verification, debugging, and synthesis. The preface outlines the evolution of programming tools and the need for intelligent systems to assist in program development and error detection.

Uploaded by

Jon Slow
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
0 views208 pages

Studies in Automatic Programming Logic

The document is a collection of studies focused on automatic programming logic, edited by Nils J. Nilsson. It includes contributions from Zohar Manna, Richard Waldinger, Shmuel Katz, and Karl Levitt, discussing topics such as program verification, debugging, and synthesis. The preface outlines the evolution of programming tools and the need for intelligent systems to assist in program development and error detection.

Uploaded by

Jon Slow
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 208

ARTIFICIAL INTELLIGENCE SE

NILS J. NILSSON, Editor

Studies in Automatic
Programming Logic
Zohar Manna
Richard Waldinger

with contributions by
Shmuel Katz and Karl Levitt

i COMPUTER SCIENCE LIBRARY


NUNC COCNOSCO EX PARTE

THOMAS J. BATA LIBRARY


TRENT UNIVERSITY
Digitized by the Internet Archive
in 2019 with funding from
Kahle/Austin Foundation

https://archive.org/details/studiesinautomatOOOOmann
Studies in Automatic
Programming Logic

V_ )
THE COMPUTER SCIENCE LIBRARY
Artificial Intelligence Series
NILS. J. NILSSON, Editor

Sussman A Computer Model of Skill Acquisition

Shortliffe Computer-Based Medical Consultations: MYCIN

Sacerdoti A Structure for Plans and Behavior

Manna and Studies in Automatic Programming Logic


Waldinger
Studies in Automatic
Programming Logic
Zohar Manna
Richard Waldinger

with contributions by
Shmuel Katz and Karl Levitt

NORTH-HOLLANDNEW YORK
NEW YORK • AMSTERDAM • OXFORD
Q

Elsevier North-Holland, Inc.


52 Vanderbilt Avenue, New York, New York 10017

North-Holland Publishing Company


P.O. Box 211
Amsterdam, The Netherlands

© 1977 by Elsevier North-Holland, Inc.

Library of Congress Cataloging in Publication Data

Manna, Zohar.
Studies in automatic programming logic.
(Artificial intelligence series) (The
computer science library)
Bibliography: p.
Includes index.
1. Electronic digital computers—Programming.
2. Computer programs. I. Waldinger, Richard,
joint author. II. Title.
QA76.6.M357 001.6’42 77-5765
ISBN 0-444-00224-3
ISBN 0-444-00225-1 pbk.

Manufactured in the United States of America


Contents

Preface vii
Chapter 1 Reasoning about Programs 1
R. Waidinger and K. Levitt
1. Introduction and Background 1
2. The Method 3
3. The Language 11
4. Examples 20
5. Conclusion 35
6. Appendix: Annotated Listing of the Deductive
System 37
7. Appendix: Traces of Solutions 69

Chapter 2 Logical Analysis of Programs 93


S. Katz and Z. Manna
1. Introduction 93
2. Preliminaries 94
3. Generation of Invariants: Algorithmic Approach 101
4. Generation of Invariants: Heuristic Approach 109
5. Correctness and Incorrectness 119
6. Automatic Debugging 127
7. Conclusion 138

Chapter 3 Knowledge and Reasoning in Program Synthesis 141


Z. Manna and R. Waldinger
1. Introduction 141
2. Fundamental Reasoning 142

v
Sa57197
VI CONTENTS

3. Program Synthesis: The Pattern Matcher 157


4. Program Modification: The Unification Algorithm 171
5. Discussion 175

Postscript 179
References 181
Index 186
Preface

As computers have developed, they have come to play a larger and


larger role in the programming process. Although twenty-five years
ago programmers had to express their programs as sequences of
numbers, and to interpret similarly encrypted error messages, today
they are assisted by a battalion of assemblers, compilers, editors,
interactive debuggers, and other tools by which the machine aids in
its own programming. The next stage in the progression is likely to
be the appearance of systems that can understand the subject matter
of the program being constructed, and thus play a more critical and
active role in its development. A compiler, for instance, can detect a
syntactic error such as a missing plus sign, but it cannot hope to
recognize a logical error, such as when a plus sign has been replaced
by a minus. To detect such a mistake, a system must understand the
program more thoroughly than the compiler does. In recent years,
experimental systems with this level of understanding have begun to
appear.
The three papers in this collection illustrate how intelligent sys¬
tems can be applied to the verification, debugging, and synthesis
of computer programs:

Reasoning About Programs (by R. Waldinger and K. Levitt)


describes a computer system to prove the correctness of a given
program.
Logical Analysis of Programs (by S. Katz and Z. Manna) presents
techniques for the automatic documentation of given programs
to assist in their debugging or verification.
Knowledge and Reasoning in Program Synthesis (by Z. Manna and
R. Waldinger) suggests methods for the automatic development of
computer programs.

vii
viii PREFACE

This collection treats, a progression of tasks of increasing com¬


plexity. In the first paper, we attempt to show that a given program
is consistent with given specifications and documentation, in the
form of “invariant assertions.” In the second paper, we begin with
only the program and its specifications, and we introduce means to
generate the documentation of the program and to correct the
program if the specifications are not met. In the final paper, we
assume that only the specifications are given, and we propose tech¬
niques for developing a program guaranteed to meet the specifications.
Progress in the implementation of these techniques reflects the
increasing complexity of their corresponding tasks and the ingenuity
they require. Program verification has been studied intensely, and our
first paper actually describes a running verification system. (The
description of the system is exceptionally complete, containing as an
appendix a full annotated listing of the theorem prover and traces
of sample solutions. Thus, a reader who is interested in actually
implementing a verification system can see exactly how it was done.)
On the other hand, debugging and synthesis are not yet so well
understood, and the methods in our second and third papers have
only been partially implemented.
The three papers have appeared earlier in journals, but are pre¬
sented here in a corrected, updated, and slightly modified form.
“Reasoning about programs” appeared in the journal Artifical
Intelligence (Vol. 5, No. 3, Fall 1974, pp. 235-316), “Logical analy¬
sis of Programs” in the Communications of the ACM (Vol. 19, No. 4,
Apr. 1976, pp. 188-206), and “Knowledge and reasoning in program
synthesis” in Artifical Intelligence (Vol. 6, No. 2, Summer 1975, pp.
175-208). We are indebted to these journals for permission to re¬
print the papers in this collection. In the subsequent postscript we
summarize more recent developments briefly. The references have
been combined into a single bibliography at the end.

Z. M. and R. W.
Chapter 1
Reasoning About Programs
Richard Waldinger and Karl Levitt

Problems worthy
of attack
Prove their worth
by hitting back.
Piet Hein

1. Introduction and Background

This paper describes a computer system that proves theorems


about programs, a task of practical importance because it helps cer¬
tify that the programs are correct. Instead of testing a program on
test cases, which may allow some bugs to remain, we can try to prove
mathematically that it behaves as we expect.
Many programs have done this sort of reasoning. King’s (1969)
program verifier proved an interesting class of theorems and was very
fast. Deutsch’s (1973) system is perhaps not as fast as King’s, but it
can prove more interesting theorems. Igarashi et al. (1975) applied a
resolution theorem prover to the verification of programs written in
PASCAL, such as Hoare’s (1961) FIND. Their system does little
actual resolution but a lot of simplification and reasoning about
equality. A program devised by Boyer and Moore (1975) can prove
difficult theorems about LISP programs.
Thus there is no shortage of interesting work related to our
own. The special characteristic of our system is that it is markedly

This is a revised version of a previously published article by the same name which ap¬
peared in Artificial Intelligence, vol 5, pp. 235-316. Copyright 1974 by North-Holland
Publishing Company, Amsterdam. Reprinted here by permission of publisher.
2 REASONING ABOUT PROGRAMS

concise, readable, and easy to change and apply to new subject


areas.
Our program verifier consists of a theorem prover (or deductive
system) and a verification condition generator. The verification
condition generator takes an annotated program as input and con¬
structs a list of theorems as output. The truth of the constructed
theorems implies the correctness of the program. The task of the
deductive system is to prove these theorems. The verification con¬
dition generator (Elspas et al., 1973) is written in INTERLISP
(Teitelman, 1975), and the deductive system is written in QA4
(Rulifson et al, 1972). This paper focuses on the deductive system
but, to be complete, gives examples of verification condition
generation as well.
In writing our deductive system, we were motivated by several
goals. First, the system should be able to find proofs; it should have
enough deductive power to prove, within a comfortable time and
space, the theorems being considered. Also, these proofs should be
at the level of an informal demonstration in a mathematical text¬
book. This means that the difficulty in following one line to the
next in any proof should be small enough that the proof is under¬
standable, yet large enough not to be trivial. In any practical program
verifier, the user will wish to follow the steps in a deduction. Fur¬
thermore, the strategies the system uses in searching for a proof
should be strategies that we find natural. Not only should the tactics
that eventually lead to the proof be ones we might use in proving
the statement by hand, but also the false starts the system makes
should be ones we might make ourselves. We do not want the system
to rely on blind search; the trace of an attempted solution should
make interesting reading.

In addition to the requirement that proofs be readable, the rules


the system uses in going from one line to the next should be easy
to read and understand. We should be able to look at a rule and see
what it does. Also, it should be easy to change old rules and to add
new rules. The user of a program verifier is likely to introduce new
concepts, such as operators or data structures. We want to be able
to tell the deductive system how these structures behave and to have
the system reason effectively using the new symbols. Giving the
system new information should be possible without knowing how
The Method 3

the system works, and certainly without reprogramming it. Further¬


more, the addition of new information should not degrade the per¬
formance of the system prohibitively.
The system is intended to evolve with use. As we apply the system
to new problems, we are forced to give it new information and, per¬
haps, to generalize some old information. These changes are incor¬
porated into the system, which may then be better able to solve
new problems.
Since the system is easy to extend and generalize, we do not
worry about the completeness or generality of any particular version
of the system. It is powerful enough to solve the sort of problem on
which it has been trained, and it can be easily changed when
necessary.
These considerations played a part in the design of the program¬
ming system called QA4, as well as the construction of our deductive
system, which is written in the QA4 language. Some of the tech¬
niques described below are embedded in the QA4 system itself;
others are expressed as parts of the deductive system.

2. The Method

Perhaps not all readers are familiar with the method of proving
statements about programs that we have followed in our work. Our
method is a natural technique introduced independently by Floyd
(1967) and Naur (1966) and formalized by Hoare (1969). Knuth
(1968, pp 17-18) traces the germ of the idea back to von Neumann
and Goldstine (1963). An other presentation of the same idea
appears in a lecture by Turing (1950). Although we cannot give a
thorough introduction to the technique here, we provide below an
example of its application to convey the flavor of the approach.

2.1. A Straight-Line Program

Consider a simple program, written in a flowchart language, that


exchanges the values of two variables, as shown in Figure 1.1. We
assume that before the program is executed, X and Y have some
initial values X0 and Y0. Suppose we want to prove that after the
program is executed, X = Y0 and Y = X0. Then we offer the input
and output assertions as comments in our program shown in Figure
4 REASONING ABOUT PROGRAMS

Figure 1.1. Figure 1.2.

1.2. These assertions are not to be executed by the program, but


they tell us something about the way the programmer expects his
program to behave. He expects the assertion at A to be true when
control passes through A, and the assertion at D to be true when
control passes through D.
The essence of the approach is to generate from a commented
program like the one above a set of statements called the verifica¬
tion conditions. If these statements and the input assertion are true,
then the other assertions the programmer has put in his program are
correct. Whereas the programmer’s assertions are intended to hold
only when control passes through the appropriate point, the verifica¬
tion conditions should be true in general, and they can be proved
by a deductive system that knows nothing about sequential proces¬
sing, loops, recursion, or other concepts about the flow of control
of the particular program.
To generate the verification condition for our sample program, we
pass the output assertion back toward the input assertion. As we pass
it back, we change it to reflect the changing state of the system. In
particular, if any assignments are made within the program, then the
corresponding substitution should be made in the assertion. Passing
the assignment at D back to point C changes it to X = Y0 and T =
X0, as shown in Figure 1.3. We can argue that if the assertion at C
The Method 5

Figure 1.3.

is true when control passes through C, then the assertion at D will


be true when control passes through D. In particular, if T = X0 is
true at C, and we execute Y T, then Y = X0 will be true at D.
Passing the assertion all the way back to A in this manner gives
the assertion Y = Y0 AX=X0.If this assertion is true at A, then the
final assertion will be true at D. However, we are already given the
initial assertion X = X0 A Y = Y0. The truth of the assertion at D
then depends on the truth of the obvious implication X = X0 A Y =
Y0 D Y = Y0 A X = X0. This statement is the verification condition
for this program. It can be proved by a deductive system indepen¬
dently of any knowledge about this program.
Constructing verification conditions by this method is an algo¬
rithmic process, not a heuristic one. On the other hand, there can be
no cut and dried algorithm for proving such verification conditions.
However, the somewhat restricted domain of program verification is
more tractable than the general theorem-proving problem.

2.2. A Loop Program

Before we explain how the system is structured or implemented,


let us first look at some examples of how verification conditions are
generated and proved by our system. This example will give a better
6 REASONING ABOUT PROGRAMS

idea of the subject domain of the inference system and of the sort
of reasoning it has to do.
Suppose we are given the annotated program shown in Figure 1.4
to compute the largest element in an array and its location. This
program searches through the array, keeping track of the largest
element it has seen so far and the location of this element. The
intermediate assertion1 at C says that MAX is the largest element
in the array between 0 and I and that LOC is the index for MAX.
Although our assertion language does not permit the ellipsis notation
we have introduced some suitable analogues, which are
discussed later.
To prove assertions about a complex program, the system decom¬
poses it into simple paths. This program can be decomposed into
four simple paths:

(1) The path from B to C.


(2) The path from C to D.
(3) The path from C around the loop and back to C through
point E.
(4) The path from C around the loop and back to C through
point F.

Notice that the author of this program has put assertions not only
at the START and HALT nodes of the program, but also at the
intermediate point C. He has done this so that the proof of the pro¬
gram can be reduced to proving straight-line paths in the same way
that the simple program of the previous section was verified. For
instance, the path that begins at C, travels around the loop through
E, and returns to C can be regarded as a simple, straight-line program
with the assertion at C as both its start assertion and its halt asser¬
tion. The assertion at C has been cleverly chosen to be true when the
loop is entered and to remain true whenever control travels around
the loop and returns to C, and to allow the assertion at D to be
proved when control leaves the loop and the program halts. The
choice of suitable internal assertions can be an intellectually exacting

1 In this program, and in examples throughout the paper, when we list several statements
in an assertion, we mean the implicit conjunction of those statements. We will often also
refer to each conjunct as an “assertion”.
The Method 7

Figure 1.4.

task; some heuristic methods have been proposed that will work in
this and many other examples (Elspas, 1974; Wegbreit, 1974;
German and Wegbreit, 1975; Katz and Manna, 1976).
If all the straight-line paths of the program are shown to be cor¬
rectly described by the given assertions, and if the program can be
shown to terminate (this must be done separately), then we can
conclude that the program is indeed correct, at least with respect
to the programmer’s final assertion.
Although there are many paths in the decomposition of a pro¬
gram, typically most of the paths are easy to verify. For this
program, we examine two of the paths.

2.2.1. Verifying One Path


First, suppose we want to demonstrate that if the assertion at
point C is true when control passes through C, then the assertion at
8 REASONING ABOUT PROGRAMS

C will still be true if control passes around the loop and returns again
to C. We will restrict our attention to the more interesting case, in
which the test MAX < A[I] ? is true; in this case, control passes
through E. Furthermore, we will try to prove only that the second
conjunct of the assertion at C remains true. Our verification
condition generator gives us the following statement to prove:

MAX = A[LOC] A (1.1)

A[0] < MAX, ..., A [ I ] < MAX A (1.2)

0 < LOC < I < N A (1.3)

n (N < I + 1) A (1.4)

MAX < A [ I + 1] D (1.5)

A [ 0] < A[I + 1], .... A[I+ 1] < A[I+ 1]. (1.6)


This statement is actually represented as five separate hypotheses
and a goal to be deduced from these hypotheses. Lines (1.1) through
(1.3) come from the assertion at C, and lines (1.4) and (1.5) come
from the tests along the path. Line (1.6) comes from the assertion
at C again. How the above statement is derived from the program is
shown in detail below.
The behavior of the deductive system in this problem is typical
of its approach to many problems. The goal (1.6) is broken into
two subgoals:
A[0] < A[I + 1 ] A - A A[I] < A[I + 1 ] (1.7)
and
A[I + 1 ] < A[I + 1 ]. (1.8)

The second subgoal, (1.8), is immediately seen to be true. The


first subgoal, (1.7), is easily derived from (1.2) and (1.5).

2.2.2. Generating a Verification Condition


For those readers unfamiliar with the Floyd method of producing
verification conditions, we give an example of its application: a com¬
plete trace of how the above verification condition was produced.
The path under consideration begins at point C, travels around the
loop through point E, and returns again to C. We will try to prove
the second conjunct at C.
The Method 9

This statement is

A[0] < MAX, A[ 1 ] < MAX, A[I]<MAX. (1.9)

We pass this assertion backward around the loop to point E, making


the corresponding substitution. The transformed assertion is then

A [ 0] < A[I], A[1]<A[I], ..., A [ I ] < A[I]. (1.10)

Since LOC does not appear explicitly in (1.10), the assignment


LOG I has no effect.
To reach point E, the test

MAX < A [ I ] ? (1.11)

must have been true. Passing the assertion back before the test gives
the implication
MAX < A [ I ] D A[0] < A[I], A[l] <A[I], ..., A[I] < A[I].

(E12)
If this implication is true before the test (1.11), the assertion (1.10)
will be true after the test. To travel around the loop at all, the result
of the test

N < I? (1.13)
must have been false. Passing the assertion (1.12) back over the test
(1.13) gives
n (N < I) A MAX < A [ I ]

D A[0] <A[I],A[1] < A[I], ..., A [ I ] <A[I], (1.14)

Passing (1.14) back over the assignment statement

I^I+l (1.15)

gives

-i (N < I + 1) AMAX < A[I + 1]

D A[0] <A[I + 1 ], A [ 1) <A[I + 1 ],..., A [I + 1] <A[I+ 1].

(1.16)

This statement has been generated in such a way that if it is true


when control passes through point C, then (1.9) will be true if
10 REASONING ABOUT PROGRAMS

control passes around the loop through point E and returns to C. If


we consider this path as a straight-line program with the assertion at
C as both its start assertion and its halt assertion, then proving the
correctness of the second conjunct (1.9) at C reduces to proving
MAX = A[LOC] A

A[0] < MAX, A[I] < MAX A

0 < LOC < I < N A

i (N < I + 1) A

MAX < A[I + 1 ] 3

A[0j <A[I+ 1], ..., A[I+ 1] < A[I + 1 ].


Finally, the antecedents of this implication are expressed as separate
hypotheses, and the consequent is represented as a goal. This is
exactly the condition that was proved in the previous section.

2.2.3. Verifying Another Path


Now let us look more briefly at the path from C to D. We will
assume the assertion at C is true and will prove the assertion at D.
We will look at the first conjunct of the assertion at D. Our verifica¬
tion condition generator gives us the following statement to prove:
MAX = A [LOC] A (1-17)

A[0] < MAX A ••• A A[I] < MAX A (1.18)

0 < LOC < I < N A (1.19)


N<I+ 1 3 (1.20)
A[0] <MAX A ••• A A[N] <MAX. (1.21)
The reasoning required for this proof is a little more subtle than the
previous deduction. When the system learns that N < 1+1 (1.20), it
immediately concludes that N + 1 < I + 1, since N and I are integers.
It further deduces that N < I. Since it already knows that I < N
(1.19), it concludes that N = I. Using the hypothesis (1.18), the
system reduces the goal (1.21) to proving that I = N, which it now
knows.
This deduction involves a lot of reasoning forward from assump¬
tions, while the preceding deduction required reasoning backward
The Language 11

from goals. Both of these proofs are typical of the behavior of the
system at large in their use of the properties of equality and the
ordering relations.
In reading the QA4 listing of the theorem prover (Section 6),
one is struck by the absence of any general deductive mechanisms
outside of the language processor itself. The QA4 system incorpor¬
ates enough of the common techniques of theorem proving and
problem solving that our inference system needs no general problem¬
solving knowledge, but only some knowledge about numbers, arrays,
and other structures. The following sections show how the QA4
language allows that knowledge to be represented.

3. The Language

3.1. Pattern Matching and the Goal Mechanism

The deductive system is made up of many rules expressed as small


functions or programs. Each of these programs knows one fact and
the use for that fact. The QA4 programming language is designed so
that all these programs can be coordinated; when a problem is
presented to the system, the functions that are relevant to the
problem “stand forward” in the sense explained below.
A program has the form

(LAMBDA (pattern) (body)).

Part of the knowledge of what the program can be used for is


expressed in the pattern. When a function is applied to an argument,
the pattern is matched against that argument. If the argument turns
out to be an instance of the pattern, the match is said to be success¬
ful. The unbound variables in the pattern are then bound to the
appropriate subexpressions of the argument, and the body of the
program is evaluated with respect to those new bindings.
For example, the program

REVTUP = (LAMBDA (TUPLE <^X +-Y) (TUPLE $Y $X))

has pattern (TUPLE <~X «-Y) and body (TUPLE $Y $X). The symbol
“TUPLE” is treated as a constant, and can only be matched against
itself. The prefix means that the variable is to be given a new
12 REASONING ABOUT PROGRAMS

binding. The prefix “$” means that the variable’s old binding is to be
used. When REVTUP is applied to (TUPLE A B), the pattern
(TUPLE <-X <-Y) is matched against (TUPLE A B). The match is
seen to be successful, the variable X is bound to A and the variable Y
is bound to B. The body (TUPLE $Y $X) is evaluated with respect to
these bindings, giving (TUPLE B A).
On the other hand, if a function is applied to an argument and the
pattern of that function does not match the argument, a condition
known as failure occurs. At many points in the execution of a pro¬
gram, the system makes an arbitrary choice between alternatives.
Failure initiates a backing up to the most recent choice and the
selection of another alternative, if one exists. The mismatching of
patterns is only one of the ways in which failure can occur in a
program.
We have yet to explain how a program stands forward when it is
relevant. In the above example, the function was called by name,
much as it is in a conventional programming language. But it is
also possible to make an argument available to any applicable pro¬
gram in a specified class. This is done by means of the goal mecha¬
nism.
When we say (GOAL (goalclass) (argument)), where the goal class
is a tuple of names of functions, we first check to see whether the
argument is known to be true, in which case the statement succeeds.
Otherwise that argument becomes available to the entire goal class.
The pattern of each of those functions is matched in turn against the
argument. If the match is successful, the function is applied to that
argument. If the function returns a value, that value is returned as
the value of the goal statement. On the other hand, if the match of
the pattern fails, or if a failure occurs in evaluating the function,
backtracking takes place, the next function in the goal class is tried,
and the process is repeated. If none of the functions in the goal class
succeed, the entire goal statement fails.
For example, in our deductive system, one of the goal classes is
called EQRULES, the rules used for proving equalities. One of these
rules is

EQTIMESDIVIDE =
(LAMBDA (EQ ^W (TIMES (DIVIDE ^X «-Y) «-Z))
(GOAL SEQRULES
(EQ (TIMES $Y $W)(TIMES $X $Z)»).
The Language 13

This rule states that to prove W = (X/Y)*Z, we should try to prove


Y* W = X*Z. It is assumed (for simplicity) that Y will not be 0. (The
actual EQTIMESDIVIDE, shown in Section 6, is more general than
this.) The rule has the pattern

(EQ +-W (TIMES (DIVIDE <-X <-Y) <-Z)).

If we execute (GOAL SEQRULES (EQ A (TIMES (DIVIDE B C)


D))) [i.e., we want to prove A = (B/C)*D], the system will try all the
applicable EQRULES in turn. If none of the previous rules succeed,
the system will eventually reach EQTIMESDIVIDE. It will find that
the pattern of EQTIMESDIVIDE matches this argument, binding W
to A, X to B, Y to C, and Z to D. Then it will evaluate the body of
this function; i.e., it will try

(GOAL SEQRULES (EQ (TIMES A C) (TIMES B D))).

If it succeeds at proving (EQ (TIMES A C) (TIMES B D)), it will


return normally. If it fails, it will try to apply the remaining EQRULES
to the original argument, (EQ A (TIMES (DIVIDE B C) D)). The
goal statement is an example of the pattern-directed function invoca¬
tion introduced by Hewitt (1971) in PLANNER.
The net effect of this mechanism is that it enables the user to
write his programs in terms of what he wants done, without needing
to specify how he wants to do it. Furthermore, at any point, he can
add new rules to EQRULES or any other goal class, thus increasing
the power of the system with little effort.

3.2. Some Sample Rules

The deductive system is a collection of rules represented as small


programs. One rule was given in the preceding section; two more
rules are presented here. The complete deductive system is included
in Section 6.
The first rule, EQSIMP, attempts to prove an equality by simpli¬
fying its arguments:
EQSIMP = (LAMBDA (EQ ^X <-Y)
(PROG (DECLARE)
(SETQ <-X (SSIMPONE $X))
(GOAL SEQRULES (EQ $X $Y)))
BACKTRACK).
14 REASONING ABOUT PROGRAMS

This rule says: to prove an equality, try to simplify one side of


the equality. The rule, a member of EQRULES, has the pattern
(EQ •*—X «-Y). It is applicable to a goal of form (EQ A B), where A
and B are any expressions. When EQSIMP is applied to such a goal, X
will bound to A and Y to B. SIMPONE, the simplifier, will simplify
A. The goal statement tries to prove that the simplified A is equal to
B. If it succeeds, the rule will return in the ordinary way. If the goal
statement fails, or if the simplifier fails to simplify A, the entire
application of the rule will fail.
The predicate EQ implicitly takes a set as its argument (see Sec¬
tion 3.4). Thus, there is an alternative match of (EQ +-X <-Y) to
(EQ A B), binding X to B and Y to A. The user has specified the
BACKTRACK option, meaning that he wants to try all possible
matches. Therefore, if the first application of EQSIMP fails, the
system will apply it the other way and try to simplify B. Only if
this second attempt fails will the entire rule fail, allowing other
members of EQRULES to work on the same goal.
The second rule is
FSUBTRACTI = (LAMBDA (<~F (SUBTRACT ^X <-Y) <-Z)
(GOAL SINEQUALITIES
($F $X (PLUS $Y $Z)»).
This rule says: to prove X-Y < Z, try to prove X < Y + Z. It belongs
to the goal class INEQUALITIES and is thus used not only for the
predicate LTQ (<), but also for LT (<), GT (>), and GTQ (>). The
variable F is bound to the appropriate predicate symbol when
the pattern is matched against the goal.

3.3. Demons

The goal mechanism is used for reasoning backward from a goal.


However, sometimes we want to reason forward from a statement.
For example, suppose that whenever an assertion of the form X > Y
is asserted, we want to assert Y < X as well. We do this by a QA4
mechanism known as the demon.
A demon is imagined to be a spirit that inhabits a hiding place,
waiting until some specified event occurs, at which time it appears,
performs some action, and vanishes again. We have put several
demons in the system, each watching for a different condition. For
The Language 15

instance, one demon watches for statements of the form X > Y and
makes the statement Y < X. The user of the system can create his
own demons. Demons are a tool for reasoning forward from an
antecedent. In particular, we use demons to drive antecedents into
a canonical form. For example, we drive all inequality expressions
with integer arguments into an assertion of the form X < Y.

3.4. Representations

To as great an extent as possible, we have chosen representations


that model the semantics of the concepts we use so as to make our
deductions shorter and easier. For example, our language has data
structures especially intended to eliminate the need for certain infer¬
ences. In addition to tuples, which are like the familiar lists of the
list-processing languages, we have the finite sets of conventional
mathematics, and bags, which are unordered tuples or, equivalently,
sets that may have multiple occurrences of the same element. [Bags
are called multisets by Knuth (1969), who outlines many of their
properties.] Furthermore, we allow arbitrary expressions to have
property lists in the same way that atoms can have property lists in
LISP (McCarthy et al., 1962).
These data structures are useful in the modeling of equivalence
relations, ordering relations,and arithmetic functions. For instance,
if the addition of numbers and the multiplication of numbers are
each represented by a function of two arguments, then it becomes
necessary to use numerous applications of the commutative and
associative laws to prove anything about the number system. How¬
ever, in QA4 all functions take only one argument, but this argument
can be a tuple, set, or bag, as well as any other expression. Functions
of multiple arguments can be represented by a function defined on
tuples. However, a function that is commutative and associative, such
as PLUS, is defined on bags. The expression (PLUS A 2 B) really
means (PLUS (BAG A 2 B)). Recall that bags are unordered; the
system cannot distinguish between (BAG A 2 B) and (BAG 2 A B).
Consequently, the expressions (PLUS A 2 B) and (PLUS 2 A B)
are identically equal in our system. This makes the commutative law
for addition redundant and, in fact, inexpressible in the language.
Most needs for the associative law are also avoided.
16 REASONING ABOUT PROGRAMS

The logical function AND has the property that, for instance,
(AND A A B) = (AND A B). The number of occurrences of an
argument does not affect its value. Consequently, AND takes a
set as its argument. Since (SET A A B) and (SET A B) are indis¬
tinguishable, (AND A A B) and (AND A B) are identical, and a
statement of their equality is unnecessary. Some functions that
take sets as arguments are AND, OR, EQ, and GCD (greatest
common divisor).
When a new fact is asserted to our system, the value TRUE is
placed on the property list of that fact. If at some later time we
want to know if that fact is true, we simply look on its property
list.
However, certain facts are given special handling in addition. For
example, if we tell the system that certain expressions are equal,
we form a set of those expressions. On the property list of each
expression, we place a pointer to that set. For instance, if we assert
(EQ A B C), the system stores the following:

ABC

(SET ABC)

If we subsequently discover any of these expressions to be equal to


still another expression, the system adds the new expression to the
previously formed set and puts the set on the property list of the
new expression as well. For instance, if we assert (EQ B D), our
structure is changed to the following:

A B C D

The transitivity, symmetry, and reflexivity of equality are thus im¬


plicit in our representation. If we ask whether A and D are equal, the
system knows immediately by looking at the property list of A or D.
Ordering relations are also stored using the property-list mechanism.
If we know that some expression A is less than B, we place a pointer
The Language 17

to B on the property list of A:

If we learn that B is less than C, we put a pointer to C on the prop¬


erty list of B:

If we then ask the system if A is less than C, it will search along the
pointers in the appropriate way to answer affirmatively. The transi¬
tive law is built into this representation.
The system knows about LT (<), GT (>), LTQ (<), GTQ (>),
EQ (=), NEQ (A), and how these relations interact. For example, if
we assert X> Y,Y>Z, and X < Z, the system will know X = Y = Z
and that (F X A) = (F Y A) for any function symbol F and argument
A. Or if we assert X > Y and X A Y, the system will know X > Y.

3.5. Contexts

When we are trying to prove an implication of the form A D B, it


is natural to want to prove B under the hypothesis that A is true.
Our assumption of the truth of A holds only as long as we are trying
to prove B; after the proof of B is complete, we want to forget that
we have assumed A. For this and other reasons, the QA4 language
contains a context mechanism. All assertions are made with respect
to a context, either implicitly or explicitly. For any context, we can
create an arbitrary number of lower contexts.
A query made with respect to a context will have access to all
assertions made with respect to higher contexts but not to any
assertions made with respect to any other contexts. For instance,
suppose we are trying to prove i < j D i + 1 < j with respect to some
context C0. We may have already made some assertions in context
C0. We establish a lower context, Cx, and assert i <j with respect to
Cx. Then we try to prove i + 1 < j with respect to Ci. When proving
i + 1 < j, we know i < j, as well as all the assertions we knew previ¬
ously in C0. When the proof of i + 1 < j is complete, we may have
other statements to prove in C0. In doing these proofs, we will know
all the assertions in C0 and also, perhaps, the assertion i <j D i + 1 <j,
but we will not know i < j because it was asserted with respect to a
lower context.
18 REASONING ABOUT PROGRAMS

3.6. User Interaction

Sometime^ our rules ask whether they should continue or fail.


This allows a user to cut off lines of reasoning that he knows in
advance are fruitless. If he makes a mistake in aswering the ques¬
tion, he may cause the system to fail when it could have succeeded.
However, he can never cause the system to find a false or erroneous
proof.
In addition to these mechanisms, which are built into the language
processor, we have developed some notations that make it easier to
describe programming constructs; these notations are a part of our
assertion language and are interpreted by the deductive system.

3.7. Notation

In speaking about the program to find the maximum element of


an array, we found it convenient to use the ellipsis notation (“...”)•
We have not introduced this notation into our assertion language;
however, we have found ways of getting around its absence.

3.7.1. TUP A, SETA, BAGA


Let A be a one-dimensional array and I and J be integers. Then
(TUPA A I J) is the tuple

(TUPLE A[I], A[I + 1], ..., A[J]).

If I > J, then (TUPA A I J) is the empty tuple.


(SETA A I J) and (BAGA A I J) are the corresponding bag and
set. To state that an array is sorted between 0 and N, we assert

(LTQ (TUPA A 0 N)).

To state that an array A is the same in contents between 0 and N as


the initial array A0, although these contents may have been per¬
muted, we assert

(EQ (BAGA AON) (BAGA A0 0 N)).


The Language 19

3.7.2. The STRIP Operator

Let X be a set or bag, X = (SET X1, ..., Xn), or X = (BAG Xx, ...,
Xn). Then (LTQ (STRIP X) Y) means X1 < Y and ... and Xn < Y.
For instance, to state that MAX is greater than or equal to any
element in an array A between I and J, we assert

(LTQ (STRIP (BAGA A I J)) MAX).

This is perhaps not quite so clear as

A[I] < MAX, A[I + 1 ] < MAX, ..., A[J]<MAX,

but we prefer it to the first-order predicate calculus notation,

(\/u)[(I<u Au< J) D A[u] < MAX].

The STRIP operator is also used to remove parentheses from


expressions:
(BAG A (STRIP (BAG B C D)))

is

(BAG A B C D).

We will eventually need two distinct operators, one to act as a


quantifier and one to remove parentheses, but the single operator
STRIP has played both roles so far.

3.7.3. Access and Change


Arrays cannot be treated as functions, because their contents can
be changed, whereas functions do not change their definitions. Thus,
while f(x) is likely to mean the same thing for the same value of x
at different times, A[x] is not. We overcome this difficulty by adopt¬
ing McCarthy (1962) functions2 ACCESS and CHANGE in our

2 They are actually called c and a (“contents” and “assign”) in that paper. We follow
King (1969), who inadvertently reversed the roles of the initials.
20 REASONING ABOUT PROGRAMS

explication of the array concept:

(ACCESS A I) means A[I],


(CHANGE A I T) means the array A after the assignment
statement A[I] has been executed.

We do not propose that ACCESS and CHANGE be used in writing


programs or assertions; we do find that they make reasoning about
arrays simpler, as King suspected they would.
The next section shows examples of some fairly difficult proofs
produced by the deductive system. The actual traces for some of
these are included in Section 7.

4. Examples

4.1. The Real-Number Quotient Algorithm

Very little work has been done to prove properties of programs


that work on the real numbers or the floating-point numbers, although
there is no reason to believe such proofs could not be done. Figure
1.5 shows, for instance, a program (Wensley, 1958) to compute an
approximate quotient Y of real numbers P and Q, where 0 < P < Q.
This is an interesting computationally plausible algorithm. It uses
only addition, subtraction, and division by 2, and it computes a
new significant bit of the quotient with each iteration.
The algorithm can be understood in the following way. At the
beginning of each iteration, P/Q belongs to the half-open interval
[Y, Y + D). It is determined whether P/Q belongs to the left half or
the right half of the interval, if P/Q is in the right half, Y is reset to Y +
D/2; otherwise, Y is let alone. Then D is halved. Thus P/Q remains in
the interval [Y, Y + D), and Y becomes a better and better approxi¬
mation for P/Q. Initially, Y is 0 and D is 1. The variables A and B
retain certain intermediate values to make the computation more
efficient.
We will consider here only one path through this program, i.e.,
the path around the loop that follows the right branch of the test
P < A + B. We will prove only one loop assertion: P < Y*Q + D*Q.
Our verification condition generator supplies us with the following
Examples 21

Figure 1.5. The Wensley Quotient Algorithm.

hypotheses:
0<P, (1.22)

P<Q, (1-23)

A = Q*Y, (1-24)

2*B - Q*D, (1.25)

P< Y*Q + D*Q, (1.26)

Y*Q < P, (1.27)

n (D < E), (1.28)

P< A + B. (1.29)

The goal is to prove from these hypotheses that


P < Q*Y + Q*(D/2). (1.30)

These hypotheses and the goal were constructed in a manner pre-


REASONING ABOUT PROGRAMS
22

cisely analogous to the generation of the condition for the previous


example of computing the maximum of an array.
The proof goes as follows. After an abortive attempt at using the
assertion (1.26), the system tries to show that the conclusion follows
from (1.29). It therefore tries to show that
A + B < Q*Y + Q*(D/2). (1-31)

This goal is broken into the following two:

A < Q*Y, (1.32)

B < Q*(D/2). (1.33)


Of course, this strategy will not always be successful. However, in
this case the goal (1.32) follows from (1.24), whereas (1.33) follows
from (1.25).
A complete trace of this proof and listings of the rules required to
achieve it are provided in the Appendices (Sections 6 and 7).

4.2. A Pattern Matcher

As an experiment in the incorporation of new knowledge into the


system, we performed the partial verification of two new examples,
a simple pattern matcher and a recursive version of the unification
algorithm (Robinson, 1965). These algorithms were of special in¬
terest to us because they involve concepts similar to those actually
used in the implementation of the QA4 system itself. They are thus
in some sense realistic, although neither of these programs appears
literally in the QA4 code.
Before we began proving properties of the pattern matcher we had
only verified numeric algorithms. With the pattern matcher the
system had to be acquainted with a new domain; it had to learn
about expressions, substitutions, variables and constants. Therefore
this phase of experiment tested the ability of the system to work
with a new set of concepts. We will now describe this new domain.
We assume that expressions are LISP S-expressions (McCarthy
et al., 1962); for example, (F X (G A B)) is an expression. Atomic
elements are designated as either constant or variable, and they can
be distinguished by the use of the predicates const and var. Here we
Examples 23

use A, B, C, F, and G as constants and U, V, W, X, Y, and Z as variables:

var(X) is true,
const(A) is true,
var(A) is false,
var((X Y)) is false.

The predicate constexp is true if its argument contains no variables:

constexpdA B (X) C)) is false,


constexp((A B (D) C)) is true,
constexpiA) is true.

Note that constexp, as distinguished from const, can be true even


if its argument is nonatomic.
A substitution replaces some of the variables of an expression by
terms. Substitutions are represented as lists of dotted pairs. ((X • A)
(Y • (F G))) is a substitution. varsubst(s, e) is the result of making
substitution s in expression e. If s is

((X • A) (Y • (G B))),
and e is
(F X A (Y B)),

then varsubst(s, e) is
(F A A ((G B) B)).

The LISP functions car, cdr, list, and atom can be used to manipulate
expressions. The empty substitution is denoted by EMPTY and has
no effect on an expression. An operation called compose, the com¬
position of substitutions, defined by Robinson (1965), has the
following property:
varsubst(compose(s \, s2), e) = varsubst(s 1, varsubst{s2, e)).
The problem of pattern matching is defined as follows: Given two
expressions called the pattern and the argument, try to find a sub¬
stitution for the variables of the pattern that makes it identical to the
argument. We call such a substitution a match. For example, if the
24 REASONING ABOUT PROGRAMS

pattern pat is
(X (Y AB) X),

and the argument arg is


(D (C A B) D),

then match(pat, arg) is


((X • D) (Y • Q).
If there is no substitution that makes the pattern identical to the
argument, we want the pattern-matcher to return the distinguished
atom NOMATCH. Thus, if pat is (X Y X) and arg is (A B C), then
matchipat, arg) = NOMATCH, since we cannot expect X to be
matched against both A and C.
For simplicity, we assume that the argument contains no variables.
A LISP-like program to perform the match might be

matchipat, arg) = prog((ml m2)


if constipat) then (if pat = arg then return(EMPTY)
else return(NOMATCH))
if var(pat) then return(list(cons(pat, arg)))
if atom(arg) then return(NOMATCH)
m\^match(car{pat), car{arg))
if m 1 = NOMATCH then return(NOMATCH)
m'l^matchiyarsubstimX, cdr(pat)), cdr(arg))
if m2 = NOMATCH then return(NOMATCH)
return(compose(m2, m 1))).

The program does the appropriate thing in the case of atomic


patterns or arguments, and it calls itself recursively on the left and
right halves of the expressions in the nonatomic case. The program
applies the substitution found in matching the left halves of the
expressions to the right half of the pattern before it is matched, so as
to avoid having the same variable matched against different terms.
We have proved several facts about a version of this program, but
we focus our attention here on one of them: If the program does not
return NOMATCH, then the substitution it finds actually is a match;
i.e. applying the substition to the pattern makes that pattern identical
Examples 25

to the argument. Thus, the output assertion is:

matchipat, arg) NOMATCH D


varsubst(match(pat, arg), pat) = arg.

Since we assume the argument contains no variables, the input


assertion is

constexp(arg). (1.34)

We have verified one condition for the longest path of match with
respect to these assertions. This path is followed when the pattern
and the argument are both nonatomic and when the recursive calls
on match successfully return a substitution. In writing our verifica¬
tion condition, we use the same abbreviations the program does, i.e.,
m 1 = match(car{pat), car(arg)) (1.35)
and
m2 = match(varsubst(m 1, cdr(pat)), cdr(arg)).
In proving a property of a recursively defined program, we follow
Manna and Pnueli (1970) and assume that property about the re¬
cursive call to the program. Thus, for this program we have the
inductive hypotheses
constexp(cariarg)) Ami A NOMATCH D
varsubst(m 1, car(pat)) = car(arg)
(the program works for the car of the pattern) and
constexp(cdr(arg)) A m2 NOMATCH Z>
varsubst(m2, varsubst(m 1, cdr(pat))) = cdriarg) (1.36)

(the program works for the instantiated cdr of the pattern).3 The
verification condition generator would split both of these hypotheses
into three cases; we will consider only the case in which the anteced¬
ents of both implications are true. Hence, we assume that both the
recursive calls to the pattern matcher succeed in finding matches.

3 Actually, in order to assume the inductive hypotheses we must ensure that the input
assertion is satisfied for the recursive calls, in other words, that constexp(car(arg)) and
constexp{cdr{arg)). This follows immediately from the input assertion for the entire
program, constexp(arg).
26 REASONING ABOUT PROGRAMS

By the path we have taken through the program, we know that


_\const{pat) (1-37)

(the pattern is not a constant),


n\var(pat) (1.38)

(the pattern is not a variable), and


~}atom(arg) (1-39)

(the argument is not an atom). Since for this path


match(pat, arg) = composeim2, ml),
the goal is to prove
varsubst(compose(m2, ml), pat) = arg. (1-40)
The proof produced by the system proceeds as follows. The goal is
split into two subgoals:
varsubst(compose(m2, m 1), car(pat)) = car(arg) (1.41)
and
varsubst(compose(m2, ml), cdr(pat)) = cdr(arg). (1.42)

From the property of compose, the first goal is simplified to

varsubst(m2, varsubst{m 1, car(pat))) = car(arg).


Since

varsubst(m 1, car{pat)) = car(arg)

by the first induction hypothesis (1.36), this simplifies to

varsubst(m2, car(arg)) = car(arg).

Since arg contains no variables, neither does car(arg). Thus, the goal
simplifies to
car(arg) = car(arg).

The proof of (1.42) is even simpler:

varsubst(compose(m2,7721), cdr(pat))

simplifies to

varsubst(m2, varsubst(m 1, cdr(pat))).


Examples 27

We know by our second induction hypothesis (1.36) that


varsubst(m2, varsubst(m 1, cdr(pat))) = cdriarg),

and this completes the proof.


This proof required not only that we add new rules describing the
concepts involved, but also that we extend certain of our older
capabilities, particularly our ability to simplify expressions using
known equalities. A trace of the system’s search for this proof is
included in Section 7.
We worked nearly a week before the system was able to do this
proof. However, once the proof was completed, the effort necessary
to enable the system to do the proof of the unification algorithm
was minimal. The latter proof, though longer than this one, did not
require much additional intellectual capacity on the part of the
deductive system. We do not show that proof here because it is
similar to the pattern matcher proof, but we include the program
and the assertion we proved about it.

4.3. The Unification Algorithm

The problem of unification is similar to that of pattern matching


except that we allow both arguments to contain variables. We
expect the algorithm to find a substitution that makes the two argu¬
ments identical when it is applied to both, if such a substitution
exists. For example, if x is (F U A) and y is (F B V), then unifyix, y)
is ((U'B) (V*A)), where U and V are variables and A, B, and F are
constants.
A simple program to unify x and y is
unifyix, y) = prog((ml m2)
if x = y then return(EMPTY)
if var(x) then
return(if occursinix, y) then NOMATCH
else list(cons(x, y)))
if variy) then
return(if occursiniy, x) then NOMATCH
else listiconsiy, *)))
if atom(x) then return(NOMATCH)
if atomiy) then return(NOMATCH)
m1 unify(car(x), cariy))
28 REASONING ABOUT PROGRAMS

if ml = NOMATCH then return (NOMATCH)


m2<- unify(vansubst(m 1, cdr(x)),
varsubstfm 1, cdr(y)))
if m2 = NOMATCH then return(NOMATCH)
return(compose(m2, ml))).
The predicate occursin(u,v) tests if u occurs in u. This program is a
recursive, list-oriented version of Robinson’s iterative, string-oriented
program. Again, we have verified only the longest path of the pro¬
gram, not the entire program. Furthermore, we have proved not
the strongest possible statement about this program, but only that
unify(x,y) A NOMATCH D
varsubstiunifyix, y), x) = varsubst(unify(x ,y), y).

4.4. The FIND Program


The program FIND, described by Hoare (1961), is intended to
rearrange an array A so that all the elements to the left of a certain
index F are less than or equal to A[F], and all those to the right of
F are greater than or equal to A[F], In other words, the relation
(STRIP (BAGA A 1 F-l)) < A[F] < (STRIP (BAGA A F+l NN))
should hold when the program halts. For instance, if F is NN^-2, then
A[F] is the median of the array. The function is useful in computing
percentiles and is fairly complex.
Hoare remarks that a sorting program would achieve the same pur¬
pose but would usually require much more time; the conditions for
FIND are much weaker in that, for example, the elements to the left
of F need not be sorted themselves, as long as none of them are
greater than A[F].
The ALGOL representation of FIND is as follows:

FIND (F,NN,A); INTEGER F,NN; INTEGER ARRAY A[ I :NN]


BEGIN
INTEGER M,N;
M <- 1;
N NN;
WHILE M <N DO
BEGIN INTEGER R,I,J;
R<-A[F];
I «- M;
J <-N;
WHILE I < J DO
Examples 29

BEGIN WHILE A[I] < R DO 1 + 1+1 ;


WHILE R < A[J] DO J + J —1;
IF I <J THEN
BEGIN EXCHANGE (A I J);
I + 1+1;
J + J-l
END
END
IF F < J THEN N + J
ELSE IF I<F THEN M + I
ELSE GO TO L
END
L:
END

The general strategy of the program FIND is to move “small”


elements to the left and “large” elements to the right. These relative
size categories are defined as being less than or not less than an arbi¬
trary array element. The algorithm scans the array from left to right
looking for a large element; when it finds one, it scans from right
to left looking for a small element. When it finds one, it exchanges
the large element and the small element it has already found, and the
scan from the left continues where it left off until the next large
element is found, and so on. When the scan from the left and the
scan from the right meet somewhere in the middle, they define a
split in the array. We can then show that all the elements to the left
of the split are small and all those to the right are large.
The index F can be either to the left or to the right of the split,
but suppose it is to the left. Then the elements to the right of the
split can remain where they are; they are the largest elements in the
array, and the element that will ultimately be in position F is to the
left of the split. We then disregard the right portion of the array and
repeat the process with the split as the upper bound of the array and
with a refined definition of “large” and “small”. We will eventually
find a new split; suppose this split is to the left of F. We can then
leave in place the elements of the array to the left of the split and
work only with the elements to the right; we readjust the left bound
of the array to occur at the split, and we repeat the process. Thus,
the left and right bounds of the array move closer and closer together,
but they always have F between them. Finally, they meet at F, and
the algorithm halts.
30 REASONING ABOUT PROGRAMS

The flowchart in Figure 1.6 follows Hoare’s algorithm closely.


In this program, I is the pointer for the left-to-right scan, J is the
pointer for the right-to-left scan, M and N are the lower and upper
bounds of the “middle” portion of the array, and R is the value used
to discriminate between small and large array elements. Hoare (1971)
provided an informal manual proof of the correctness of his program.
Deutsch (1973) and Igarashi, London, and Luckham (1975) have
produced machine proofs. The proof we obtained required a minimal
number (three) of intermediate assertions; however, one of the veri¬
fication conditions produced was quite difficult to prove. This con¬
dition corresponds to the statement that the elements to the right of
the right boundary dominate the elements to its left after an ex¬
change is performed and a new right boundary is established. We
present a sketch of this proof below.

4.4.1. Assertions for FIND

The input assertion qs for FIND is (the conjunction of)

1 <F <NN,

A = AP,

The array AP is the initial version of A; we define it in the input


assertion so that we can refer to it after we have modified A.
The output assertion qH is

(STRIP (BAGAA 1 F-1))<A[F] < (STRIP (BAGA A F+l NN))


(BAGA A 1 NN) = (BAGA AP 1 NN).

The second conjunct of qH states that when the program termin¬


ates, the array A is indeed a permutation of the initial array AP.
The intermediate assertion q1 is

1<M<F<N<NN
(STRIP (BAGA A 1 M— 1)) < (STRIP (BAGA A M NN))
(STRIP (BAGA A 1 N)) < STRIP (BAGA A N+l NN))
(BAGA A 1 NN) = (BAGA AP 1 NN).

This assertion is reached whenever a new bound on the middle sec¬


tion of the array is established.
Figure 1.6.

31
32 REASONING ABOUT PROGRAMS

The assertion q2 is

1<M<F<N<NN
(STRIP (BAGA A 1 M—1)) < (STRIP (BAGA A M NN))
(STRIP (BAGA A 1 N)) < (STRIP (BAGA A N+l NN))
M<I
J<N
(STRIP (BAGA A 1 I- 1)) < R < (STRIP (BAGA A J+l NN))
(BAGA A 1 NN) = (BAGA AP 1 NN).
The assertion q3 is the same as the assertion q2, with the addi¬
tional conjunct
R < A[I].
4.4.2 The Proof
All but one of the verification conditions for this program were
proved fairly easily. The one difficult condition corresponds to the
path beginning at q3 that follows the heavy line and finally ends at
qx. The verification-condition generator supplied us with the following
hypotheses:

1 < M < F < N < NN, (1.43)


(STRIP (BAGA A 1 M-1)) < (STRIP (BAGA A M NN)), (1.44)
(STRIP (BAGA A 1 N)) < (STRIP (BAGA A N+1 NN)), (1.45)
M < I, (1.46)
J<N, (1.47)
(STRIP (BAGA A 1 I-1)) < R < (STRIP (BAGA A J+1 NN)), (1.48)
R < A[I], (1.49)
(BAGA AP 1 NN) = (BAGA A 1 NN), (1.50)
n(R < A[ J]), (1.51)

I<J, (1.52)
-|(I+1 < J—1), (1.53)

F<J-h (1.54)

The interesting consequence for this path is

(STRIP (BAGA A' 1 J— 1)) < (STRIP (BAGA A' (J—1)+1 NN)),

(1.55)
Examples 33

where
A' = (EXCHANGE A I J),

the array that results when elements A[ 1 ] and A[J] are interchanged
in A.
The proof sketched below roughly parallels the proof produced by
the inference system. Portions of the trace are shown in Section 7.
The (J-D+l term in the goal (1.55) is simplified to J, giving the
goal

(STRIP (BAGA A' 1 J—1)) < (STRIP (BAGA A' J NN)). (1.56)

The difficulty in the proof arises from the uncertainty about whether
J < I. We are reasoning about an array segment, and it is not clear
whether that segment is affected by the exchange or not. Hand
analysis of the hypotheses (1.52) and (1.53) reveals that I = J or I =
J-l. The value of a term like (BAGA (EXCHANGE A I J) 1 J—1)
depends on which possibility is actually the case.
The system “simplifies” the term into

(IF J < I THEN (BAGA A 1 J-1)


ELSE (BAG (STRIP (BAGA A 1 1-1))
A[J]
(STRIP (BAGA A 1+1 J-l)))).

Intuitively, if J < I, then both I and J are outside the bounds of the
array segment, whereas if I < J, then the array segment loses A[I]
but gains A[J].
Similarly, the term

(BAGA (EXCHANGE A I J) J NN)

is “simplified” into

(IF J < I THEN (BAGA A J NN)


ELSE (BAG (STRIP (BAGA A J J-l))
A [I ]
(STRIP (BAGA A J+l NN)))).

Note that (BAGA A J J-1) is empty; the ELSE clause is then

(BAG A[I] (STRIP (BAGA A J+l NN))).


34 REASONING ABOUT PROGRAMS

Our goal can thus be reduced to showing that


(IF J < I THEN (STRIP (BAGA A 1 J-l))
ELSE (STRIP (BAG (STRIP (BAGA A 1 I—1))
A[J ]
(STRIP (BAGA A I+J J-l)))))
<
(IF J < I THEN (STRIP (BAGA A J NN))
ELSE (STRIP (BAG A[I]
(STRIP (BAGA A J+l NN))))).
(1.57)
The system approaches the conditional expression by creating two
contexts: In one context, J < I holds, and in the other, I < J. In the
first context we must prove that

(STRIP (BAGA A 1 J-1)) < (STRIP (BAGA A J NN)). (1.58)

In the second context, the statement to be proved is

(STRIP (BAG (STRIP (BAGA A 1 I- 1))


A[J]
(STRIP (BAGA A 1+1 J-l))))
<
(STRIP (BAG A[ I ]
(STRIP (BAGA A J+l NN)))). (1.59)

Note that ir the first context, J = I by (1.52). In working on (1.58),


(BAGA A J NN) is expanded to (BAG A[J] (STRIP (BAGA A J+l
NN))). Thus, (1.58) breaks into two subgoals:

(STRIP (BAGA A 1 J-l)) <A[J] (1.60)


and
(STRIP (BAGA A 1 J-l)) < (STRIP (BAGA A J+l NN)).

(1.61)

Since I = J, (1.60) follows from (1.48) and (1.49), and (1.61) follows
from (1.48) alone.
Work on the goal (1.59) proceeds in the second context, in which
I< J. Since J-l < 1+1 (1.53), we know (BAGA A 1+1 J-l) is empty.
Conclusion 35

The inequality (1.59) may thus be broken into four inequalities:

(STRIP (BAGA A 1 I-l))< A[I], (1.62)

(STRIP (BAGA A 1 I-1)) < (STRIP (BAGA A J+1 NN)),


(1.63)
A[J]<A[I], (1.64)
and
A[J] < (STRIP (BAGA A J+l NN)). (1.65)

The goal (1.62) follows from the hypotheses (1.48) and (1.49). The
goal (1.63) follows from (1.48). The goal (1.64) follows from (1.49)
and (1.51). The goal (1.65) follows from (1.51) and (1.48). This
completes the proof.
This proof is the most complex achieved by our deductive system
so far.

5. Conclusion

5.1 Summary of Results

Complete proofs have been found of the correctness of the follow¬


ing algorithms:

(1) Finding the largest element of an array.


(2) Finding the quotient of two real numbers.
(3) Hoare’s FIND program.
(4) The Euclidean algorithm for finding the greatest common
divisor.
(5) The exponentiation program from King’s thesis.
(6) Integer quotient and remainder.
(7) Integer multiplication by repeated addition.
(8) Computing the factorial of a nonnegative integer.

Theorems have been proved about the following algorithms:

(1) The pattern matcher.


(2) Unification.
36 REASONING ABOUT PROGRAMS

(3) Exchanging two array elements (the theorem is that the


bag of the contents of the array is unchanged).
(4) King’s exchange sort.

We believe the system now has the power to do all of King’s


problem set except the linear inequalities problem, which is not
really a proof about an algorithm.

5.2. Future Plans

We are currently applying the verifier to more and more complex


programs in a variety of subject domains. We are continuously being
forced to add new rules and occasionally to generalize old ones; a
special-purpose rule that worked for one problem may not work for
the next.
The deductive system is implemented in the QA4 language.
Although QA4 is ideally suited for expressing our rules, it is an
experimental system evaluated by an interpreter which is written
in LISP; furthermore, it uses space inefficiently. Reboh and Sacer-
doti have integrated QA4 into INTERLISP to produce a system
known as QLISP (Wilber, 1976). QLISP programs are translated into
LISP programs that can be evaluated by the LISP interpreter or even
compiled. Furthermore, QLIP is much more conservative in its
use of space. The QLISP system is considerably faster and more com¬
pact that the QA4 system. Our deductive system has already been
translated into QLISP, and the same proofs are carried out many
times faster.
QA4 subtly encourages its users to write depth-first search strate¬
gies, since it implements the goal mechanism by means of back¬
tracking. The deductive system uses depth-first search and, for the
most part, this has been the proper thing to do. There have been
times, however, when we have felt the need for something more
discriminating. Suppose, for example, we are trying to prove an
expression of the form x = y. We can do this by trying to simplify
x and then proving that the simplified x is equal to y, or we can
try to find some assertion a - b and prove x = a and y = b. In the
current system, we must exhaust one possibility before trying
another, whereas we would like to be able to switch back and forth
Appendix: Annotated Listing of the Deductive System 37

between different approaches, giving more attention to the one


that currently seems to be making the best progress. In other words,
we hope to use processes rather than backtracking in the imple¬
mentation of the goal mechanism.
Finally, we hope to apply this work to the generation of counter¬
examples for “wrong” programs, to the generation of Floyd asser¬
tions, and to the automatic construction of programs. It seems
inevitable that if we know how to reason about programs, that
reasoning should be able to help us in the process of forming or
changing a program. Rather than taking a handwritten and hand-
debugged program to a verifier for approval, we hope to collaborate
with a system that will play an active role in the creation of the
algorithm.

6. Appendix: Annotated Listing of the Deductive System

The deductive system has the overall structure shown in Figure 1.7. The
names on the chart are either function names or goal classes. Only important
substructures and relationships are included.

Figure 1.7. Structure of the Deductive System.


38 REASONING ABOUT PROGRAMS

An annotated listing of the programs used for reasoning is presented below.


An index of functions and goal classes is included at the end of this appendix.
The reader will note how little of the space is devoted to general strategies
and how much is devoted to subject-specific knowledge. Some ol the programs
use QA4 features that are not described in this paper. The reader can rely on the
English explication of the programs, or he can refer to the QA4 manual (Rulif-
son et al., 1972).
To start a deduction, we say to the system
(GOAL SPROVE (some statement))

• PROVE is a goal class: 4


(TUPLE ANDSPLIT ORSPLIT ORSPLITMANY PROOFSWITCH)

• The rule ANDSPLIT takes a goal that is a conjunction of two or more expres¬
sions 5 and tries to prove each conjunct independently.

ANDSPLIT=(LAMBDA (AND <-X


(ATTEMPT (GOAL SGOALCLASS $X)
THEN
(ATTEMPT (GOAL SGOALCLASS
(AND $$Y))
ELSE
(FAIL))
ELSE
(FAIL]6 7”8
If repeated applications of ANDSPLIT are successful eventually, the goal (AND)

4 Bullets are used to indicate the beginning of the description of a new function.
5 Variables with double prefixes, or respectively match or evaluate to a
sequence of terms rather than a single term. In the rule, for example, Y can be bound to
a set of terms, including the empty set.
6 The right bracket represents a string of right parentheses long enough to balance the
expression.
7 The ATTEMPT statement is a conditional expression that tests for failure rather than
falsehood, and has the additional power to restrict and control the effects of backtracking.
For instance, suppose an expression of the form (ATTEMPT P THEN Q ELSE R) is being
evaluated. If the evaluation of P is completed successfully, then Q is evaluated. On the
other hand, if the evaluation of P results in a failure, then R is evaluated. A failure in P
cannot cause the ATTEMPT statement to fail. A failure in Q or R, however, will cause the
ATTEMPT statement to fail as usual.
8 The ANDSPLIT rule occurs in several goal classes. The variable GOALCLASS that
occurs in ANDSPLIT is bound by the system to whatever goal class was in effect when
ANDSPLIT was invoked. Thus, ANDSPLIT applies to each conjunct the same goal class
that was applied to the entire conjunction.
Appendix: Annotated Listing of the Deductive System 39

will be generated. However, (AND) is an assertion in the data base, and so the
rule will then succeed.

• ORSPLIT applies to a goal that is the disjunction of two expressions and


works on each separately.

ORSPLIT=(LAMBDA (OR <-Y)


(ATTEMPT (GOAL SGOALCLASS $X)
ELSE
(GOAL SGOALCLASS $Y]

The expression x is attempted as a goal first; if this is successful, we are done.


Otherwise, ORSPLIT works on.y; if it is unsuccessful, then a failure is generated.

• ORSPLITMANY is similar to ORSPLIT, except that it takes as a goal the dis¬


junction of three or more expressions:

ORSPLITMANY =
(LAMBDA (OR «-X «-Y ^W)
(ATTEMPT (GOAL SGOALCLASS $X)
ELSE
(GOAL SGOALCLASS (OR $Y $Z $$W)

The expression x is attempted first; if the proof is successful, the disjunction is


true. Otherwise, the disjunction of the remaining expressions is established as
a new goal. Continued failure to prove members of a disjunction will eventually
cause ORSPLIT to be invoked.

• PROOFSWITCH attempts to apply the appropriate goal class to prove a goal.


It determines whether the goal is an equality; if not, it is assumed to be an in¬
equality. (Other goal classes could be added with little difficulty.) If the proof
is successful, the goal is added to the data base as an assertion.

PROOFSWITCH=
(LAMBDA (<-F <-X)
(PROG (DECLARE)
(IF (EQUAL $F (QUOTE EQ))
THEN
(GOAL SEQRULES ($F $X))
ELSE
(GOAL SINEQUALITIES ($F $X)))
(ASSERT ($F $X))
(RETURN ($F $X]

In either case, the appropriate set of rules is applied.


40 REASONING ABOUT PROGRAMS

6.1. Equalities

• The equality class is

EQRULES=
(TUPLE ANDSPLIT ORSPLIT ORSPLITMANY RELCHECK EQTIMESDIVIDE
EQSUBST LEIBT LEIBF LEIBB LEIBS EQSIMP PROOFLEIB)

• The rule RELCHECK merely checks the property lists of the expressions to
see if they are already known to be equal:

RELCHECK=( LAMBDA (ISREL? $X]

When RELCHECK is applied, x is bound to an equality statement, which is fed


to the ISREL? statement. ISREL? will succeed not only if the equality has been
explicitly asserted, but also if the equality follows by the transitive law from
other equalities or inequalities. ISREL? is the mechanism for making queries
about special relations. It will work with inequality relations, such as LT, GTQ,
and NEQ, as well as EQ.
EQTIMESDIVIDE and EQSUBST are rules for reasoning about numbers and
substitutions, respectively. They are discussed in the relevant sections.

• To prove /(x) = f(y), try to prove x = y: this form of Leibniz’s law for func¬
tion applications is expressed by the rule LEIBF. The analogous rules for tuples,
sets, and bags are expressed by LEIBT, LEIBS, and LEIBB respectively.

LEIBF=(LAMBDA (EQ (<-F <-X)


(<"F <"Y))
(PROG (DECLARE)
($ASK ('(EQ $X $Y))
PROVE?)
(GOAL SEQRULES (EQ $X $Y]

LEIBT=(LAMBDA (EQ (TUPLE «-X <-«-Z)


(TUPLE <-Y <-*-W))
(PROG (DECLARE)
(GOAL SEQRULES (EQ $X $Y))
(GOAL SEQRULES (EQ $Z $W]

LEIBS=(LAMBDA (EQ (SET <- X ^Z)


(SET <-Y *-<-Z))
(GOAL SEQRULES (EQ $X $Y]

LEIBB=(LAMBDA (EQ (BAG «-X <+Z)


(BAG <-Y <^Z))
(GOAL SEQRULES (EQ $X $Y]
Appendix: Annotated Listing of the Deductive System 41

The LEIBF rule asks the user if he wants that rule to be applied. The function
ASK that performs the interaction is described in the section on utility functions.
• EQSIMP and PROOFLEIB are very time-consuming but also very powerful.
EQSIMP says: to prove x = y, simplify x and try to prove that the simplified x
is equal to y.

EQSIMP= (LAMBDA (EQ «-Y)


(PROG (DECLARE)
(SETQ ^X ($SIMPONE $X))
(GOAL SEQRULES (EQ $X $Y»)
(BACKTRACK]

Since the program uses the BACKTRACK option, and since EQ implicitly
takes a set as its argument, EQSIMP can work on y as well as on x. In other
words, if it fails to simplify x, it will go ahead and try to simplify y.

• PROOFLEIB tries to make use of information stored in the data base. It is


used to prove inequalities as well as equalities.

PROOFLEIB=(LAMBDA (■HE7 HX)


(PROG (DECLARE)
(EXISTS ($F <-Y))
($ASK ('(EQ $X $Y))
PROVE?)
(GOAL SEQRULES (EQ $X $Y]

It says: to prove u = v, find an assertion of the form a = b and prove u = a and


v = b. The rule relies on user interaction to cut off bad paths. Note that if / is
EQ, we can expect x and y to be sets, so that LEIBS will ultimately be called to
prove the equality expression generated by PROOFLEIB.
The EXISTS statement searches the data base for an assertion that matches
its argument, binding the variables appropriately. Subsequent failure causes it
to look for another matching assertion. When no more assertions match, the
EXISTS statement itself fails.

6.2. Inequalities

We now turn to the rules for proving inequalities.

INEQUALITIES =
(TUPLE ANDSPLIT RELCHECK ORSPLIT ORSPLITMANY PROOFSIMP
INEQIFTHENELSE INEQSTR1PBAG INEQSTRIPSTRIP
INEQSTRIPTRAN GTQLTQ LTQMANY FSUBSTRACT1
FSUBTRACT2 INEQTIMESDIVIDE EQINEQMONOTONE LTQPLUS
PROOFLEIB INEQLEIB)
42 REASONING ABOUT PROGRAMS

RELCHECKhas been mentioned above.

• GTQLTQ says: to proven try to prove x

GTQLTQ =(LAMBDA (GTQ <-X)


(GOAL SINEQUALITIES (LTQ $X $Y]

• LTQMANY takes an inequality goal, such as

X1 <x2< <Xn,

and breaks it into separate goals,

x1 <x2 and x2<x3 and — and <xn.

LTQMANYKLAMBDA (LTQ <-X ^Y <~Z «-<-W)


(PROG (DECLARE)
(GOAL SINEQUALITIES (LTQ $X $Y))
(GOAL SINEQUALITIES
(LTQ $Y $Z $$W]

LTQPLUS, FSUBTRACT1, and FSUBTRACT2 are special rules for reasoning


about numbers and are discussed in the relevant section.

• PROOFSIMP proves an expression f(y) by trying to simplify y and proving


the simplified expression.

PROOFSIMP=(LAMBDA (PAND *-X (HF <~Y))


(PROG (DECLARE GOALCLASS1)
(SETQ ^GOALCLASSl SGOALCLASS)
(ATTEMPT (SETQ <-X (SARGSIMP $X))
ELSE
(FAIL))
(GOAL SGOALCLASS1 $X]

It has more general application than just to inequalities, although so far we


have used it only for inequalities.

• INEQLEIB is similar to PROOFLEIB, but it works only for inequalities.

INEQLEIB=(LAMBDA (<-L <~Y)


(PROG (DECLARE LOWER UPPER)
(EXISTS ($L SLOWER ^UPPER))
($ASK PROVE (' (LTQ $X SLOWER))
AND
('(LTQ SUPPER $Y))
?)
(GOAL SINEQUALITIES
(AND (LTQ $X SLOWER)
(LTQ SUPPER $Y]
Appendix: Annotated Listing of the Deductive System 43

L is expected to be LT or LTQ. To prove x<y, for example, find an asserted


statement LOWER < UPPER and prove x<LOWER and UPPERS.

• INEQIFTHENELSE is a rule that sets up a case analysis:

INEQIFTHENELSE=(LAMBDA
(«-F ^wi (IFTHENELSE ^X <~Y <~Z) *+W2)
(PROG (DECLARE VER1CON)
(ATTEMPT (SETQ +-VERICON
(CONTEXT PUSH LOCAL))
(ASSERT $X WRT SVERICON)
THEN
(GOAL SINEQUALITIES
($F $$W1 $Y $$W2)
WRT SVERICON))
(ATTEMPT (SETQ <~VERICON
(CONTEXT PUSH LOCAL))
(DENY $X WRT SVERICON)
THEN
(GOAL SINEQUALITIES
($F SSWl $Z $$W2)
WRT SVERICON)
ELSE
(RETURN (SUCCESS WITH
INEQIFTHENELSE]

For example, suppose the goal is (IF x THEN y ELSE z) < w. This rule
establishes two subcontexts of the local context. In one of these contexts, x is
true; in the other, x is false. In the first context, the rule tries to prove y < w,
whereas in the second, it tries to prove z < w. Note that the subsystem that
stores equalities and inequalities will cause a failure if an assertion (or a denial)
would lead it to contradict what it knows. In that case the goal is considered to be
achieved.

• INEQSTRIPBAG is an inequality rule that has a bag as one of its arguments.

INEQSTRIPBAG=(LAMBDA (<~F ^W (STRIP (BAG «-X «-«-Y))


+-*-z)
(GOAL SINEQUALITIES
(AND ($F $$W $X $$Z)
($F $$W (STRIP (BAG $$Y))
$$Z ]

This rule would be invoked when we want to show, e.g., uq <


(STRIP (BAG cq c2, ...)) ^ w2. The intention here is to demonstrate that w1 <
Cj < w2 and w1 < c2 < w2, and so forth. Ultimately, we might have to dem¬
onstrate that w1 <(STRIP (BAG)) <w2. The special relations handler (ISREL?)
44 REASONING ABOUT PROGRAMS

succeeds vacuously with any inequality relation where one of the arguments is
(STRIP (BAG)).

6.3. Deduce

• DEDUCE is a goal class of rules that are guaranteed to terminate quickly. It is


used when we want something more inquisitive than EXISTS but less time-
consuming than PROVE, EQRULES, or INEQUALITIES.

DEDUCE=
(TUPLE RELCHECK ANDSPLIT ORSPLIT ORSPLITMANY LTPLUS
FSUBTRACT1 FSUBTRACT2 LTQPLUS NOTATOM CONSTCAR
CONSTCDR)

We have already described RELCHECK, ANDSPLIT, ORSPLIT, and ORSPLIT¬


MANY.
The other DEDUCE rules are for special applications and are discussed in the
appropriate sections.

6.4. Simplification

• The top-level simplification function is SIMPONE. This function does not try
to simplify its argument completely. It will find a partial simplification; repeated
applications, if necessary, will completely simplify the expression.
The simplification rules may, of course, be added by the user. We expect that
each simplification rule should make the expression simpler in some sense.
Otherwise, the program may loop interminably.

SIMPONE=(LAMBDA *~GOALl (PROG


(DECLARE SIMPGOAL)
(IF (EQUAL (STYPE $GOALl) NUMBER)
THEN
(FAIL))
($ASK SGOALl SIMPLIFY?)
(SETQ ^-SIMPGOAL
(ATTEMPT
(GOAL STOPRULES SGOALl)
ELSE
($TRY STOPRULES
(GOAL SDOWNRULES SGOALl))))
(PUT SGOALl SIMPLIFIED SSIMPGOAL
WRT ETERNAL)
(RETURN SSIMPGOAL]

SIMPONE fails if it cannot simplify its argument at all. It treats numbers as


Appendix: Annotated Listing of the Deductive System 45

being completely simplified. It asks the user for permission to go ahead. It tries
a goal class, TOPRULES, on the expression.

• TOPRULES is a set of rules that work on the top level of the expression:

TOPRULES=
(TUPLE HASSIMP FAILINTODOWNRULES PLUSOP TIMESOP MINUSOP
FIFTHENELSE BAGAOP SUBSTOP EXPZERO EXPEXP SUBPLUS
SUBNUM GCDEQ ACCH CONSD1FF DIFDIF D1FFCONS DIFFONE
MAXPLUS MAXONE BAGSTRIP ACCEX EQNUMB)

If any of these rules apply, SIMPONE returns the simplified expression as its
value. Otherwise, it tries to simplify some subexpression of the given expression:

DOWNRULES=(TUPLE ARGSIMP TUPS1MP BAGSIMP SETSIMP)

ARGSIMP=(LAMBDA (*-F *~X)


(SUBST (' ($F $X))
(TUPLE $X (SSIMPONE $X]

TUPSIMP=(LAMBDA (TUPLE ^X <-Y «-<-Z)


(TUPLE $$X (SSIMPONE $Y)
$$Z)
BACKTRACK]

BAGSIMP=(LAMBDA (BAG <-X <-*-Y)


(BAG (SSIMPONE $X)
$$Y)
BACKTRACK]

SETSIMP= (LAMBDA (SET «-X <-«-Y)


(SET (SSIMPONE $X)
$$Y)
BACKTRACK]

The DOWNRULES simplify a complex expression by simplifying the component


parts of the expression. If any of the DOWNRULES applies, SIMPONE applies
the TOPRULES again to the new expression. SIMPONE calls the functions ASK
and TRY that are described in the section on utility functions.

• SIMPONE puts the simplified expression on the property list of the original
expression. In this way, if it ever comes across the original expression again, one
of the TOPRULES, HASSIMP, will immediately know what simplification was
found before.

HASSIMP=(LAMBDA ^X (IF (NOT (IN (SETQ «-X (GET $X SIMPLIFIED))


(TUPLE DONE NOSUCHPROPERTY)))
THEN $X ELSE (FAIL]
46 REASONING ABOUT PROGRAMS

• If the expression to be simplified is a set, tuple, or bag rather than a function


application, none of the TOPRULES will apply to it. To avoid the cost of
searching for a match among all the TOPRULES, the rule FAILINTODOWN-
RULES will first test for this condition and cause the entire goal statement to
fail should it arise:

FAILINTODOWNRULES=(LAMBDA «-X
(IF (IN (STYPE $X)
(TUPLE TUPLE SET BAG))
THEN
(FAIL GOAL)
ELSE
(FAIL]

SIMPONE will then apply the DOWNRULES to the argument to see if any of
its subexpressions can be simplified.

• One of the most general TOPRULES is EQNUMB, which replaces any ex¬
pression by the “smallest” known equal expression:

EQNUMB=(LAMBDA <~X (PROG (DECLARE BEST EQSET)


(IF (EQUAL (SETQ ^EQSET (GET $X EQ))
NOSUCHPROPERTY)
THEN
(FAIL))
(SETQ ^BEST($SHORTEST SEQSET))
(IF (EQUAL SBEST $X)
THEN
(FAIL)
ELSE
(RETURN SBEST]

The “smallest” element of a set is computed by the QA4 function SHORTEST,


described among the utility functions. If EQNUMB fails to find a smaller
representation forx, it fails.

• FIFTHENELSE=(LAMBDA (<r-F (IFTHENELSE <-W ^X <~Y))


('(IFTHENELSE $W ($F $X)
($F $Y]

FIFTHENELSE moves conditional expressions outside of function applications.


An expression of the form

/(IF w THEN x ELSEy)

translates into

IF w THEN/(x) ELSE/(v).
Appendix: Annotated Listing of the Deductive System 47

The remaining rules in TOPRULES are discussed in the sections dealing


with special subject domains.

6.5. Reasoning About Numbers

6.5.1. Equality and inequality rules

• EQTIMESDIVIDE is an EQRULE. It means that to prove w = (x/y)*z, one


should prove w*v = x*z:

EQTIMESDIVIDE=(LAMBDA (EQ (TIMES (DIVIDE *~X <~Y)

(GOAL SEQRULES (EQ (TIMES $Y $W)


(TIMES $X $$Z)))
BACKTRACK]

Some inequality rules that know about numbers are presented below.

• LTQPLUS says that to prove i< j+k, one should prove i </ and 0 < k:

LTQPLUS=(LAMBDA (LTQ 1 (PLUS «-J «-K))


(GOAL SDEDUCE (AND (LTQ $1 $J)
(LTQ 0 $K)))
BACKTRACK]

First, the rule attempts to prove that i < / and 0 < k. If either of these proofs
is unsuccessful, then the backtracking mechanism will interchange the bindings
of the arguments of LTQPLUS. This then leads to an attempt to prove i < k
and 0 </.

• LTPLUS is the analogue of LTQPLUS for LT:

LTPLUS=(LAMBDA (LT «-I (PLUS <-J <-K))


(GOAL SDEDUCE (AND (LTQ $1 $J)
(LT 0 $K)))
BACKTRACK]

It means: to prove i < j+k, prove i < j and 0 < k. It can backtrack to reverse the
roles of / and k.
• FSUBTRACT1 and FSUBTRACT2 allow us to remove subtraction from the
goal; for example, to prove x-y < z, try to prove x <x+z-

FSUBTRACT1=( LAMBDA (<-F (SUBTRACT <~X ^Y)


^Z)
(GOAL SGOALCLASS ($F $X (PLUS $Y $Z]
48 REASONING ABOUT PROGRAMS

FSUBTRACT2=(LAMBDA («-F *-X (SUBTRACT <~Y <-Z))


(GOAL SGOALCLASS ($F (PLUS $X $Z)
$Y]

• EQINEQMONOTONE says: to prove w+x < y+z, prove w < y and x < z or
w <z and x
EQINEQMONOTONE= (LAMBDA («-L (PLUS <-X)
(PLUS «-Y <-Z))
(PROG (DECLARE)
($ASK PROVE ('($L $W $Y))
AND
('($L $X $Z))
?)
(GOAL SGOALCLASS
(AND ($L $W $Y)
($L $X $Z]
BACKTRACK]

• The rule INEQTIMESDIVIDE is similar to EQTIMESDIVIDE except that it


must check that the denominator is nonnegative before multiplying out:

INEQTIMESDIVIDE= (LAMBDA («-F (TIMES (DIVIDE *~X *-Y)


++-Z))
(PROG (DECLARE)
(GOAL SDEDUCE (LT 0 $Y))
(GOAL SINEQUALITIES
($F (TIMES $Y $W)
(TIMES $X $$Z))))
BACKTRACK]

This rule says: to prove w < (x/y)*z, say, in the case that 0 <y, try to prove
w*y <x*z.

6.5.2. Numerical demons

• When x >y is asserted, assert that v <x:

(WHEN EXP (GTQ ^X <-Y)


INDICATOR MODELVALUE THEN (ASSERT (LTQ $Y $X)
WRT SVERICON]

These demons make their assertions with respect to the current context,
VERICON.
• Whenever x+y <x+z is asserted, we want to conclude thaty <z:

(WHEN EXP (LTQ (PLUS ^X <-Y)


(PLUS ^X <-Z))
INDICATOR MODELVALUE THEN
(ASSERT (LTQ $Y $Z)
WRT SVERICON]
Appendix: Annotated Listing of the Deductive System 49

• Whenever w—x < y is asserted, assert w < x+y, simplifying the right side if
possible.
(WHEN EXP (LTQ (SUBTRACT MV <~X)
MO
INDICATOR MODELVALUE THEN
(PROG (DECLARE RTSIDE)
(SETQ MfTSIDE
(STRYALL SPLUSRULES
(' (PLUS $Y $X]
(ASSERT (LTQ $W SRTSIDE)
WRT SVERICON))))

• Whenever (w—.x)+x is asserted, then assert w <y:

WHEN EXP (LTQ (PLUS (SUBTRACT MV MX)


<~X)
MQ
INDICATOR MODELVALUE THEN
(ASSERT (LTQ $W $Y)
WRT SVERICON]

Certain demons are intended exclusively for the integer domain.


• x <y Dx+1

(WHEN EXP (LT MX MQ


INDICATOR MODELVALUE THEN
(ASSERT (LTQ (PLUS $X 1)
$Y)
WRT SVERICON]

• x >y Djp+1 <x:

(WHEN EXP (GT M( MQ


INDICATOR MODELVALUE THEN
(ASSERT (LTQ (PLUS $Y 1)
$X)
WRT SVERICON]

Whenever w—x <y is denied, deny w <y+x—1, simplifying if possible:

(WHEN EXP (LT (SUBTRACT MV MX)


MO
INDICATOR MODELVALUE PUTS FALSE THEN
(PROG (DECLARE RTSIDE)
(SETQ
^RTSIDE
50 REASONING ABOUT PROGRAMS

(STRYALL SPLUSRULES
('(PLUS $Y $X
(MINUS 1))))
(DENY (LTQ $W SRTSIDE)
WRT SVERICON]

6.5.3. Numerical simplification

Much of the knowledge the system has about numbers is embedded in the
simplifier. For efficiency, these rules have been arranged hierarchically. For
example, only one rule, PLUSOP, in TOPRULES deals with sums.9

PLUSOP=(LAMBDA (PAND <~Y (PLUS ^X))


(STRYALLFAIL SPLUSRULES $Y]

Flowever, this one rule coordinates a multitude of other rules. All the rules that
operate on plus expressions are in the goal class PLUSRULES.

PLUSRULES=(TUPLE PLUSEMPTY PLUSSINGLE PLUSZERO PLUSPLUS


PLUSMINUS PLUSDIFFERENCE PLUSCOMBINE
PLUSNUMBER)

The strategy PLUSOP uses is to apply all the PLUSRULES to its argument until
no further simplification is possible. (The function TRYALLFAIL, that expresses
this strategy, is described among the utility functions.) If PLUSOP can find no
simplification at all, it fails.
Most of the PLUSRULES are quite simple.

• The sum of the empty bag is 0:

PLUSEMPTY= (LAMBDA (PLUS) 0)

• The sum of a bag of one element is that element itself:

PLUSSINGLE=(LAMBDA (PLUS <-X) $X]

(i.e., +x = x).
• x+0 = +x:

PLUSZERO=(LAMBDA (PLUS *-+-X 0) (' (PLUS $$X]

9 A pattern (PAND pat\ pat2) will match an argument if and only if pat\ and pat2
both match that argument.
Appendix: Annotated Listing of the Deductive System 51

Note that this rule implicitly says

0 + x = +x,
x + 0+y=x+y,

x+y + 0 + z= x+y+z,

and so forth, because PLUS takes a bag as its argument.

• ((xi+X2+...)+yi+y2+-) = (xi+x2+...+);i+y2+-):

PLUSPLUS=(LAMBDA (PLUS (PLUS «-«-X) <-<-Y)


('(PLUS $$X $ $ Y ]

• x+(—x)+y = +y:

PLUSMINUS=(LAMBDA (PLUS (MINUS <-X) <~^Y)


(' (PLUS $$Y]

• x+(y—z)+w = x+y+w+(—z):

PLUSDIFFERENCE=(LAMBDA (PLUS *~X (SUBTRACT *-Y <-Z) <-«-W)


($TRY (TUPLE PLUSMINUS)
(' (PLUS $Y $X $$W (MINUS $Z]

Note that PLUSDIFFERENCE recommends that PLUSMINUS be attempted


immediately afterward. This is merely advice; if PLUSMINUS does not apply,
nothing is lost. (TRY is described in the section on utility functions.)

• x+x+y = 2 *x+y:

PLUSCOMBINE=(LAMBDA (PLUS <~X +-X ++-Y)


(STRYSUB STIMESRULES ON 1
(' (TIMES 2 $X))
IN
(' (PLUS (TIMES 2 $X) $$Y]

Note that PLUSCOMBINE recommends that the 2*x term be simplified if


possible. (TRYSUB is explained in the section on utility functions.)

• If two elements of a plus expression are syntactically numbers, PLUS-


NUMBER will add them up:

PLUSNUMBER=(LAMBDA (PLUS *-X «-Y «-«-Z)


(PROG (DECLARE SUM)
52 REASONING ABOUT PROGRAMS

(SINSIST (EQUAL (STYPE $X)


NUMBER))
(SINSIST (EQUAL (STYPE $Y)
NUMBER))
(SETQ ^SUM (PLUS $X $Y))
(RETURN (PLUS $SUM $$Z)))
BACKTRACK]

• The rule TIMESOP is strategically similar to PLUSOP:

TIMESOP=(LAMBDA (PAND <-Y (TIMES <-*-X))


(STRYALLFAIL STIMESRULES $Y]

It will apply all the TIMESRULES to the expression in question. TIMESRULES


is

TIMESRULES =
(TUPLE TIMESEMPTY TIMESSINGLE TIMESZERO TIMESONE TIMESPLUS
TIMESTIMES CANCEL SQRULE TIMESEXP TIMESDIVIDEONE)

• The product of the empty bag is 1:

TIMESEMPTY=(LAMBDA (TIMES) 1)

• The product of a bag of one element is that element itself:

TIMESSINGLE=(LAMBDA (TIMES <-X) $X)

• 0*j = 0:
TIMESZERO=(LAMBDA (TIMES 0 <-*-Y) 0)

• 1 *x = x:

TIMESONE=(LAMBDA (TIMES 1 *+X)


('(TIMES $$X]

Recall that these rules also imply


x*l*y = x*y,

x*0*y*z = 0,

and so forth.

• (x+y)*z = x*z+(+y)*z (distributive law), where +y represents a sum of one or


more terms:
Appendix: Annotated Listing of the Deductive System 53

TIMESPLUS=(LAMBDA (TIMES (PLUS ++-Z)


($TRY SPLUSRULES (STRYSUB SPLUSRULES ON ('(PLUS $$Y))
IN
('(PLUS (TIMES $X $$Z)
(TIMES (PLUS $$Y) $$Z]

(Some simplification is attempted immediately on +y and on x*z + (+y)*z.


TRYSUB is explained in the section on utility functions.)

• ((*1 *x2*...)*Ti *X2 •••)= (*■ 1 2* ■■■ *y i *y 2 *•••)•

TIMESTIMES=(LAMBDA (TIMES (TIMES «-*-X) ^Y)


(' (TIMES $$X $$Y]

• x*(l /y)*z = (x/y)*z:

TIMESDIVIDEONE=(LAMBDA (TIMES *-X (DIVIDE 1 <-Y)


(' (TIMES (DIVIDE $X $Y)
$$Z]

• x*(y/x)*z = y*z:

CANCEL=(LAMBDA (TIMES ^X (DIVIDE^Y «-X) *-<-Z)


(' (TIMES $Y $$Z]

• x*x*y - x2* y:

SQRULE=(LAMBDA (TIMES ^X «-X ^Y)


(STRY (TUPLE TIMESSINGLE)
(' (TIMES (EXP $X 2) $$Y]

• x*xn*y = xn + 1 *y~.

TIMESEXP=(LAMBDA (TIMES ^X (EXP ^X *-N) «-*-Y)


(STRYSUB SPLUSRULES ON ('(PLUS $N 1))
IN
(' (TIMES (EXP $X (PLUS $N 1)) $$Y]

• To the reader who has gotten this far, MINUSOP will be self-explanatory:

MINUSOP=
(LAMBDA (MINUS <-X)
(GOAL (TUPLE MINUSZERO MINUSMINUS MINUSPLUS)
(MINUS $X]
54 REASONING ABOUT PROGRAMS

Note that MINUSOP, unlike PLUSOP and TIMESOP, does not apply all
the rules to the expression, but will return the value of the first rule that does
not fail.

• -0 = 0:
MINUSZERO=(LAMBDA (MINUS 0) 0)

• -(-*) = *:

MINUSMINUS=(LAMBDA (MINUS (MINUS «-X)) $X)

• -(x+y) - (-x)+(-^):

MINUSPLUS=(LAMBDA (MINUS (PLUS +-+-Y))


($TRY SPLUSRULES (PLUS (MINUS $X)
(MINUS (PLUS $$Y]

At present there are only two subtraction rules, and so we do not combine
them into one operator:

• x-y=x+(-y):

SUBPLUS=(LAMBDA (SUBSTRACT ^X <-Y)


($TRY SPLUSRULES (' (PLUS $X (MINUS $Y]

• If x and y are both numbers and not variables, SUBNUM actually evaluates
x-y:

SUBNUM=LAMBDA (SUBTRACT ^X <-Y)


(PROG (DECLARE)
(SINSIST (AND (EQUAL (STYPE $X) NUMBER)
(EQUAL (STYPE $Y)
NUMBER)))
(RETURN (= (SUBTRACT $X $Y]

The “=” sign forces the system to evaluate what it would otherwise merely
instantiate. INSIST is another utility function. Two more rules about expo¬
nentiation are given below.
• x° = 1:

EXPZERO= (LAMBDA (EXP «-X 0) 1)

• (xyy =xy**:
EXPEXP=(LAMBDA (EXP (EXP «-X <-Y) ^Z)
(STRYSUB STIMESRULE ON ('(TIMES $Y $Z))
IN
('(EXP $X (TIMES $Y $Z]
Appendix: Annotated Listing of the Deductive System 55

Note that EXPEXP recommends that the TIMESRULES be applied to the


product y*z; this is heuristic advice that could have been omitted.
• gcd{x x)=x\

GCDEQ=(LAMBDA (GCD <-Y)


(PROG (DECLARE)
(GOAL SDEDUCE (EQ ^X <-Y))
(RETURN $X]

The GCD is the greatest common divisor.

6.6. Reasoning About Arrays

Most of the knowledge about arrays embedded in the system is expressed


as simplification rules.

• (ACCESS (CHANGE A I T) I) = T,
I J D (ACCESS (CHANGE A I T) J) = (ACCESS A J ):

ACCH=(LAMBDA (ACCESS (CHANGE <~A «-I «-T)


<-J)
(PROG (DECLARE)
(ATTEMPT (GOAL SDEDUCE (EQ $1 $J))
THEN
(RETURN ST))
(GOAL SDEDUCE (NEQ $1 $J))
(RETURN (ACCESS $A $J]

ACCH is one of the TOPRULES, as are the rules below, ACCEX, MAXONE,
MAX, and MAXPLUS.

• (EXCHANGE A I J) is a higher-level function whose output is the array A


with the values of A[I] and A[J] exchanged. The value of (ACCESS
(EXCHANGE A I J ) K) depends on whether or not K equals I or J, i.e., whether
the element here accessed was affected by the exchange. If K = I, the value is A[J] .
If K = J, the value is A[I]. If K is neither I nor J, the value is the original value
of A[K], since the location has not been affected by the exchange. The rule
fails if it cannot be determined whether or not K= I or K=J. This information is
embodied in the rule ACCEX:

ACCEX=(LAMBDA (ACCESS (EXCHANGE -'-A <~l «-J)


<-K)
(PROG (DECLARE)
(ATTEMPT (GOAL SDEDUCE (EQ $K $1))
THEN
(RETURN (ACCESS $A $J)))
REASONING ABOUT PROGRAMS

(ATTEMPT (GOAL SDEDUCE (EQ $K $J))


THEN
(RETURN (ACCESS $A $1)))
(GOAL SDEDUCE (AND (NEQ $K $1)
(NEQ $K $J)))
(RETURN (ACCESS $A $K]

• The maximum of an array, MAXA, is a function of three arguments: the


array, the lower bound, and the upper bound.

(MAXA A I J)=(MAX A[I], A[I + 1], ..., A[J] )

(MAXA A I I)=A [I] :

MAXONEKLAMBDA (MAXA <-A «-I <-J)


(PROG (DECLARE)
(GOAL SDEDUCE (EQ $1 $J))
(RETURN (ACCESS $A $1]

• If
(MAXA A I J)<A[J+1],

then

(MAXA A I J+1) = A[J+1].

On the other hand, if

(MAXA A I J) >A[J+1],

then

(MAXA A I J + l) = (MAXA A I J);

MAXPLUS=(LAMBDA
(MAXA <-l (PLUS 1))
(PROG (DECLARE)
(ATTEMPT (GOAL SDEDUCE (LTQ (MAXA $A $1 SJ)
(ACCESS $A (PLUS $J 1]
THEN
(RETURN (ACCESS $A (PLUS $J 1))))
(GOAL SDEDUCE (LT (ACCESS $A (PLUS $J 1))
(MAXA $A SI $J))
(RETURN (MAXA $A $1 $ J ]

• Recall that (BAGA A I J) is (BAG A[I], A[I+1], ..., A[J]). Because of the
crucial part this function plays in assertions about sortlike programs, we have
many rules for it.
Appendix: Annotated Listing of the Deductive System 57

BAGARULES=
(TUPLE BAGAPLUS BAGAEMPTY BAGAII ARGSIMP BACH BAGEX BAGEX1
BAGAMINUS BAGALOWERPLUS BAGEXCOMPLICATED)

• These rules are controlled by the rule BAGAOP, one of the TOPRULES:

BAGAOP=(LAMBDA (PAND «-Y (BAGA <-<-X))


(STRYALLFAIL SBAGARULES $Y]

Thus, the BAGARULES will be tried whenever we are simplifying an expres¬


sion of the form (BAGA A I J).

• If I < J+l, then (BAGA A I J+l) = (BAG A[J+1] (STRIP (BAGA A I J))):

BAGAPLUS=(LAMBDA (BAGA ^A <-l (PLUS 1 -<-J))


(PROG (DECLARE)
(GOAL SDEDUCE (LTQ $1 (PLUS 1 $J)))
(RETURN (BAG (ACCESS SA (PLUS $J 1))
(STRIP (BAGA $A $1 $J]

• If I < J, then (BAGA A J I) is the empty bag:

BAGAEMPTY=(LAMBDA (BAGA ^A «-I)


(PROG (DECLARE)
(GOAL SDEDUCE (LT $1 $J))
(RETURN(BAG]

• (BAGA A 11) is (BAG A[I]):

BAGAII=(LAMBDA (BAGA <~A +-J)


(PROG (DECLARE)
(GOAL SDEDUCE (EQ $1 $J))
(RETURN (BAG (ACCESS $A $1]

• If I < J, then (BAGA A I J) = (BAG (STRIP (BAGA A I J—1)) A[J] ):

BAGAMINUSKLAMBDA (BAGA <-A «-I <-J)


(PROG (DECLARE)
(SINSIST (EQUAL (STYPE $J)
IDENT))
(GOAL SDEDUCE (LTQ SI $J))
(RETURN (BAG (ACCESS $A $J)
(STRIP (BAGA $A $1 (SUBTRACT $J 1 ]

Since this rule would apply so often, it is restricted by forcing J to be an


identifier rather than a complex expression.
58 REASONING ABOUT PROGRAMS

• IfL<M, then

(BAGA A L M) = (BAG A[L] (STRIP (BAGA A L+l M))):

BAGALOWERPLUS=
(LAMBDA (BAGA SARNAME -HM)
(PROG (DECLARE F LOWER UPPER W)
(EXISTS (<-F (STRIP (BAGA SARNAME SLOWER
SUPPER))
*~-W))
(GOAL SDEDUCE (EQ SLOWER (PLUS 1 $L)))
(RETURN (BAG (ACCESS SARNAME $L)
(STRIP (BAGA SARNAME (PLUS 1 $L)
$M]

This rule tries to determine if its application is desirable by checking in the


model for any relationship involving an array segment with lower bound equal
to L+l; if no such relationship exists, it is doubtful that the proposed sim¬
plification will lead to a proof.
• If I < J < K, then

(BAGA (CHANGE A J T) I K) =

(BAG T (STRIP (BAGA A I K))) ~ (BAG A[J] ).

On the other hand, if J < I or K < J,

(BAGA (CHANGE A J T) I K) = (BAGA A I K).

(The notation ~ means the difference between two bags.) In other words,
making an assignment to an array element whose index is outside the bounds
of a segment does not affect the segment. However, if the index is within
bounds of the segment, then the corresponding bag will lose the old value of the
array element but gain the new value:

BACH=
(LAMBDA
(BAGA (CHANGE <~A <KT <-T) «-I «-K)
(PROG
^DECLARE)
(ATTEMPT
(GOAL SDEDUCE (LTQ $1 $J $K))
THEN
(RETURN
(=
($TRY
SDIFFRULES
Appendix: Annotated Listing of the Deductive System 59

(STRYSUB
(TUPLE ACCH ACCEX)
ON
(' (ACCESS $A $J))
IN
(STRYSUB SBAGARULES ON ('(BAGA $A $1 $K))
IN
(' (DIFFERENCE (BAG $T
(STRIP (BAGA $A $1 $K)))
(BAG (ACCESS $A $J))))))))))
(GOAL SDEDUCE (OR (LT $J $I)(LT $K $J)))
(RETURN (BAGA $A $1 $K]

The rule BACH contains many recommendations about possible future sim¬
plifications. These recommendations are included to promote efficiency; the
simplifier would eventually try the recommended rules even if the advice were
omitted. The advice-giving functions TRY and TRYSUB are described in the
section on utility functions.

• As mentioned above, (EXCHANGE A I J) is the array A with the values of


A[I] and A[J] interchanged. If I and J are either both inside or both outside
an array segment, then the exchange operation has no effect on the bag corre-
ponding to that segment:

BAGEX = (LAMBDA (BAGA (EXCHANGE *~A -H <Kf) <HL -HM)


(PROG (DECLARE)
(GOAL SDEDUCE (LTQ $1 $J))
(ATTEMPT
(GOAL SDEDUCE
(OR (AND (LTQ $L $1)
(LTQ $J SM)
(LT $J $L)
(LT $M SI)
(AND (LT SI $L)
(LT SM $J))))
THEN
(RETURN (BAGA $A $L $M))
ELSE
(FAIL]

For simplicity, BAGEX requires that I < J.

• If elements A[I] and A[J] are exchanged, and if J is in the array segment and
I is not, or if I is in the segment and J is not, then the corresponding bag is
indeed affected by the exchange operation. For instance, in the case in which J
60 REASONING ABOUT PROGRAMS

is in the segment and I is not, if the segment is bounded by L and M, the new
bag is
(BAG (STRIP (BAGA ALJ-1)
A [I]
(STRIP (BAGA A J +1M))):

BAGEX1=
(LAMBDA
(BAGA (EXCHANGE <~A «-I «-J) <-M)
(PROG (DECLARE)
(GOAL $DEDUCE (LTQ $1 $J))
(ATTEMPT (GOAL SDEDUCE (AND (LT $1 $L)
(LTQ $L $J)
(LTQ $J $M)))
THEN
(RETURN (BAG (STRIP (BAGA $A $L (SUBTRACT $J 1)))
(ACCESS $A $1)
(STRIP (BAGA $A (PLUS 1 $J) $M))))
(ATTEMPT (GOAL SDEDUCE (AND (LT $M $J)
(LTQ $L $1)
(LTQ $1 $M»)
THEN
(RETURN (BAG (STRIP (BAGA $A $L (SUBTRACT $1 1)))
(ACCESS $A $J)
(STRIP (BAGA $A (PLUS 1 $1) $M»»
ELSE
(FAIL]

• BAGEXCOMPLICATED handles the case in which it can be determined


that one of the exchanged elements is within or outside the array segment, but
the location of the other array element is uncertain. The result is then a con¬
ditional expression. For example, if J is known to be outside the segment but
I is only known to be greater than or equal to the lower limit L, the result is

(IF M < I THEN (BAGA A L M)


ELSE (BAG (STRIP (BAGA A L I—1))
A [J]
(STRIP (BAGA A 1 + 1 M))):

BAGEXCOMPLICATED=
(LAMBDA
(BAGA (EXCHANGE <-A <-I «-J) <~h «-M)
(PROG
Appendix: Annotated Listing of the Deductive System 61

(DECLARE)
(GOAL SDEDUCE (LTQ $1 $J))
(ATTEMPT (GOAL SDEDUCE (AND (LTQ $L $1)
(LTQ $M $J)))
THEN
(RETURN (IFTHENELSE
(LT $M $1)
(BAGA $A $L $M)
(BAG (STRIP (BAGA $A $L (SUBTRACT $1 1)))
(ACCESS $A $J)
(STRIP (BAGA $A (PLUS 1 $1) $M))))))
(ATTEMPT (GOAL SDEDUCE (AND (LTQ $J $M)
(LTQ $L $J)»
THEN
(RETURN (IFTHENELSE
(LTQ $L $1)
(BAGA $A $L $M)
(BAG (STRIP (BAGA $A $L (SUBTRACT $J 1)))
(ACCESS $A $1)
(STRIP (BAGA $A (PLUS 1 $J) $M)))))
ELSE
(FAIL]

BAGEXCOMPLICATED comes after BAGEX and BAGEX1 in the goal class


BAGARULES because we prefer the definite answer they provide to the con¬
ditional expression returned by BAGEXCOMPLICATED.
All the rules in this section have been simplification rules. There also are two
inequalities rules that pertain to arrays, INEQSTRIPTRAN and INEQSTRIP-
STRIP.

• To prove that every element in an array segment is less than (or less than or
equal to) some quantity C, find an array segment that properly contains the
given segment such that every element in the larger segment is less than some
element D that is, in turn, less than or equal to C:

INEQSTRIPTRAN=
(LAMBDA (-HF (STRIP (BAGA ^ARNAME <-L <-M))<-C)
(PROG (DECLARE LOWER UPPER D)
(EXISTS ($F (STRIP (BAGA SARNAME SLOWER
•HJPPER)) -HD))
(GOAL SDEDUCE (AND (LTQ SLOWER $L)
(LTQ $M SUPPER)
(LTQ $D SC]

• To prove some ordering relation < or < between all the elements of two array
segments, Sx and S2, find relations of the same sense involving Si' and C, and
62 REASONING ABOUT PROGRAMS

involving D and 82 ’. Then show that Sx’ and S * contain Sx and S


2 2 respectively,
and that C is less than or equal to D.

INEQSTRIPSTRIP=
(LAMBDA (<-F (STRIP (BAGA ^A <~1 <-]))
(STRIP (BAGA ^A <-L)))
(PROG (DECLARE LOWER 1 UPPER 1 LOWER2 UPPER2 C D)
(ATTEMPT (EXISTS ($F (STRIP (BAGA $A SLOWER 1
<-UPPERl))
<-C))
(EXISTS ($F -<-D
(STRIP (BAGA $A ^LOWER2
^UPPER2))))
(GOAL SDEDUCE (AND (LTQ SLOWER] $1)
(LTQ $J SUPPER 1)
(LTQ SLOWER2 $K)
(LTQ $L SUPPER2)
(LTQ $C SD)))
ELSE
(FAIL]

6.7. Reasoning About Bags

We have accumulated a number of rules about bags. Many of these rules have
set-theoretic counterparts, which could have been included, but we have needed
only bags in our proofs.
We use the QA4 function DIFFERENCE to mean the difference between
bags, written informally as
• (BAG xj>)~(BAG x) = (BAG_y):

DIFFXX=(LAMBDA (DIFFERENCE (BAG ^X <-«-Y)


(BAG <-X))
(BAG $$Y]

• cons(x, y ~ z) = cons(x, y)~ z:

CONSDIFF=(LAMBDA (CONS ^X (DIFFERENCE e-Z))


( (DIFFERENCE (CONS SX $Y)
SSZ]

• (x ~_y) ~ z - x ~_y ~ z:

DIFDIF=(LAMBDA (DIFFERENCE (DIFFERENCE -«-X <-<-Y)

('(DIFFERENCE $X $$Y $$Z]


Appendix: Annotated Listing of the Deductive System 63

• consix, y) ~ (BAG x) ~ u = y ~ u:

DIFFCONS=(LAMBDA (DIFFERENCE (CONS «-X <-Y)


(BAG <-X)
^-U)
($TRY (TUPLE DIFFONE)
('(DIFFERENCE $Y $$U]

• (DIFFERENCE x) is taken to be x itself:

DIFFONE= (LAMBDA (DIFFERENCE ^X)$X)

• (BAG (STRIP x)) = x:

BAGSTRIP=(LAMBDA (BAG (STRIP *-X)) $X)

6.8. Reasoning about Substitutions

The rules in this section were added to prove assertions about the pattern
matcher and the unification algorithm.

• An atom is either a variable or a constant:

~\var(x) A ~\const(x) D ~\atom(x):

NOTATOMKLAMBDA (NOT (ATOM <-X))


(PROG (DECLARE)
(EXISTS (NOT (VAR $X)))
(EXISTS (NOT (CONST $X]

• If an expression is made of constants, so is the car and the cdr of the expres¬
sion:

CONSTCAR=(LAMBDA (CONSTEXP (CAR <-X))


(EXISTS (CONSTEXP $X]
CONSTCDR=(LAMBDA (CONSTEXP (CDR <-X»
(EXISTS (CONSTEXP $X]

NOTATOM, CONSTCAR, and CONSTCDR are DEDUCE rules.

• The empty substitution does not change the expression:

SUBSTEMPTY=(LAMBDA (VARSUBST EMPTY <~X)$X)


64 REASONING ABOUT PROGRAMS

• No substitution changes an expression made up entirely of constants:

SUBSTCONST=(LAMBDA (VARSUBST «-S *-Y)


(PROG (DECLARE)
(GOAL SDEDUCE (CONSTEXP $Y))
$Y)

SUBSTEMPTY and SUBSTCONST are simplification rules.


• To prove

varsubst{s, car(x)) = car(y),


prove

varsubst(s, x) = y:

SUBSTCAR=(LAMBDA (EQ (VARSUBST «-Sl (CAR ^X))(CAR <-Y))


(GOAL SEQRULES (EQ (VARSUBST $S1 $X)
$Y]

• Similarly, to prove

varsubst(s, cdr(x)) = cdr(y).


prove

varsubst(s, x) = y:

SUBSTCDR-(LAMBDA (EQ (VARSUBST -^Sl (CDR ^X))(CDR -<-Y))


(GOAL SEQRULES (EQ (VARSUBST SSI $X)
$Y]

• To prove

varsubst(s, x) = y,
where x and y are nonatomic, prove

varsubst(s, car(x)) = car(y )


and

varsubst{s, cdr(x)) = cdr(y):

SUBSTCONS=
(LAMBDA (EQ (VARSUBST +-S1 <-X) <-Y)
(PROG (DECLARE)
(GOAL SDEDUCE (NOT (ATOM $X)))
(GOAL SDEDUCE (NOT (ATOM $Y)))
Appendix: Annotated Listing of the Deductive System 65

(GOAL (= (SREMOVE EQSUBST FROM SEQRULES))


(EQ (VARSUBST $S1 (CAR $X))(CAR $Y)))
(GOAL (= (SREMOVE EQSUBST FROM $EQRULES))
( EQ (VARSUBST $S1 (CDR $X))(CDR $Y]

• SUBSTCAR, SUBSTCDR, and SUBSTCONS are equality rules. They are


clustered together in a goal class:

EQSUBSTRULES=
(TUPLE SUBSTCAR SUBSTCDR SUBSTCARCDR SUBSTCONS)

• EQSUBSTRULES is called from EQSUBST, an EQRULE.

EQSUBST=(LAMBDA (PAND (EQ (VARSUBST «-S <-X) <~Z))


(GOAL SEQSUBSTRULES $Y]

Note that SUBSTCONS removes EQSUBST from the EQRULES. This avoids
the loop that would occur if SUBSTCAR were applied after SUBSTCONS.

• To prove
varsubst(s, u) = varsubst(s, v),

where u and uare nonatomic, prove

varsubst{s, cariu)) = varsubst(s, car(y))


and
varsubst(s, cdr(u)) = varsubstis, cdr(v)):

SUBSTCARCDR=(LAMBDA (EQ (VARSUBST «-S «-U)


(VARSUBST <-S -«-V))
(PROG (DECLARE)
(GOAL SDEDUCE (NOT (ATOM $U)))
(GOAL SDEDUCE (NOT (ATOM $V)))
(GOAL SEQRULES
(EQ (VARSUBST $S (CAR $U))
(VARSUBST $S (CAR $V))))
(GOAL $EQRULES
(EQ (VARSUBST $S (CDR $U))
(VARSBUST $S (CDR $V]
Substitutions are represented as lists of dotted pairs.

• If v is a variable,
varsubst(((wy)), v)-y:
66 REASONING ABOUT PROGRAMS

SUBSTLIST=(LAMBDA (VARSUBST (LIST (CONS «-V <-Y))«-V)


(PROG (DECLARE)
(EXISTS (VAR $V» $Y]

• The composition operator has the property

varsubst(compose(s 1, s2), x) = varsubst(s 1, varsubst(s2, x)):

SUBSTCOMPOSEKLAMBDA (VARSUBST (COMPOSE +-S1 *-S2)«-X)


($TRY SSUBSTRULES
('(VARSUBST $S1 (VARSUBST $S2 $X]

• These simplification rules are members of the goal class

SUBSTRULES=
(TUPLE SUBSTEMPTY SUBSTLIST SUBSTCOMPOSE SUBSTCONST)

which is called by SUBSTOP, a member of TOPRULES:

SUBSTOP=(LAMBDA (PAND (VARSUBST ^X)^Y)


(GOAL SSUBSTRULES$Y]

6.9. Utility Functions

• TRY is like a GOAL statement that will not fail if none of the goal class
applies but instead returns its argument.

TRY=(LAMBDA (TUPLE •'KJOALCLASS +-GOAL1)


(ATTEMPT (GOAL SGOALCLASS SGOALl)
ELSE SGOALl]

It evaluates (GOAL SGOALCLASS SGOALl), but, if failure results, it


returns GOAL1.

• TRY ALL will try a goal class on an expression. If any member of the goal
class applies, it will apply the same goal class to the resulting expression, and so
on, until no rule applies. TRYALL returns the last expression it has derived,
which may be the same as the first expression; TRYALL will not fail:

TRYALL=
(LAMBDA (TUPLE ^GOALCLASSl -‘KjOALI)
(PROG (DECLARE)
TOP (ATTEMPT (SETQ ^GOALl (GOAL SGOALCLASS1
SGOALl))
THEN
(GO TOP))
(RETURN SGOALl ]
Appendix: Annotated Listing of the Deductive System 67

• TRYALLFAIL is like TRYALL, except it will fail if none of the goal class
applies to the argument.

TRYALLFAIL=(LAMBDA (TUPLE ^GOALCLASSl +-GOAL1)


(STRYALL SGOALCLASSl
(GOAL SGOALCLASSl SGOALl]

• TRYSUB applies a goal class to a specially designated subexpression of the


given expression:

TRYSUB=(LAMBDA (TUPLE ^GOALCLASS ON ^SUB IN ^EXP)


(SUBST SEXP
(TUPLE SSUB (STRYALL SGOALCLASS $SUB]

• INSIST fails if its argument is FALSE.

INSIST=(LAMBDA *-X (IF $X ELSE (FAIL]

• REMOVE removes a designated item from a tuple.

REMOVE=(LAMBDA (TUPLE <-X FROM +-Y)


((QUOTE (LAMBDA (TUPLE $X ++-V)
(TUPLE $$U $$V)))
$Y]

• ASK queries the user:

ASK=(LAMBDA *~X
(IF (LISP ASK $X)
ELSE
(FAIL]

It types two expressions, the first a QA4 expression and the second an atom
(e.g., PROVE? or SIMPLIFY?). If the user types YES, TRUE, OK, Y, or T,
ASK returns TRUE; otherwise ASK fails. ASK uses a LISP function of the same
name.
• SHORTEST computes the “smallest” element of a set, bag, or tuple:

SHORTEST=
(LAMBDA <~X
(PROG (DECLARE BEST BESTCOUNT TEMPCOUNT)
(SETQ +-BESTCOUNT 2000)
(MAPC $X (QUOTE (LAMBDA ^Y
(IF (OR (LT (SETQ ^TEMPCOUNT
(LISP QA4COUNT $Y))
68 REASONING ABOUT PROGRAMS

TABLE 1.1
Index of Functions and Goal Classes

Name Page Name Page Name Page

ACCEX 55 FSUBTRACT1 47 PROOFLEIB 41


ACCH 55 FSUBTRACT2 47 PROOFSIMP 42
ANDSPLIT 38 GCDEQ 55 PROOFSWITCH 39
ARGSIMP 45 GTQLTQ 42 PROVE0 38
ASK 67 HASSIMP 45 RELCHECK 40
BACH 58 INEQIFTHENELSE 43 REMOVE 67
BAGAEMPTY 57 INEQLEIB 42 SETSIMP 45
BAGA1I 57 INEQSTRIPBAG 43 SHORTEST 67
BAGALOWERPLUS 58 INEQSTRIPSTRIP 61 SIMPONE 44
BAGAMINUS 57 INEQSTRIPTRAN 61 SQRULE 53
BAGAOP 57 INEQTIMESDIVIDE 48 SUBNUM 54
BAGAPLUS 57 INEQUALITIES0 41 SUBPLUS 54
BAGARULES0 56 INSIST 67 SUBSTCAR 64
BAGEX 59 LEIBB 40 SUBSTCARCDR 65
BAGEX1 59 LEIBF 40 SUBSTCDR 64
BAGEXCOMPLICATED 60 LEIBS 40 SUBSTCOMPOSE 66
BAGSIMP 45 LEIBT 40 SUBSTCONS 64
BAGSTRIP 63 LTPLUS 47 SUBSTCONST 64
CANCEL 53 LTQMANY 42 SUBSTEMPTY 63
CONSDIFF 62 LTQPLUS 47 SUBSTLIST 65
CONSTCAR 63 MAXONE 56 SUBSTOP 66
CONSTCDR 63 MAXPLUS 56 SUBSTRULES0 66
DEDUCE0 44 MINUSMINUS 54 TIMESDIVIDEONE 53
DIFD1F 62 MINUSOP 53 TIMESEMPTY 52
DIFFCONS 63 MINUSPLUS 54 TIMESEXP 53
DIFFONE 63 MINUSZERO 54 TIMESONE 52
DIFFXX 62 NOTATOM 63 TIMESOP 52
DOWNRULES ° 45 ORSPLIT 39 TIMESPLUS 52
EQINEQMONOTONE 48 ORSPLITMANY 39 TIMESRULES0 52
EQNUMB 46 PLUSCOMBINE 51 TIMESSINGLE 52
EQRULES0 40 PLUSDIFFERENCE 51 T1MESTIMES 53
EQSIMP 41 PLUSEMPTY 50 TIMESZERO 52
EQSUBST 65 PLUSMINUS 51 TOPRULES0 45
EQSUBSTRULES0 65 PLUSNUMBER 51 TRY 66
EQTIMESDIVIDE 47 PLUSOP 50 TRYALL 66
EXPEXP 54 PLUSPLUS 51 TRYALLFAIL 67
EXPZERO 54 PLUSRULES0 50 TRYSUB 67
FAILINTODOWNRULES 46 PLUSSINGLE 50 TUPSIMP 45
FIFTHENELSE 46 PLUSZERO 50

0 Goal class.
Appendix: Traces of Solutions 69

SBESTCOUNT)
(EQUAL (STYPE $Y)
NUMBER))
THEN
(SETQ *-BEST $Y)
(SETQ ^BESTCOUNT STEMPCOUNT)))))
$BEST]

The size of an expression is roughly the number of atoms in the expression.


It is computed by a LISP function, QA4COUNT. Numbers are assumed to be
“smaller” than identifiers.
Table 1.1 gives an index of functions and goal classes.

7. Appendix: Traces of Solutions

7.1. The Maximum of an Array (First Verification Condition)

A complete trace of a proof performed by our system is presented below.


The verification condition to be proved is derived from the program to compute
the maximal element of an array. Although a proof is contained above in the
body of the text, following the trace tells us exactly what rules were applied
in the proof. Furthermore, we can see exactly what false starts were made by
the system and what user interaction was required to keep the program on the
right track.
This particular verification condition was derived from the loop path of the
program. The hypotheses are

1 (CONTEXT (1 0) 1 0)
2 (ASSERT (EQ MAX (ACCESS A LOC)) WRT SVERICON)
3 TRUE
4 (ASSERT (LTQ (STRIP (BAGA A 0 I)) MAX) WRT SVERICON)
5 TRUE
6 (ASSERT (LTQ 0 LOC) WRT SVERICON)
7 TRUE
8 (ASSERT (LTQ LOC I) WRT SVERICON)
9 TRUE
10 (ASSERT (LTQ I N) WRT SVERICON)
11 TRUE
12 (DENY (LT N (PLUS 1 I)) WRT SVERICON)
13 FALSE
14 (DENY (LT MAX (ACCESS A (PLUS 1 I))) WRT SVERICON)
15 FALSE
70 REASONING ABOUT PROGRAMS

Since the hypotheses for the different verification conditions of a program


may contradict each other, each proof is done in a separate context. The name
of that context is VERICON. That is the meaning of the phrase “WRT $VERI-
CON'’ which follows our assertions and goal. [For this proof, VERICON is
((1 0) 1 0).] Assertions made with respect to one VERICON will not affect
problems solved with respect to another.

16 (GOAL SPROVE (LTQ (STRIP (BAGA A 0 (PLUS 1 I))) MAX) WRT


SVERICON)
1 7 LAMBDA PROOFSWITCH (LTQ (STRIP (BAGA A 0 (PLUS 1 I))) MAX)

When a traced function is applied to an argument, the trace says

LAMBDA (function name) (argument).

Some of the utility functions are not traced.

18 (GOAL SINEQUALITIES ($F $X))


19 LAMBDA RELCHECK (LTQ (STRIP (BAGA A 0 (PLUS 1 I))) MAX)
20 LAMBDA PROOFSIMP (LTQ (STRIP (BAGA A 0 (PLUS 1 I))) MAX)
21 LAMBDA ARGSIMP (LTQ (STRIP (BAGA A 0 (PLUS 1 I))) MAX)
22 LAMBDA SIMPONE (TUPLE STRIP (BAGA A 0 (PLUS 1 I))) MAX)
(TUPLE (STRIP (BAGA A 0 (PLUS 1 I))) MAX)
SIMPLIFY?
:Y

The system asked us whether we wanted it to simplify

(TUPLE (STRIP (BAGA A 0 (PLUS 1 I))) MAX).

We said yes.

23 (GOAL STOPRULES SGOALl)


24 LAMBDA HASSIMP (TUPLE (STRIP (BAGA A 0 (PLUS 1 I))) MAX)
25 (FAIL)
26 LAMBDA EQNUMB (TUPLE (STRIP (BAGA A 0 (PLUS 1 I))) MAX)
27 (FAIL)
28 (GOAL SDOWNRULES $GOALl)
29 LAMBDA TUPSIMP (TUPLE (STRIP (BAGA A 0 (PLUS 1 I))) MAX)
30 LAMBDA SIMPONE (STRIP (BAGA A 0 (PLUS 1 1)))
(STRIP (BAGA A 0 (PLUS 1 I)))
SIMPLIFY?
:Y
31 (GOAL STOPRULES SGOALl)
32 LAMBDA HASSIMP (STRIP (BAGA A 0 (PLUS 1 I)))
33 (FAIL)
34 LAMBDA EQNUMB (STRIP (BAGA A 0 (PLUS 1 I)))
Appendix: Traces of Solutions 71

35 (FAIL)
36 (GOAL SDOWNRULES (GOAL1)
37 LAMBDA ARGSIMP (STRIP (BAGA A 0 (PLUS 1 I)))
38 LAMBDA SIMPONE (BAGA A 0 (PLUS 1 1))
(BAGA A 0 (PLUS 1 I))
SIMPLIFY?
:Y

We have given the system permission to simplify (BAGA A 0 (PLUS 1 I)).

39 (GOAL STOPRULES SGOALl)


40 LAMBDA HASSIMP (BAGA A 0 (PLUS 1 I))
41 (FAIL)
42 LAMBDA BAGAOP (BAGA A 0 (PLUS 1 I))
43 (GOAL SGOALCLASS1 $GOALl)
44 LAMBDA BAGAPLUS (BAGA A 0 (PLUS 1 I))
45 (GOAL SDEDUCE (LTQ $1 (PLUS 1 $J)))

The system tries to prove that 0<(PLUS 1 I).

46 LAMBDA RELCHECK (LTQ 0 (PLUS 1 I))


47 LAMBDA LTQPLUS (LTQ 0 (PLUS 1 I))
48 (GOAL SDEDUCE (AND (LTQ $1 $J) (LTQ 0 $K)))
49 LAMBDA RELCHECK (AND (LTQ 0 I) (LTQ 0 1))

It breaks down the goal to 0 < I and 0 < 1.

50 LAMBDA ANDSPLIT (AND (LTQ 0 I)(LTQ 0 1))


51 (GOAL SGOALCLASS $X)
52 LAMBDA RELCHECK (LTQ 0 1)
53 RELCHECK = TRUE
When a function returns a value, the trace says
(function name) = (value).

In this case, the system knew that 0 < by performing the corresponding
LISP evaluation.

54 (GOAL SGOALCLASS $Y)


55 LAMBDA RELCHECK (LTQ 0 I)
56 RELCHECK - TRUE

The 0 < 1 follows from the hypotheses 6 and 8.

57 ANDSPLIT = TRUE
58 LTQPLUS = TRUE
59 BAGAPLUS = (BAG (ACCESS A (PLUS 1 I)) (STRIP
(BAGA A 0 I)))
72 REASONING ABOUT PROGRAMS

The system has succeeded in simplifying

(BAGA A 0 (PLUS 1 I))

to
(BAG (ACCESS A (PLUS 1 I)) (STRIP (BAGA A 0 I))).

60 (GOAL SGOALCLASS1 SGOALl)


61 BAGAOP - (BAG (ACCESS A (PLUS 1 I)) (STRIP (BAGA
A 0 I)))
62 SIMPONE = (BAG (ACCESS A (PLUS 1 I)) (STRIP (BAGA
A 0 I)))
63 ARGSIMP = (STRIP (BAG (ACCESS A (PLUS 1 I)) (STRIP (BAGA
A 0 I))))
64 (GOAL SGOALCLASS SGOALl)
65 LAMBA HASSIMP (STRIP (BAG (ACCESS A (PLUS 1 I))
(STRIP (BAGA A 0 I))))
66 (FAIL)
67 LAMBDA EQNUMB (STRIP (BAG (ACCESS A (PLUS 1 I)) (STRIP
(BAGA A 0 I))))
68 (FAIL)
69 SIMPONE = (STRIP (BAG (ACCESS A (PLUS 11)) (STRIP (BAGA
A 0 I))))
70 TUPSIMP = (TUPLE (STRIP (BAG (ACCESS A (PLUS 1 I)) (STRIP
(BAGA A 0 I)))) MAX)
71 (GOAL SGOALCLASS SGOALl)
72 LAMBDA HASSIMP (TUPLE (STRIP (BAG (ACCESS A (PLUS 1 I))
(STRIP (BAGA A 0 I)))) MAX)
73 (FAIL)
74 LAMBDA EQNUMB (TUPLE (STRIP (BAG (ACCESS A (PLUS 1 I))
(STRIP (BAGA A 0 I)))) MAX)
75 (FAIL)
76 SIMPONE - (TUPLE (STRIP (BAG (ACCESS A (PLUS 1 I)) (STRIP
(BAGA A 0 I)))) MAX)
77 ARGSIMP = (LTQ (STRIP (BAG (ACCESS A (PLUS 1 I)) (STRIP (BAGA
A 0 I)))) MAX)

The problem now is to prove

(STRIP (BAG (ACCESS A (PLUS 1 I)) (STRIP (BAGA A 0 I)))) < MAX:

78 (GOAL SGOALCLASS1 $X)


79 LAMBDA RELCHECK (LTQ (STRIP (BAG (ACCESS A (PLUS 1 I))
(STRIP (BAGA A 0 I)))) MAX)
80 RELCHECK=TRUE

But since the system already knows


(ACCESS A (PLUS 1 I)) < MAX from (14),
Appendix: Traces of Solutions 73

and
(STRIP (BAGA A 0 I)) < MAX from (4),
the proof is complete:

81 PROOFSIMP = TRUE
82 (ASSERT ($F $X))
83 PROOFSWITCH = (LTQ (STRIP (BAGA A 0 (PLUS 1 I))) MAX)
84 (LTQ (STRIP (BAGA A 0 (PLUS 1 I))) MAX)

7.2. The Maximum of an Array (Second Verification Condition)

The following is the trace of the proof for another verification condition
for the program that computes the maximal element of an array. This verifica¬
tion condition is derived from the halt path of the program.

1 (CONTEXT (1 0) 1 0)
2 (ASSERT (EQ MAX (ACCESS A LOC)) WRT SVERICON)
3 TRUE
4 (ASSERT (LTQ (STRIP (BAGA A 0 I)) MAX) WRT SVERICON)
5 TRUE
6 (ASSERT (LTQ 0 LOC) WRT $VERICON)
7 TRUE
8 (ASSERT (LTQ LOC I) WRT SVERICON
9 TRUE
10 (ASSERT (LTQ I N) WRT SVERICON)
1 1 TRUE
12 (ASSERT (LT N (PLUS 1 I)) WRT SVERICON)

There is a demon that knows that in the integer domain,

x <y L> x+1 <>'.

This demon is responsible for the assertion

13 (ASSERT (LTQ (PLUS 1 $X) $Y) WRT SVERICON)

The system now knows N+l < 1+1. This assertion wakes up another demon,
which asserts that N < I:

14 (ASSERT (LTQ $Y $Z) WRT SVERICON)


15 TRUE

Since I < N has also been asserted (10), the mechanism for storing ordering
relations silently tells the system that I = N.
74 REASONING ABOUT PROGRAMS

The system proceeds with the proof:

16 (GOAL $PROVE (LTQ (STRIP (BAGA A 0 N)) (ACCESS A LOC)) WRT


SVERICON)
17 LAMBDA PROOFSWITCH (LTQ (STRIP (BAGA A 0 N)) (ACCESS A
LOC))
18 (GOAL SINEQUALITIES ($F $X))
19 LAMBDA RELCHECK (LTQ (STRIP) BAGA A 0 N)) (ACCESS A LOC))
20 LAMBDA PROOFSIMP (LTQ (STRIP (BAGA A 0 N))1(ACCESS A LOC))
21 LAMBDA ARGSIMP (LTQ (STRIP (BAGA A 0 N)) (ACCESS A LOC))
22 LAMBDA SIMPONE (TUPLE (STRIP (BAGA A 0 N)) (ACCESS A
LOC))
(TUPLE (STRIP (BAGA A 0 N)) (ACCESS A LOC))
SIMPLIFY?
:N
23 (FAIL)
24 (FAIL)
25 LAMBDA PROOFLEIB (LTQ (STRIP (BAGA A 0 N)) (ACCESS A LOC))
26 (EXISTS ($F «~Y))

The system searches the data base for an assertion of the form (LTQ <-Y),
i.e., the gross form of the goal we are trying to prove. It finds one [assertion
(4)] and asks us if it should try to prove that the argument of the assertion
it has found is equal to the argument of the goal:

(EQ (TUPLE (STRIP (BAGA A 0 N)) (ACCESS A LOC)) (TUPLE (STRIP


(BAGA A 0 I)) MAX))
PROVE?
We say yes, the proof proceeds.
:Y
27 (GOAL SEQRULES (EQ $X $Y))
28 LAMBDA RELCHECK (EQ (TUPLE (STRIP (BAGA A 0 N)) (ACCESS
A LOC)) (TUPLE (STRIP (BAGA A 0 I)) MAX))
29 RELCHECK = TRUE
30 PROOFLEIB = TRUE
31 (ASSERT ($F $X))
32 PROOFSWITCH = (LTQ (STRIP (BAGA A 0 N)) (ACCESS A LOC))
33 (LTQ (STRIP (BAGA A 0 N)) (ACCESS A LOC))

The two tuples were found to be equal because N = I (from 10 and 14), and
MAX = A[LOC]. The proof is complete.

7.3. The Wensley Division Algorithm

The following is the complete trace of the proof included in the body of the
text:

1 (CONTEXT (1 0) 1 0)
Appendix: Traces of Solutions 75

2 (ASSERT (EQ AA (TIMES QQ YY)) WRT SVERICON)


3 TRUE
4 (ASSERT (EQ (TIMES 2 BB) (TIMES QQ DD)) WRT SVERICON)
5 TRUE
6 (ASSERT (LT PP (PLUS (TIMESQQ YY) (TIMES QQ DD))) WRT
SVERICON)
7 TRUE
8 (ASSERT (LTQ (TIMES QQ YY) PP) WRT $VERICON)
9 TRUE
10 (ASSERT (LT PP (PLUS AA BB)) WRT SVERICON)
11 TRUE
1 2 (DENY (LT (DIVIDES DD 2) EE) WRT $VERICON)
13 FALSE

The goal is to prove PP < QQ*YY + QQ*(DD/2):

14 (GOAL SPROVE (LT PP (PLUS (TIMES QQ YY) (TIMES QQ (DIVIDES


DD 2 )))) WRT SVERICON
15 LAMBDA PROOFSWITCH (LT PP (PLUS (TIMES QQ YY) (TIMES QQ
(DIVIDES DD 2))))
16 (GOAL SINEQUALITIES ($F $X))
17 LAMBDA RELCHECK (LT PP (PLUS (TIMES QQ YY) (TIMES QQ
(DIVIDES DD 2))))
18 LAMBDA PROOFSIMP (LT PP (PLUS (TIMES QQ YY) (TIMES QQ
(DIVIDES DD 2))))
19 LAMBDA ARGSIMP (LT PP (PLUS (TIMES QQ YY) (TIMES QQ
(DIVIDES DD 2))))
20 LAMBDA SIMPONE (TUPLE PP (PLUS (TIMES QQ YY) (TIMES
QQ (DIVIDES DD 2))))
21 LAMBDA ASK (TUPLE PP (PLUS (TIMES QQ YY) (TIMES QQ
(DIVIDES DD 2)))) SIMPLIFY?)
(TUPLE PP (PLUS (TIMES QQ YY) (TIMES QQ (DIVIDES DD 2))))
SIMPLIFY?
:NO
22 (FAIL)
23 (FAIL)
24 LAMBDA PROOFLEIB (LT PP (PLUS (TIMES QQ YY) (TIMES QQ
(DIVIDES DD 2))))
25 (EXISTS ($F <-Y))
26 LAMBDA ASK (TUPLE (EQ (TUPLE PP (PLUS (TIMES QQ YY)
(TIMES QQ (DIVIDES DD 2))))
(TUPLE PP (PLUS (TIMES QQ YY)
(TIMES QQ DD)))) PROVE?)
(EQ (TUPLE PP (PLUS (TIMES QQ YY) (TIMES QQ (DIVIDES DD 2))))
(TUPLE PP (PLUS (TIMES QQ YY) (TIMES QQ DD))))
PROVE?
:NO
76 REASONING ABOUT PROGRAMS

27 (FAIL)
28 LAMBDA ASK (TUPLE (EQ (TUPLE PP (PLUS (TIMES QQ YY)
(TIMES QQ (DIVIDES DD 2))))
(TUPLE PP (PLUS AA BB))) PROVE?)
(EQ (TUPLE PP (PLUS (TIMES QQ YY) (TIMES QQ (DIVIDES DD 2))))
(TUPLE PP (PLUS AA BB)))
PROVE?
:NO
29 (FAIL)
30 (LAMBDA INEQLEIB (LT PP (PLUS (TIMES QQ YY) (TIMES QQ
(DIVIDES DD 2))))
31 (EXISTS ($L (TUPLE SLOWER ^UPPER)))
32 LAMBDA ASK (TUPLE PROVE (LTQ PP PP) AND (LTQ (PLUS
(TIMES QQ YY) (TIMES QQ DD))
(PLUS (TIMES QQ YY) (TIMES
QQ (DIVIDES DD 2))))?)
PROVE
(LTQ PP PP)
AND
(LTQ (PLUS (TIMES QQ YY) (TIMES QQ DD)) (PLUS (TIMES QQ YY)
(TIMES QQ (DIVIDES DD 2))))
?
:NO
33 (FAIL)

After several false starts, the system uses hypothesis (10), to generate two sub¬
goals: PP < PP and AA + BB < QQ*YY + QQ*(DD/2). We give our approval of
this tactic:

34 LAMBDA ASK (TUPLE PROVE (LTQ PP PP) AND (LTQ (PLUS AA


BB)(PLUS (TIMES QQ YY) (TIMES QQ (DIVIDES DD 2)))) ?)
PROVE
(LTQ PP PP)
AND
(LTQ (PLUS AA BB) (PLUS (TIMES QQ YY) (TIMES QQ (DIVIDES DD
2))))
?

YES
35 ASK = TRUE

It proves the first subgoal immediately.

36 (GOAL SINEQUALITIES (AND (LTQ $X SLOWER) (LTQ SUPPER $Y)))


37 LAMBDA ANDSPLIT (AND (LTQ PP PP) (LTQ (PLUS AA BB) (PLUS
TIMES QQ YY) (TIMES QQ (DIVIDES DD 2)))))
38 (GOAL SGOALCLASS $X)
Appendix: Traces of Solutions 77

39 LAMBDA RELCHECK (LTQ PP PP)


40 RELCHECK = TRUE
41 (GOAL SGOALCLASS (AND $$Y))
42 LAMBDA ANDSPLIT (AND (LTQ (PLUS AA BB)
(PLUS (TIMES QQ YY)
(TIMES QQ (DIVIDES DD 2)))))
43 (GOAL SGOALCLASS $X)
44 LAMBDA RELCHECK (LTQ (PLUS AA BB) (PLUS (TIMES QQ
YY) (TIMES QQ (DIVIDES DD 2))))
45 LAMBDA INEQMONOTONE (LTQ (PLUS AA BB)(PLUS (TIMES
QQ YY) (TIMES QQ (DIVIDES DD 2))))
46 LAMBDA ASK (TUPLE ((LTQ AA (TIMES QQ (DIVIDES DD
2))) (LTQ BB (TIMES QQ YY))) PROVE?)
((LTQ AA (TIMES QQ DIVIDES DD 2))) (LTQ BB (TIMES QQ YY)))
PROVE?
:NO
47 (FAIL)
48 LAMBDA INEQMONOTONE (LTQ (PLUS AA BB) (PLUS (TIMES
QQ YY) (TIMES QQ (DIVIDES DD 2))))
49 LAMBDA ASK (TUPLE ((LTQ AA (TIMES QQ YY)) (LTQ BB
(TIMES QQ (DIVIDES DD 2)))) PROVE?)
((LTQ AA (TIMES QQ YY)) (LTQ BB (TIMES QQ (DIVIDES DD 2))))
PROVE?

It divides the second subgoal into two sub-subgoals: AA < QQ*YY and BB
QQ*(DD/2):

: YES
50 ASK = TRUE
51 (GOAL SGOALCLASS (AND ($F (TUPLE $W $Y)) ($F (TUPLE $X
$Z))))
52 LAMBDA ANDSPLIT (AND (LTQ AA (TIMES QQ YY)) (LTQ BB
(TIMES QQ (DIVIDES DD 2))))
53 (GOAL SGOALCLASS $X)
54 LAMBDA RELCHECK (LTQ AA (TIMES QQ YY))
55 RELCHECK = TRUE

The first sub-subgoal follows from hypothesis (2).

56 (GOAL SGOALCLASS (AND SSY))


57 LAMBDA ANDSPLIT (AND LTQ BB (TIMES QQ (DIVIDES DD
2))))
58 (GOAL SGOALCLASS $X)
59 LAMBDA RELCHECK (LTQ BB (TIMES QQ (DIVIDES DD
2)))
78 REASONING ABOUT PROGRAMS

60 LAMBDA INEQTIMESDIVIDE (LTQ BB (TIMES QQ (DIVIDES


DD 2)))
61 (GOAL SDEDUCE (LT 0 $Y))
62 LAMBDA RELCHECK (LT 0 2)
63 RELCHECK = TRUE
64 (GOAL SINEQUALITIES ($F (TUPLE (TIMES $Y $W) (TIMES
$X $$Z))))
65 LAMBDA RELCHECK (LTQ (TIMES 2 BB) (TIMES QQ DD))
66 RELCHECK = TRUE

Checking that 2 > 0, the system multiplies out the second subgoal into 2*BB <
QQ*DD. This follows from assertion (4). The proof is complete:

67 INEQTIMESDIVIDE = TRUE
68 (GOAL SGOALCLASS (AND $$Y))
69 ANDSPLIT = (AND)
70 ANDSPLIT = (AND)
71 INEQMONOTONE=(AND)
72 (GOAL SGOALCLASS (AND $$Y))
73 ANDSPLIT = (AND)
74 ANDSPLIT = (AND)
75 INEQLEIB = (AND)
76 (ASSERT ($F $X))
77 (RETURN ($F $X))
78 PROOFSWITCH = (LT PP (PLUS (TIMES QQ YY) (TIMES QQ (DIVIDES
DD 2))))
79 (LT PP (PLUS (TIMES QQ YY) (TIMES QQ (DIVIDES DD 2))))

7.4. The Pattern Matcher

As an abbreviation, let

ml = match(car(pat), cariarg j)
and

m2 = match(varsubst(m 1, cdr(pat)), cdr(arg)).


The hypotheses are that

varsubst(m 1, caripat)) = car(arg);


or, in unabbreviated form,

1 (ASSERT (EQ (VARSUBST (MATCH (CAR PAT) (CAR ARG))


(CAR PAT)) (CAR ARG)))
2 TRUE
Appendix: Traces of Solutions 79

and that

varsubst(m2, varsubst(m 1, cdr(pat))) = cdr(arg):

3 (ASSERT (EQ (VARSUBST (MATCH (VARSUBST (MATCH (CAR PAT)


(CAR ARG)) (CDR PAT)) (CDR ARG)) (VARSUBST (MATCH (CAR PAT)
(CAR ARG)) CDR PAT))) (CDR ARG)))
4 TRUE

The other hypotheses are

5 (ASSERT (CONSTEXP ARG))


6 TRUE
7 (ASSERT (NOT (CONST PAT)))
8 TRUE
9 (ASSERT (NOT (ATOM ARG)))
10 TRUE
1 1 (ASSERT (NOT (VAR PAT)))
12 TRUE

The goal is to prove

varsubst(compose{m2, ml),pat) = arg:

13 (GOAL SPROVE (EQ (VARSUBST (COMPOSE (MATCH (VARSUBST


(MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG)) (MATCH
(CAR PAT) (CAR ARG))) PAT) ARG))

The proof begins:

14 LAMBDA PROOFSWITCH (EQ (VARSUBST (COMPOSE (MATCH


(VARSUBST (MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG))
(MATCH (CAR PAT) (CAR ARG))) PAT) ARG)
15 (GOAL SEQRULES ($F $X))
16 LAMBDA RELCHECK (EQ (VARSUBST (COMPOSE (MATCH
(VARSUBST (MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG))
(MATCH (CAR PAT) (CAR ARG))) PAT) ARG)
17 LAMBDA EQSUBST (EQ (VARSUBST (COMPOSE (MATCH (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG)) (MATCH
(CAR PAT) (CAR ARG))) PAT) ARG)
18 (GOAL SEQSUBSTRULES $Y)
19 LAMBDA SUBSTCONS (EQ (VARSUBST (COMPOSE (MATCH
(VARSUBST (MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR
ARG)) (MATCH (CAR PAT) (CAR ARG))) PAT) ARG)
20 (GOAL SDEDUCE (NOT (ATOM $X)))
21 LAMBDA RELCHECK (NOT (ATOM PAT))
80 REASONING ABOUT PROGRAMS

22 LAMBDA NOTATOM (NOT (ATOM PAT))


23 (EXISTS (NOT (VAR $X)))
24 (EXISTS (NOT (CONST $X)))
25 NOTATOM = (NOT (CONST PAT))
26 (GOAL SDEDUCE (NOT (ATOM $Y)))
27 (GOAL (= (SREMOVE (TUPLE EQSUBST FROM SEQRULES)))
(EQ (VARSUBST $S1 (CAR $X)) (CAR $Y)))

Reasoning that pat is not an atom because it is neither a variable nor a constant,
the system breaks the goal into two subgoals:

varsubst(compose(m7, m 1), car(pat)) = car(arg)


and
varsubst(compose(m2, ml), cdr(pat)) = cdr(arg).

It begins work on the first of these:

28 LAMBDA RELCHECK (EQ (CAR ARG) (VARSUBST (COMPOSE


(MATCH (VARSUBST (MATCH (CAR PAT) (CAR ARG)) (CDR
PAT)) (CDR ARG)) (MATCH (CAR PAT) (CAR ARG))) (CAR PAT)))
29 LAMBDA EQSIMP (EQ (CAR ARG) (VARSUBST (COMPOSE
(MATCH (VARSUBST (MATCH (CAR PAT) (CAR ARG)) (CDR PAT))
(CDR ARG)) (MATCH (CAR PAT) (CAR ARG))) (CAR PAT)))
30 LAMBDA SIMPONE (VARSUBST (COMPOSE (MATCH (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG)) (MATCH
(CAR PAT) (CAR ARG))) (CAR PAT))
(VARSUBST (COMPOSE (MATCH (VARSUBST (MATCH (CAR PAT) (CAR
ARG)) (CDR PAT)) (CDR ARG)) (MATCH (CAR PAT) (CAR ARG))) (CAR
PAT))
SIMPLIFY?

We give the system our permission to simplify the left side of the first subgoal,

varsubst(compose(m2, ml), car{pat)):

31 (GOAL STOPRULES SGOALl)


32 LAMBDA HASSIMP (VARSUBST (COMPOSE (MATCH (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG))
(MATCH (CAR PAT) (CAR ARG))) (CAR PAT))
33 (FAIL)
34 LAMBDA SUBSTOP (VARSUBST (COMPOSE (MATCH
(VARSUBST (MATCH (CAR PAT) (CAR ARG)) (CDR PAT))
(CDR ARG)) (MATCH (CAR PAT) (CAR ARG))) (CAR PAT))
35 (GOAL SSUBSTRULES $Y)
36 LAMBDA SUBSTCOMPOSE (VARSUBST (COMPOSE (MATCH
(VARSUBST (MATCH (CAR PAT) (CAR ARG)) (CDR PAT))
(CDR ARG)) (MATCH (CAR PAT) (CAR ARG))) (CAR PAT))
Appendix: Traces of Solutions 81

37 (GOAL SGOALCLASS SGOALl)


38 LAMBDA SUBSTCONST (VARSUBST (MATCH (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG))
(VARSUBST (MATCH (CAR PAT) (CAR ARG)) (CAR PAT)))
39 (GOAL SDEDUCE (CONSTEXP $Y))
40 LAMBDA RELCHECK (CONSTEXP (VARSUBST (MATCH
(CAR PAT) (CAR ARG)) (CAR PAT)))
41 SUBSTCOMPOSE = (VARSUBST (MATCH (VARSUBST (MATCH
(CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG)) (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CAR PAT)))
42 SUBSTOP - (VARSUBST (MATCH (VARSUBST (MATCH (CAR
PAT) (CAR ARG)) (CDR PAT)) (CDR ARG)) (VARSUBST (MATCH
(CAR PAT) (CAR ARG)) (CAR PAT)))
43 (RETURN SSIMPGOAL)
44 SIMPONE - (VARSUBST (MATCH (VARSUBST (MATCH (CAR PAT)
(CAR ARG)) (CDR PAT)) (CDR ARG)) (VARSUBST (MATCH (CAR
PAT) (CAR ARC,)) (CAR PAT)))

The system has succeeded in simplifying the left half of the goal into

varsubst(m 2, varsubst(m 1, caripat))).

It now tries to prove this new expression equal to car(arg). In sympathy with
our conscientious readers, we omit portions of the remainder of the trace.

55 LAMBDA SIMPONE (VARSUBST (MATCH (VARSUBST (MATCH


(CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG)) (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CAR PAT)))
(VARSUBST (MATCH (VARSUBST (MATCH (CAR PAT) (CAR ARG))
(CDR PAT)) (CDR ARG)) (VARSUBST (MATCH (CAR PAT) (CAR ARG))
(CAR PAT)))
SIMPLIFY?

The system asks permission to simplify

varsubst{m 1, varsubst(m2, caripat)))

further. Permission is granted. We omit a portion of the trace.

The system wants to simplify varsubst(m 1, caripat)), a subexpression of our


goal. We give our blessings:

:Y
80 (GOAL STOPRULES SGOALl)
81 LAMBDA HASSIMP (VARSUBST (MATCH (CAR PAT)
(CAR ARG)) (CAR PAT))
82 (FAIL)
82 REASONING ABOUT PROGRAMS

83 LAMBDA SUBSTOP (VARSUBST (MATCH (CAR PAT)


(CAR ARG)) (CAR PAT))
84 (GOAL SSUBSTRULES $Y)
85 LAMBDA SUBSTCONST (VARSUBST (MATCH (CAR
PAT) (CAR ARG)) (CAR PAT))
86 (GOAL SDEDUCE (CONSTEXP $Y))
87 LAMBDA RELCHECK (CONSTEXP (CAR PAT))
88 LAMBDA CONSTCAR (CONSTEXP (CAR PAT))
89 (EXISTS (CONSTEXP $X))
90 LAMBDA EQNUMB (VARSUBST (MATCH (CAR
PAT) (CAR ARG)) (CAR PAT))
91 (RETURN $BEST)
92 EQNUMB = (CAR ARG)
93 (RETURN SSIMPGOAL)
94 SIMPONE = (CAR ARG)

The subexpression varsubst(m 1, car(pat)) is known to be equal to car(arg) by


hypothesis (1). The rule EQNUMB has found this simplification. Work continues
on simplifying the entire left-hand side.

95 TUPSIMP = (TUPLE (MATCH (VARSUBST (MATCH


(CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG))
(CAR ARG))
96 GOAL SGOALCLASS SGOALl)
97 LAMBDA HASSIMP (TUPLE (MATCH (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR
ARG)) (CAR ARG))
98 (FAIL)
99 LAMBDA EQNUMB (TUPLE (MATCH (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR
ARG)) (CAR ARG))
100 (FAIL)
101 (RETURN SSIMPGOAL)
102 SIMPONE - (TUPLE (MATCH (VARSUBST (MATCH
(CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG))
(CAR ARG))
103 ARGSIMP - (VARSUBST (MATCH (VARSUBST (MATCH
(CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG)) (CAR
ARG))

The expression being simplified is now varsubst(m2, car(arg)):

104 (GOAL SGOALCLASS SGOALl)


105 LAMBDA HASSIMP (VARSUBST (MATCH (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR
ARG)) (CAR ARG))
Appendix: Traces of Solutions 83

106 (FAIL)
107 LAMBDA SUBSTOP (VARSUBST (MATCH (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR
ARG)) (CAR ARG))
108 (GOAL SSUBSTRULES $Y)
109 LAMBDA SUBSTCONST (VARSUBST (MATCH
(VARSUBST (MATCH (CAR PAT) (CAR ARG)) (CDR
PAT)) (CDR ARG)) (CAR ARG))
110 (GOAL SDEDUCE (CONSTEXP $Y))
11 1 LAMBDA RELCHECK (CONSTEXP (CAR ARG))
1 12 LAMBDA CONSTCAR (CONSTEXP (CAR ARG))
113 (EXISTS (CONSTEXP $X))
114 CONSTCAR = (CONSTEXP ARG)
115 SUBSTCONST = (CAR ARG)
1 16 SUBSTOP = (CAR ARG)
117 (RETURN SSIMPGOAL)
118 SIMPONE = (CAR ARG)

Since arg consists entirely of constants, so does car(arg). Therefore, substitu¬


tions have no effect on car(arg), and the left-hand side of our subgoal reduces to
car(arg) itself, which is precisely the same as the right-hand side.

119 (GOAL SEQRULES (EQ $X $Y))


120 LAMBDA RELCHECK (EQ (CAR ARG))
121 RELCHECK = TRUE
122 EQSIMP = TRUE
123 EQSIMP = TRUE

We omit the trace for the proof of the second subgoal,


varsubst(compose(m2, m\), cdr(pat)) = cdr(arg).

This subgoal is simplified to


varsubst(m2, varsubst(m 1, cdr(pat))) = cdr(arg),

which is precisely our hypothesis (line 3).

142 (GOAL SEQRULES (EQ $X $Y))


143 EQSIMP = (EQ (VARSUBST (MATCH (VARSUBST (MATCH (CAR
PAT) (CAR ARG)) (CDR PAT)) (CDR ARG)) (VARSUBST (MATCH
(CAR PAT) (CAR ARG)) (CDR PAT))) (CDR ARG))
144 SUBSTCONS = (EQ (VARSUBST (MATCH (VARSUBST (MATCH
(CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG)) (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CDR PAT))) (CDR ARG))
145 EQSUBST = (EQ(VARSUBST (MATCH(VARSUBST (MATCH(CAR
PAT) (CAR ARG)) (CDR PAT)) (CDR ARG)) (VARSUBST (MATCH
(CAR PAT) (CAR ARG)) (CDR PAT))) (CDR ARG))
84 REASONING ABOUT PROGRAMS

146 (ASSERT ($F $X))


147 (RETURN ($F $X))
148 PROOFSWITCH = (EQ (VARSUBST (COMPOSE (MATCH (VARSUBST
(MATCH (CAR PAT) (CAR ARG)) (CDR PAT)) (CDR ARG)) (MATCH
(CAR PAT) (CAR ARG))) PAT) ARG)
149 (EQ (VARSUBST (COMPOSE (MATCH (VARSUBST (MATCH (CAR PAT)
(CAR ARG)) (CDR PAT)) (CDR ARG)) (MATCH (CAR PAT)
(CAR ARG))) PAT) ARG)

The proof is complete.

7.5. The FIND Program

Only a selection from the trace for the interesting verification condition of
FIND is presented here because the entire trace was more than 500 lines long.
We will focus on the use of the case analysis during the proof.
The antecedent hypotheses for this condition are
1 <M < F < NN

M<I
J<N
(STRIP (BAGA A I M— 1)) < (STRIP (BAGA A M NN))

(STRIP (BAGA A I N» < (STRIP (BAGA A N+l NN))

(STRIP (BAGA A 1 I— 1)) < R

R < (STRIP (BAGA A 1+J NN))

A[J] <R

R< A[I]

I<J

J-l <1+1
F< J-l

The theorem to be proved is

(STRIP (BAGA (EXCHANGE A I J) 1 J-l))

< (STRIP (BAGA (EXCHANGE A I J) (J—1)+1 NN).

This goal is simplified to


(IF J-l < I THEN (STRIP (BAGA A 1 J-l))

ELSE (STRIP (BAG (STRIP (BAGA A 1 I—1))


Appendix: Traces of Solutions 85

A [J ]
(STRIP (BAGA A 1+1 J-l)))))

< (IF J < I THEN (STPIP) (BAGA A J NN)

ELSE (STRIP (BAG A[I]

(STRIP (BAGA A J+l NN))

(STRIP (BAGA A J J-l))))):

1 (GOAL SINEQUALITIES (LTQ (IFTHENELSE (LT (SUBTRACT J 1) I)


(STRIP (BAGA A 1 (SUBTRACT J 1))) (STRIP (BAG (STRIP (BAGA A 1
(SUBTRACT I 1))) (ACCESS A J) (STRIP (BAGA A (PLUS 1 I) (SUBTRACT
J 1)))))) (IFTHENELSE (LTQ J I) (STRIP (BAGA A J NN)) (STRIP (BAG
(ACCESS A I) (STRIP (BAGA A (PLUS 1 J) NN)) (STRIP (BAGA A J
(SUBTRACT J 1))))))))
2 LAMBDA RELCHECK (LTQ (IFTHENELSE (LT (SUBTRACT J 1) I)
(STRIP (BAGA A 1 (SUBTRACT J 1))) (STRIP (BAG (STRIP (BAGA A 1
(SUBTRACT I 1))) (ACCESS A J) (STRIP (BAGA A (PLUS 1 I)
(SUBTRACT J 1)))))) (IFTHENELSE (LTQ J I) (STRIP (BAGA A J NN)
(STRIP (BAG (ACCESS A I) (STRIP (BAGA A (PLUS 1 J) NN)) (STRIP
(BAGA A J (SUBTRACT J 1)))))))
3 LAMBDA INEQIFTHENELSE (LTQ (IFTHENELSE (LT (SUBTRACT J 1)
I) (STRIP (BAGA A 1 (SUBTRACT J 1))) (STRIP (BAG (STRIP (BAGA
A 1 (SUBTRACT I 1))) (ACCESS A J) (STRIP (BAGA A (PLUS 1 I)
(SUBTRACT J 1)))))) (IFTHENELSE (LTQ J I) (STRIP (BAGA A J NN))
(STRIP (BAG (ACCESS A I) (STRIP (BAGA A (PLUS 1 J) NN)) (STRIP
(BAGA A J (SUBTRACT J 1)))))))
4 (ASSERT $X WRT SVERICON)

Since the left side of the goal has an IF-THEN-ELSE form, it causes the rule
INEQIFTHENELSE to be applied. This rule sets VERICON to be a new lower
context and asserts
J-l <1

with respect to the new VERICON. This question triggers off a demon:

5 (ASSERT (LTQ (PLUS 1 $X) $Y) WRT SVERICON)

The new assertion is


(J-l)Fl <1.

The new assertion triggers off another demon, which makes still another asser¬
tion with respect to VERICON:

6 (ASSERT (LTQ $W $Y) WRT SVERICON)


86 REASONING ABOUT PROGRAMS

This new assertion is

J<I.

(Later in the proof, another context will be established; J-l < I will be denied
with respect to the new context.)
The THEN clause of the IF-THEN-ELSE expression must now be proved less
than or equal to the right side of the goal:

7 (GOAL SINEQUALITIES ($F (TUPLE $$W1 $Y $$W2)) WRT SVERICON)

This goal is attempted with respect to the new context VERICON. In other
words, we are trying to prove

(STRIP (BAGA A 1 J-l))

< (IF J < I THEN (STRIP (BAGA A J NN))

ELSE (STRIP (BAG A[I]

(STRIP (BAGA A J+l NN))

(STRIP (BAGA A J J-l)))))

with respect to the context in which J < I has been asserted:

8 LAMBDA RELCHECK (LTQ (STRIP (BAGA A 1 (SUBTRACT J 1)))


(IFTHENELSE (LTQ J I) (STRIP (BAGA A J NN)) (STRIP (BAG
(ACCESS A I) (STRIP (BAGA A (PLUS 1 J) NN)) (STRIP (BAGA
A J (SUBTRACT J 1)))))))
9 LAMBDA INEQIFTHENELSE (LTQ (STRIP (BAGA A 1 (SUBTRACT J
1))) (IFTHENELSE (LTQ J 1) (STRIP (BAGA A J NN)) (STRIP (BAG
(ACCESS A I) (STRIP (BAGA A (PLUS 1 J) NN)) (STRIP (BAGA A J
(SUBTRACT J 1)))))))

Since the right side of the inequality is still in IF-THEN-ELSE form, the rule
INEQIFTHENELSE applies again. A new context VERICON, even lower than
the last, is established, and the (redundant) statement

J<I

is asserted with respect to the new context:

10 (ASSERT $X WRT SVERICON)

A new goal is established with respect to the new context.

1 1 (GOAL SINEQUALITIES ($F (TUPLE $$W1 $Y $$W2)) WRT SVERICON)


Appendix: Traces of Solutions 87

The new goal is

(STRIP (BAGA A 1 J—1)) < (STRIP (BAGA A J NN))

12 LAMBDA RELCHECK (LTQ (STRIP (BAGA A 1 (SUBTRACT J 1)))


(STRIP (BAGA A J NN)))
13 LAMBDA PROOFSIMP (LTQ (STRIP (BAGA A 1 (SUBTRACT J 1)))
(STRIP (BAGA A J NN)))
14 LAMBDA ARGSIMP (LTQ (STRIP (BAGA A 1 (SUBTRACT J 1)))
(STRIP (BAGA A J NN)))
15 LAMBDA SIMPONE (TUPLE (STRIP (BAGA A 1 (SUBTRACT J
1))) (STRIP (BAGA A J NN)))

The simplifier is invoked. We will omit some steps from the trace here and
mention only that the rule BAGALOWERPLUS played an important part in the
simplification of the second element of the tuple.

90 SIMPONE = (TUPLE (STRIP (BAGA A 1 (SUBTRACT J 1))) (STRIP


(BAG (STRIP (BAGA A (PLUS 1 J) NN)) (ACCESS A J))))
91 ARGSIMP = (LTQ (STRIP (BAGA A 1 (SUBTRACT J 1))) (STRIP
(BAG (STRIP (BAGA A (PLUS 1 J) NN)) (ACCESS A J))))
92 (GOAL SGOALCLASSl $X)

The simplified goal is

(STRIP (BAGA A 1 J-l))

< (STRIP (BAG (STRIP (BAGA A J+l NN)) A[J] )):

93 LAMBDA RELCHECK (LTQ (STRIP (BAGA A 1 (SUBTRACT J I)))


(STRIP (BAG (STRIP (BAGA A (PLUS 1 J) NN)) (ACCESS A J))))
94 LAMBDA INEQSTRIPBAG (LTQ (STRIP (BAGA A 1 (SUBTRACT
J 1))) (STRIP (BAG (STRIP (BAGA A (PLUS 1 J) NN)) (ACCESS A
J))))

INEQSTRIPBAG breaks up the goal into two subgoals. The first of these goals is

(STRIP (BAGA A 1 J-l)) < (STRIP (BAGA A J+l NN)):

95 (GOAL SINEQUALITIES ($F (TUPLE $$W $X $$Z)))


96 LAMBDA RELCHECK (LTQ (STRIP (BAGA A 1 (SUBTRACT J 1)))
(STRIP (BAGA A (PLUS 1 J) NN)))

The rule INEQSTRIPSTRIP is applicable to this goal:

103 LAMBDA INEQSTRIPSTRIP (LTQ (STRIP (BAGA A 1 (SUBTRACT


J 1))) (STRIP (BAGA A (PLUS 1 J) NN)))
REASONING ABOUT PROGRAMS
88

Since it is known that


(STRIP (BAGA A 1 1-1)) < (STRIP (BAGA A J+l NN)),

and, in this context, J < I, INEQSTRIPSTRIP succeeds. The other subgoal to be


proved is
(STRIP (BAGA A 1 J — 1)) < (STRIP (BAG A[ J])):
143 (GOAL SINEQUALITIES ($F (TUPLE $$W (STRIP (BAG $$Y))
$$Z)))
144 LAMBDA RELCHECK (LTQ (STRIP (BAGA A 1 (SUBTRACT
J 1))) (STRIP (BAG (ACCESS A J))))
INEQSTRIPBAG applies again, splitting this goal into two subgoals, one of
which is trivial.

145 LAMBDA INEQSTRIPBAG (LTQ (STRIP (BAGA A 1


(SUBTRACT J 1))) (STRIP (BAG (ACCESS A J))))
146 (GOAL SINEQUALITIES ($F (TUPLE $$W $X $$Z)))

The nontrivial goal is


(STRIP (BAGA A 1 J-l)) < A[J] :

147 LAMBDA RELCHECK (LTQ (STRIP (BAGA A 1


(SUBTRACT J 1))) (ACCESS A J))

This goal invokes the rule INEQSTRIPTRAN. We will examine the operation
of this rule in detail:

1 54 LAMBDA INEQSTRIPTRAN (LTQ (STRIP (BAGA A 1


(SUBTRACT J 1))) (ACCESS A J))
155 (EXISTS ($F (TUPLE (STRIP (BAGA SARNAME SLOWER
"HJPPER)) «-D)))

The rule finds the hypothesis

(STRIP (BAGA A 1 1-1)) < R.

It tests if this relation is appropriate:

1 56 (GOAL SDEDUCE (AND (LTQ SLOWER $L) (LTQ $M


SUPPER) (LTQ $D $C»)
157 LAMBDA RELCHECK (AND (LTQ 1 1) (LTQ (SUBTRACT
J 1) (SUBTRACT I 1)) (LTQ R (ACCESS A J)))

The system is testing whether the array segment between 1 and I—1 includes the
Appendix: Traces of Solutions 89

segment between 1 and J—1, and also whether R< A[J] :


158 LAMBDA ANDSPLIT (AND (LTQ 1 1) (LTQ (SUBTRACT J 1)
(SUBTRACT I 1)) (LTQ R (ACCESS A J)))
159 (GOAL SGOALCLASS $X)
160 LAMBDA RELCHECK (LTQ 1 1)
161 RELCHECK = TRUE
162 (GOAL SGOALCLASS (AND $$Y))
163 LAMBDA RELCHECK (AND (LTQ (SUBTRACT J 1)
(SUBTRACT I 1)) (LTQ R (ACCESS A J)))
164 LAMBDA ANDSPLIT (AND (LTQ (SUBTRACT J 1) (SUBTRACT
I 1)) (LTQ R (ACCESS A J)))
165 (GOAL SGOALCLASS $X)
166 LAMBDA RELCHECK(LTQ(SUBTRACT J 1) (SUBTRACT
I D)
167 RELCHECK = TRUE
168 (GOAL SGOALCLASS (AND $$Y))
169 LAMBDA RELCHECK (AND (LTQ R (ACCESS A J)))
1 70 LAMBDA ANDSPLIT (AND (LTQ R (ACCESS A J)))
171 (GOAL SGOALCLASS $X)
172 LAMBDA RELCHECK (LTQ R (ACCESS A J))
173 RELCHECK = TRUE
1 74 (GOAL SGOALCLASS (AND $$Y))
175 ANDSPLIT = (AND)
176 ANDSPLIT = (AND)
177 ANDSPLIT = (AND)

The tests prove to be successful, and INEQSTRIPTRAN returns

178 INEQSTRIPTRAN - (AND)

The trivial subgoal is achieved:

179 (GOAL SINEQUALITIES ($F (TUPLE $$W (STRIP (BAG $$Y))


$$Z)))
180 LAMBDA RELCHECK (LTQ (STRIP (BAGA A 1 (SUBTRACT
J 1))) (STRIP (BAG)))
181 RELCHECK=TRUE

The call to INEQSTRIPBAG from line 145 returns successfully:

182 INEQSTRIPBAG = TRUE

The call to INEQSTRIPBAG from line 94 also returns:

183 INEQSTRIPBAG = TRUE


90 REASONING ABOUT PROGRAMS

Thus, the goal established in line 11 has been successfully proved:

184 PROOFSIMP = TRUE

That goal was established by the rule INEQIFTHENELSE. This rule asserted
J < I with respect to a lower context and set up the goal with respect to that
context. The rule now attempts to deny J < I with respect to another context
and to establish a new goal with respect to the new context.

185 (DENY $X WRT SVERICON)

However, J < I was also asserted with respect to a higher context in line 6.
Therefore, denying J < I contradicts this assertion, causing the denial to fail.
Since the situation is contradictory and could not arise, it is unnecessary to
achieve the goal, and the call to INEQIFTHENELSE from line 9 returns
successfully:

186 (RETURN (SUCCESS (TUPLE WITH INEQIFTHENELSE)))


1 87 INEQIFTHENELSE = (SUCCESS (TUPLE WITH INEQIFTHENELSE))

The goal established in line 7 has been achieved. This goal was set up by an
earlier call to INEQIFTHENELSE (line 3) with respect to a context in which
J—1 < I was asserted (line 4). It is now necessary to set up a new goal with
respect to a new context; in this new context, J—1 < I is denied:

188 (DENY $X WRT SVERICON)

This denial activates a demon that denies

J<I:

189 LAMBDA TRYALL (TUPLE (TUPLE PLUSEMPTY PLUSSINGLE


PLUSZERO PLUSPLUS PLUSMINUS PLUSDIFFERENCE
PLUSCOMBINE PLUSNUMBER) (PLUS 1 I (MINUS 1)))
190 (GOAL SGOALCLASS1 SGOALl)
191 LAMBDA PLUSMINUS (PLUS 1 I (MINUS 1))
192 PLUSMINUS = (PLUS 1)
193 (GOAL SGOALCLASS1 SGOALl)
194 LAMBDA PLUSSINGLE (PLUS I)
195 PLUSSINGLE = I
196 (GOAL SGOALCLASS1 SGOALl)
197 (RETURN SGOALl)
198 TRYALL=I
199 (DENY (LTQ $W SRTSIDE) WRT SVERICON)
Appendix: Traces of Solutions 91

The new goal

(STRIP (BAG (STRIP (BAGA A 1 1-1)

A [J ]

(STRIP (BAGA A 1+1 J-l))))


< (IF J < I THEN (STRIP (BAGA A J NN))
ELSE (STRIP (BAG A[I]

(STRIP (BAGA A J+l NN))


(STRIP (BAGA A J J-l))))

is established with respect to the new context:

200 (GOAL SINEQUALITIES ($F (TUPLE $$W1 $Z $$W2)) WRT $VERICON)


201 LAMBDA RELCHECK (LTQ (STRIP (BAG (STRIP (BAGA A 1
(SUBTRACT I 1))) (ACCESS A J) (STRIP (BAGA A (PLUS 1 I)
(SUBTRACT J 1))))) (IFTHENELSE (LTQ J I) (STRIP (BAGA A J NN))
(STRIP (BAG (ACCESS A I) (STRIP (BAGA A (PLUS 1 J) NN)) (STRIP
(BAGA A J (SUBTRACT J 1)))))))

INEQIFTHENELSE is invoked because the right-side of the goal is of the form


IF-THEN-ELSE.

202 LAMBDA INEQIFTHENELSE (LTQ (STRIP (BAG (STRIP (BAGA A 1


(SUBTRACT I 1))) (ACCESS A J) (STRIP (BAGA A (PLUS 1 I)
(SUBTRACT J 1))))) (IFTHENELSE (LTQ J I) (STRIP (BAGA A J NN))
(STRIP (BAG (ACCESS A I) (STRIP (BAGA A (PLUS 1 J) NN)) (STRIP
(BAGA A J (SUBTRACT J 1)))))))

Again the rule creates two contexts: In one context J < I is asserted, and in the
other J < I is denied. However, since J < I was denied in a higher context (line
199), the assertion of J < I fails; this contradictory case can safely be ignored,
and attention focuses on the second context:

204 (DENY $X WRT SVERICON)

The goal is established using the ELSE clause of the previous goal,

(STRIP (BAG (STRIP (BAGA A 1 1-1))

A[J]
(STRIP (BAGA A 1+1 J-l))))

< (STRIP (BAG A[I]


)2 REASONING ABOUT PROGRAMS

(STRIP (BAGA A J+l NN))

(STRIP (BAGA A J J-l))))):

205 (GOAL $INEQUALITIES ($F (TUPLE $$W1 $Z $$W2)) WRT


SVERICON)
206 LAMBDA RELCHECK (LTQ (STRIP (BAG (STRIP (BAGA A 1
(SUBTRACT I 1))) (ACCESS A J) (STRIP (BAGA A (PLUS 1 I)
(SUBTRACT J 1))))) (STRIP (BAG (ACCESS A I) (STRIP (BAGA A
(PLUS 1 J) NN)) (STRIP (BAGA A J (SUBTRACT J 1))))))
207 LAMBDA INEQSTRIPBAG (LTQ (STRIP (BAG (STRIP (BAGA A 1
(SUBTRACT I 1))) (ACCESS A J) (STRIP (BAGA A (PLUS 1 I)
(SUBTRACT J 1))))) (STRIP (BAG (ACCESS AI) (STRIP (BAGA A
(PLUS 1 J) NN)) (STRIP (BAGA A J (SUBTRACT J 1))))))

The proof from this point will only be summarized, since it is lengthy but
uneventful. The goal is divided into nine subgoals by successive applications
of INEQSTRIPBAG. Each of these goals turns out to be easily proved, and the
proof ends successfully.

558 INEQIFTHENELSE = TRUE


559 INEQIFTHENELSE = TRUE
560
TRUE

Acknowledgments
The work on program verification was done in close collaboration with
Bernie Elspas. The work on QA4 was done with Jeff Rulifson and Jan Derksen.
Irene Greif wrote the first version of the simplifier and participated in the con¬
ceptualization of the pattern matcher and unification proofs. Jeff Rulifson en¬
couraged us to write this paper and suggested its format. Rich Fikes has
helped with design modification and debugging of QA4. Bert Raphael read the
manuscript and suggested many improvements. Tony Hoare read and com¬
mented on the entire final draft; much of his advice was incorporated in the
published paper. This work has benefitted from our conversations with Cordell
Green, Peter Neumann, Larry Robinson, Earl Sacerdoti, Rene Reboh, Mark
Stickel, Steve Crocker, and John McCarthy. Many members of the Artificial
Intelligence Center and the Computer Science Group at SRI helped with support
and criticism.
The research reported herein was supported in part by the National Science
Foundation under Grant GJ-36146 and in part by the Advanced Research Pro¬
jects Agency under Contract DAHC04-72-C-0008 and by the National Aeronau¬
tics Space Administration under Contract NASW-2086.
Chapter 2
Logical Analysis of Programs
Shmuel Katz and Zohar Manna

1. Introduction

In recent years considerable effort has been devoted to the goal of


proving (or “verifying”) that a given computer program is partially
correct—i.e., that if the program terminates, it satisfies some user-
provided input/output specification. Floyd (1967) suggested a
method for proving partial correctness of flowchart programs which
has been shown amenable to mechanization [e.g., see King (1969),
Deutsch (1973), Waldinger and Levitt (1974), Good et al. (1975),
and Suzuki (1975)]. However, most existing implementations are in¬
complete in that they are not oriented toward incorrect programs:
their declared goal is to prove that a correct program really is correct.
If a program is not verified, it is unclear whether the program is
erroneous or whether a proper proof has simply not been discovered.
We suggest conducting logical analysis of programs using “in¬
variants” which express the actual relationships among the variables
of the program. These “invariants” differ from Floyd’s programmer-
supplied “assertions” in that they are generated directly from the
program text. In our conception, the invariants are independent of
the output specification of the program and reflect what is actually
happening during the computation, as opposed to what is supposed
to be happening. Thus our invariants can be used either to verify the
program with respect to its specifications or to prove that the pro¬
gram cannot be verified (i.e., contains an error). In addition, these
This is a revised version of a previously published article by the same name which ap¬
peared in Communications of the ACM. vol. 19, no. 4, pp. 188-206. Copyright 1976 by
Association for Computing Machinery, Inc., reprinted by permission.

93
94 LOGICAL ANALYSIS OF PROGRAMS

invariants enable us to integrate proofs of termination and non¬


termination into our logical analysis. Invariants can also be used
to debug an incorrect program, i.e., to diagnose the errors and to
modify the program.
The need to relieve the user of the task of supplying fully detailed
assertions (or invariants) has been widely recognized. We devote
a large part of the paper to presenting techniques for the systematic
generation of invariants.
Ultimately, we envision a system based on these techniques
which would automatically generate the straightforward invariants.
The programmer would still be expected to supply suggestions for
those invariants requiring more insight into the logic of the program.
Whenever new invariants had been produced, all invariants generated
up to that point would be used to check simultaneously for correct¬
ness or incorrectness. If correctness (including termination) has been
established, an attempt may be made to optimize the program
through a fundamental revision of the program statements, based on
the invariants. If incorrectness has been established, an attempt is
made to automatically debug the program, i.e., to diagnose and
correct the errors in a systematic manner, again using the invariants.
If neither correctness nor incorrectness can be established, we
attempt to generate additional invariants and repeat the process.
Assertions (“comments”) supplied by the programmer may or may
not be correct, and therefore are considered to be just promising can¬
didate invariants. As a last resort, it may nevertheless be possible to
take a more radical approach and use the invariants for modifying
the program so that correctness is guaranteed, taking the calculated
risk of modifying an already correct program.
In the following sections we first present the techniques of auto¬
matic invariant generation, an algorithmic approach in Section 3, and
a heuristic approach in Section 4. Then in Section 5 we describe the
applications of the invariants for proving correctness (including
termination) or incorrectness. In Section 6 we outline the practical
implications of the invariants for automatic debugging. The Con¬
clusion includes some bibliographical remarks.

2. Preliminaries

The programs treated in this paper are written in a simple flow¬


chart language with standard arithmetic operators over the domain of
Preliminaries 95

real or integer numbers. We assume a flowchart program P with


input variables x, which do not change during execution, and pro¬
gram variables y, which do change during execution and whose
final values constitute the output of the program. In addition, we
are given an input predicate 0(F>, which restricts the legal input
values, and an output predicate \p(x, y), which indicates the desired
relationship between the input and output values.
For convenience we consider blocked programs. That is, we
assume the program is divisible into (possibly nested) “blocks”
in such a way that every block has at most one top-level loop
(in addition to possible lower-level loops which are already contained
in inner blocks). The blocks we consider have one entrance and may
have many exists. Every “structured program” can be decomposed
into such blocks.
The block structure allows us to treat the program by first con¬
sidering inner blocks (ignoring momentarily that they are included
in outer blocks) and then working outwards. Thus, for each block we
can consider its top-level loop using information we have obtained
from the inner blocks.
The top-level loop of a block can contain several branches, but all
paths around the loop must have at least one common point. For
each loop we will choose one such point as the cutpoint of the loop.
We use counters attached to each block containing a loop as an
essential tool in our techniques. Since each loop has a unique cut-
point, we associate a counter with the cutpoint of the loop. The
counter is initialized before entering the block so that its value
is 0 upon first reaching the cutpoint, and is incremented by 1
exactly once somewhere along the loop before returning to the
cutpoint. There are many locations where the initialization of the
counters could be done. The two extreme cases are of special inter¬
est: (1) the counter is initialized only once, at the beginning of the
program (a “global” initialization, parametrizing the total number
of times the cutpoint is reached), or (2) the counter is initialized
just before entering its block (a “local” initialization, indicating the
number of executions of the corresponding loop since the most
recent entrance to the block). In the continuation, we will assume
a local initialization of counters, since our experience has been that
this is generally the most convenient choice.
The counters will play a crucial role both for generating invariants
96 LOGICAL ANALYSIS OF PROGRAMS

and for proving termination. They will be used both to denote rela¬
tions among the number of times various paths have been executed
and to help express the values assumed by the program variables. It
should be noted that it is unnecessary to add the counters physically
to the body of the program. Their location can merely be indicated,
since their behavior is already fixed.
It is sometimes convenient to add auxiliary cutpoints at the en¬
trance and exit of a block. In addition, we always add a special cut-
point on each arc immediately preceding a HALT statement. Such
cutpoints will be called haltpoints of the program.
A typical situation is shown in Figure 2.1. There is an inner block
with cutpoint M and counter m, and an outer block with a cutpoint
TV and counter n. The outer block also has auxiliary cutpoints L and
K at the entrance and exit of the block, respectively.
Our first task is to attach an appropriate invariant qfx, y) to each
cutpoint i. We first define our terms.
A predicate qfx, y) is said to be an invariant at cutpoint i w.r.t.
fix) if for every input a such that f(a) is true, whenever we reach
point i with y = b, then qfa, b) is true. An invariant at i is thus
some statement about the variables which is true for the current
values of the variables each time i is reached during execution.
For a path a from cutpoint i to cutpoint /, we define Ra(x, y)
as the condition for the path a to be traversed, and ra(x, y) as
the transformation in the y values which occurs on path a. A set
S of points of a program P is said to be complete if, for each cut-
point i in S, all the cutpoints on any path from START to i are
also in S. For example, if L is the entrance to the program in Figure
2T, {L}, {L, M, TV} and {L, M, TV, K} are all complete sets of cut-
points; {I, TV} is not.
We shall use the following sufficient condition [proven in Manna
(1969)] for showing that assertions (“candidate invariants”) are
actually invariants.

Lemma A. Let S be a complete set of cutpoints of a program P.


Assertions {qfx, y) \ i € S} will be a set of invariants for P w.r.t.
0 if

(a) for every path a from the START statement to a cutpoint


Preliminaries 97

v
i-1
i n <-0 i

Figure 2.1. Blocks, Cutpoints, and Counters.


98 LOGICAL ANALYSIS OF PROGRAMS

j (which does not contain any other cutpoint) 1 and

\/x[f(x)/\Ra(x) D qj(x,ra(x))],

(b) for every path a. from a outpoint i to a outpoint j (which


does not contain any other outpoint),
Vxfylqfx, y) A Rjx, y) D qfx, rjx, y))].

Consider, for example, the initial segment of a program shown in


Figure 2.2. Assertions qx (3c, y) and q2(x, y) will be invariants at cut-
points 1 and 2, respectively, if

(a) \/x[f(^) D q1 (x, g(T))l,


(b) \/x\/y[qx (3c, y) A (x, y) D qx (3c, fix, y))],
Vi\/y[(?i(xJ)A t(x,y)D q2(x, h(x, y))].

Intuitively, condition (a) implies that qx(x, y) holds the first time
the cutpoint 1 is reached, while the first formula of (b) states that if
qx(x, y) holds at the cutpoint, it is still true after the loop is execu¬
ted. Thus, by induction it follows that qx(x, y) holds whenever cut-
point 1 is reached, i.e., it is an invariant at 1. The second formula of
(b) states that if qx(x, y) holds at cutpoint 1, then q2(x, y) holds at
cutpoint 2. Thus, since qx(x, y) is an invariant at 1, q2(x, y) is an
invariant at 2.
Note thft the input predicate fix), which depends only on 3c
(variables not changed during execution), is automatically an in¬
variant of any cutpoint of the program, and does not need any
further justification.
Lemma A is slightly misleading, because it implies that a full-
fledged set of assertions is provided at a complete set of the cut-
points and that these are checked simultaneously. In practice,
the invariants will be added one after another until the needs of the
logical analysis have been met. At every stage of the invariant gen¬
erating process, a situation as in Figure 2.3 will apply for each block.
At cutpoints L, N, and M, invariants p(x, y), q(x, y), and s(3c, y),
respectively will already have been proven. However, we also will

1 Note that the y values are not defined at the START statement, and that they are
initialized by constants or functions of x along a path from START. Thus, Ra and r
for such a path are really only functions of x, and not of y.
Preliminaries 99

START
c
rx
1 inpu* specification i
d>(*)
i_I,_i

r- q(i)
i' ►
_'
assertion n—n*l •

'__ J

——f
y — Mx.y) y — f (x,y )
-f
~li
|-^ --“-I
, assertion
! q2(i.y) J

Figure 2.2. An Initial Segment of a Program.

have promising candidate invariants p'(x, y), q'(x, y) and s'(x, y),
which we have so far been unable to prove invariant. These can¬
didates could originate as comments given by the user or, as in the
case of s'(x, y), from the output specification, which we automatically
designate as a candidate at the haltpoints. As indicated in Section 4,
additional candidates may be generated during this process.
For a block of the form given in Figure 2.3, we concentrate on
developing invariants at cutpoint N on the loop. For the auxiliary
cutpoint M, the invariants are generated by “pushing forward” any
invariant obtained at N. Thus, if at any stage an invariant q(x, y)
has been established at N, we automatically can take as an invariant
at M any s(5c, y) satisfying

V A V y[q(x, y) A t(x, y) D s(x, h(x, y))].


To establish that a candidate q'(x, y) is actually an invariant at N,
100 LOGICAL ANALYSIS OF PROGRAMS

mvonont p(x , y)]

candidate p’(*,y)i
1 L
y- g(*.y)

I n -^o I
I_, J
invaricnt q(* ,y)]

candidate q'(*,y)
1-
t(i \_F

— M», yT~| y — Hl.y) ]


invariant S(x,y )
■a M
candidate s'(i.y)

Figure 2.3. A Block Containing a Single-Path Loop.

it follows from Lemma A that we must show

(i) V x V y[p(x, y)^q'(x, g(x, yf)]


and

(ii) VxV y[q(x, y) A q \x, y) A ~t(x, y) D q '(x, fix, y)) ].

It must be emphasized that special care should be taken in case of


failure in an attempt to establish that a candidate is an invariant. For
example, suppose that q1 and q2 are candidate invariants at the
cutpoint TV and that both q'x and q'2 satisfy condition (i). It is en-
tiiely possible that neither qx nor q2 satisfies condition (ii) individ¬
ually, but that q[ A q'2 does satisfy condition (ii), and therefore is
an invariant. This phenomenon-i.e., that it is impossible to show a
weak property but it is possible to show a stronger one—is typical
in mathematical proofs by induction. The explanation is that
although we must show a stronger property on the right of the
implication, we are also provided with a stronger inductive hypothesis
on the left.
In Sections 3 and 4 we present techniques for discovering invariants.
These techniques were originally designed with an automatic imple¬
mentation in mind. However, they are in fact also useful for finding
Generation of Invariants: Algorithmic Approach 101

invariants by humans. For simplicity of presentation, we consider


the single block of Figure 2.3. We will distinguish between two
general approaches to producing invariants:

(1) the algorithmic approach, in which we obtain guaranteed in¬


variants q(x, y) at N directly from the assignments and tests of
the loop (using also any already established invariants at L and
at N), and
(2) the heuristic approach, in which we obtain a new candidate
q\x, y) for an invariant at N from already established in¬
variants at N and old candidates which we have not yet been
able to prove to be invariants.

3. Generation of Invariants: Algorithmic Approach

We present first the algorithmic approach for generating invariants.


We distinguish between invariants derivable from the assignment
statements and ones based primarily on the test statements. The
input predicate 0(F) and the fact that a counter is always a non¬
negative integer will be used as “built-in” invariants whenever con¬
venient.

3.1. Generating invariants from assignment statements

We observe that assignment statements which are on the same path


through the loop must have been executed an identical number of
times whenever the cutpoint is reached. Thus the counter n ot the
cutpoint can be used to relate the variables iterated. We denote by
y(n) the value of y the (n + l)th time the cutpoint is reached since
the most recent entrance to the block (assuming a local initialization
of the counters). Thus y(0) indicates the value of y the first time the
cutpoint is reached.
We use a self-evident fact as the basis for generating invariants: for
3c such that 0(F) is true and for each path a around the loop, we have
Ra(x, yin - 1)) D yin) = rjx, y(n - 1)) for n> 1. (2.1)

That is, if values y{n - 1) occurred at the cutpoint, and a path a


around the loop is then followed [that is, R^ix, y(n — 1)) is true],
102 LOGICAL ANALYSIS OF PROGRAMS

then the next values of y at the outpoint [i.e., y(n)] will be the result
of applying ra to y(n - 1).
In practice, if there is only a single path around the loop such as in
the block of Figure 2.3, it usually suffices to ignore the path condi¬
tion Ra, and find invariants which satisfy the stronger condition
y(n) = ra(x,y(n — 1)) for n> 1. (2.2)
Considering (2.2) for each component of y, we have a set of
recurrence equations, one for each yj. We now attempt to express
as many as possible of these equations in iterative form, e.g., as

(a) yfn) = y}(n - 1) + gj(x, y(n - 1)) or


(b) yj(n)=yj(n - 1 )-g/(x, y(n - 1)).

Such forms are desirable because they can be solved to obtain

(a') yj(n)=yj(0) + 2 gj(x, y(i - 1)) or


i= 1

(b') yj(n) = y,(0>J^ gj(x, y(i - 1)).


i= i

There are two ways to obtain invariants at a cutpoint from equa¬


tions of the form (a') or (b'). First, it may be possible to express

2 gj(x,y(i-1)) or gj(x, y(i — 1))


1= 1 i= i

as only a function of x and n, not containing any elements of


y(i - 1). We then have an assertion which relatesyj(n), y7(0), x, and
n. Second, if tor two variables yt and yk there is a relation between

"_ n
2 Si(x,y(i- 1)) and 2 gk(x,y(i- 1)),
1=1 /=i

or between

I] 2i(x,y(i- 1)) and J] gk(x,y(i- 1)),


1=1 1=1
Generation of Invariants: Algorithmic Approach 103

then we can use this to connect yt(ji) and yh{ri). Once we have rela¬
tions which are true for all n > 0, with all variables in the form y^n),
we can simplify by replacing each yt{n) by jq, obtaining an in¬
variant which may still contain occurrences ofy,(0).
Whenever possible, known information from the entry invariant
p(x, y) may be used to obtain y(0). When the variables are initialized
immediately before entering the loop, p(x, y) will indicate the exact
values of y(0). However, even when this is not the case, p(x, y) may
often contain valuable information about y(0).
It is important to note that any predicate obtained as above,
say from (a') or (V), is not simply a candidate for an invariant, but
is actually an invariant. This is because substituting the correct initial
value in place of y(0) ensures that the relation obtained is true the
tirst time the cutpoint is reached, and the use of ra(x, y) in obtaining
the recurrence equations ensures that the relation is true at sub¬
sequent times the cutpoint is reached.
Recall that the transformation from the recurrence equation (2.1)
to (2.2) was made under the assumption that there was a single path
around the loop as in Figure 2.3. The above discussion can easily
be extended to the case of a loop with several possible paths—by
using if-then-else expressions. For example, considering the loop of
Figure 2.4, with two paths around the loop, Equation (2.1) expands
to two equations:

~h(x, v(«-l)) A t2(x,y(n-1)) D y(n) = fx(x, y(n-\)),

~h(x,y(n-1)) A ~t2(x,y(n-1)) D y(n) = f2(x, y(n-1)).

These can be combined into one statement, as

y{n - 1)) D [ if t2(x, y(n — 1))


then y(n) = fx (x, y(n -1))
else y(n) = f2 (x, y(n - 1)) ].

Since tx{x, y) controls the exit from the block, and does not
affect the choice between the two paths around the loop, it can
be ignored, as before, giving the stronger condition

if t2(x, y(n-1)) theny(n)=f1 (x, y(n-1))


else y(n)=f2 Qc, y(n-1)). (2.3)
104 LOGICAL ANALYSIS OF PROGRAMS

invOr'iant p(*,y)
candidate p' (* ,y)! L
11
y — q( * .y)

n —0

invarlont q(*,y ) j
condidate q' (i , y) i.r
T-\ MT'yW r n-«-n ♦

T A _ F
y — h(*,y) \ >2 (»■ y)y

>1- i M t
>J } y — fi(*.y) j y— G i *. y) ii
i
±
Figure 2.4. A Block Containing a Loop with Two Paths.

Equations of this form can then be put in iterative form, and treated
just like equations of form (2.2).

3.2. Generating invariants from tests

So far we have concentrated on generating invariants from assign¬


ment statements, and the tests did not play a major role. Now we
will show how the tests can aid in extracting additional invariants
from the loop.
Suppose the block has the paths oq, a2, ak (fc >\) from the
cutpoint N around the loop back to N. Again we shall use an obvious
fact: whenever N is reached during execution, either the block has
just been entered, or control was previously at N and one of the
paths oq, ..., ak was followed. Letting n be the counter of the block,
this can be written more precisely as

« = 0 V [^ai (x, y(n—\)) V Ra2(x, y(«—1)) V •••


VAafe(x,y(n-1))]. (2.4)

This claim is clearly always true at N. By expressing y(n — 1)


Generation of Invariants: Algorithmic Approach 105

in terms of y(n)—using the recurrence equations given by (2.1)—


and adding known information about y(0), we can often simplify
(2.4). Again, if we obtain relations which are true for all n > 0, and
all variables are expressed as y,•(«), we can remove the parameter n to
obtain an invariant.
We demonstrate some of the above techniques on a program. Note
that we do not claim that this program is correct.

Example A. Program2 A of Figure 2.5 is intended to divide*! by


*2 within tolerance *3, where xlt x2, and x3 are real numbers
satisfying 0 < *x < *2 and 0 <*3 ; the final value ofy4 is supposed
to satisfy x1/x2 — *3 < y4 <^x1/x2 at the haltpoint H. For clarity
we have explicitly added the counter n to the program. There are
two paths from the cutpoint N around the loop and back to N: the
right path following the T-branch from the test x1 <yx + y2, and
the left path following the corresponding F-branch. By using (2.1)
we have for each path:

Right path:

[.h3 («— 1 )>*3 A < y1 (,n-1 )+y2 (n— 1)]

^ [yi(n)=y1(n-\) A

y2(n)=y2(n-\)/2 A

yz(n)=yz{n-\)!2 A

y*(n)=y4(n-l)].

Left path:

iy3(«— 1 )2>x3 Ax1>y1 (n—\)+y2(n— 1)]

D [ v 1 {n) =y1(n - 1) + y2 (n - 1) A
y2(n) =y2(n - 1 )/2 A

ys(n)= y3(n - l)/2 A

y4(n) = y4(n - 1 )+y3(n- l)/2].

This program is based on Wensley’s (1958) division algorithm. Note that we use a
vector assignment notation, where, for example, (y1} y4) <- (yx +V2’V4+T3/2) means that
y 1 *~y 1 + y2 and*4 <~y4 + y3/2 simultaneously.
106 LOGICAL ANALYSIS OF PROGRAMS

START

r
1 input specification i
i 0 « < *2 A 0 < *3 I
J

1
iy, . y2 ’ ^3 '*4 ) (0 .*2 . 1 >0 ) j

/X/
i n —0 i

N
,__
*3 ) i-"•*-1

output
T
specification
v y3S

:7 .
i n —n »I i
_i

*i< y\*y2
X
y4 s K, /*2
L
(y, .y4^Wi+y2.y4<-y3/2)
IX
HALT

(y2.y;) — (y2/2,y3/2)

Figure 2.5. Program A. Real Division within Tolerance.

Since the assignments to y2 and y3 are not affected by which


path is used, we may ignore the path conditions, obtaining
y2(n)=y2{n- l)/2 A y3(n) = y3(n - l)/2.
Both of these are in the iterative form (b) and may be solved to
yield

y2(n) =L2(0)-A y3(n) =y3(0) •


i=i 2 i=i 2
Since it is clear that y2(0) = x2 and y3(0) = 1 at N and that
n"=1 1/2 = 1/2" , we have (dropping the parameter n) the invariants

(Al) y2 =x2/2n at N,
Generation of Invariants: Algorithmic Approach 107

and

(A2) y 3 = 1/2" at N.

These may be combined to yield the additional invariant

(A3) y2 = x2-y3 at N.

For variables yx and y4, we apply the techniques used to obtain


Equation (2.3): Ignoring the exit test y3 < x3 and expressing the
effect of the branching by using if-then-else, the resulting recurrence
relations are

Ti(«) = if *i < y4(n - \)+y2(n - 1)


then y1{n — 1)
and else y1(n - 1) + y2(n - 1),
y4(n) = if xt < yx{n - 1) +y2(n - 1)
then y4(n — 1)
else y4(n — 1) +y3(n - l)/2.
Both of these are in iterative form, and we can obtain the sum¬
mations

Ti(«) =Ti(0) + 2 tif*i <TiO' - 0 +T2(3 - 1)


1=1 thenO
elsey2(/ - 1)]

y4(n) =y ( ) +
4 0 2 tif <yi(i - O +y2(i -
X1 0

!=1 then 0
else y O' - l)/2].
3

We will use the invariant (A3), that y = x2 *y at N, in order


2 3

to bring the two summations to an identical form. Substituting


x2 •y 30- ) for y
1 - ) in the else part of the equation foryx(n),
20 1

factoring out x2, and dividing by 2 inside the summation and multi¬
plying by outside, we obtain
2

Ti(n) = yi(0) + 2x2 2 [if^i iO ~ 1) + y2^ — 1)


1 then 0
else y30 - l)/2].
108 LOGICAL ANALYSIS OF PROGRAMS

We have expressed yx(n) and y4(ri) in terms of the same summation,


which thus can be used to connect these two variables. Substituting
Px(0) = 0 andy4(0) = 0, we obtain
n

yx(n) = 2x2 • 2 fifxi <yi(i - 0 +L20' - 1)


1=1
then 0
else y3(i - l)/2]
= 2x2 • y4(n).

Thus we have the invariant

(A4) yx = 2x2-L4 atA^-

We now turn to Equation (2.4), using the tests of the loop to gen¬
erate additional invariants. We have the fact
n = 0
V [y3(n - 1) > x3 A *i < y±(n - 1) +y2(n - 1)1 (right path)
V [y3(n - 1) > x3 /\x1 > y1(n — 1) + y2(n - 1)] (left path).
For each path, we now use the equations for y(n) obtained from
(2.1) to express y(n — 1) in terms of y{ri). For the right path we
will use the fact that yx(n) = yx{n - 1), y2(n) = y2(n - l)/2, and
y3(w) = y3(n — l)/2, while for the left path we will use the fact that
Ti(«) = yi(n - 1) + y2(n - 1) and y3(n) = y3(n — l)/2. These
substitutions will yield.

n~ 0

V [2y3(n)>x3 A Xj <yx (n) + 2y2{n)] (right path)

V [2y3{n)>x3 A x1>y1(n)] (left path).


Removing the parametrization in terms of n, and separating the term
involving y3, we have the two new invariants at N,
[n = 0 V 2y3 >x3 ] A [n = 0 V^ <yx + 2y2 Vxx>yx ].

To obtain stronger invariants, we can check whether the n = 0


case is subsumed in the other alternatives. The left conjunct may
not be so reduced, and we have the invariant

(A5) n = 0 V 2y3 > x3 at N.


Generation of Invariants: Heuristic Approach 109

The n = 0 possibility in the right conjunct can easily be seen to be


included in the other possibilities, since >q(0) = 0 and x1 > 0 imply
that Xi >>>! (0). Thus we have the invariant

(A6) x1 <y1 + 2y2 V>yx atN.

Note that invariant (A6) is a disjunction of the form pVq. This


disjunction actually reflects the effect of taking the right path or
the left path, respectively, around the loop. □

4. Generation of Invariants: Heuristic Approach

We now describe several heuristic techniques which suggest


promising candidate invariants. There is no guarantee that the can¬
didates produced are actually invariants, and they must be checked
(using Lemma A).
It is important to notice that when we are unable to establish that
a candidate is an invariant, it should be saved to retry later. In the
meantime, additional invariants or new candidates may have been
developed that, along with the original candidate, satisfy Lemma
A.
It should be clear that before an automatic system for generating
invariants is practical, strong guidance must be provided for the
application of the following rules, since, applied blindly, they
could result in too many irrelevant candidates. Here we merely state
some of the various possibilities in order to give the flavor of this
approach.

4.1. Disjunct Elimination

Whenever we have established an invariant at a cutpoint i which is


a disjunction of the form

Pi V P2 V *" V pk (k> 2),

we try to see whether any subdisjunction (in particular, each pj


alone) is itself an invariant at i. In the previous section, we actually
used this approach when we eliminated the n = 0 alternative to
obtain the invariant (A6).
110 LOGICAL ANALYSIS OF PROGRAMS

4.2. Conjunct Elimination

Suppose we have at i a candidate which is a conjunction of the


form
Pi Ap2 A ••• f\pk (k > 2),
and we have failed to prove that it is an invariant at i. One
natural heuristic is to try a subconjunction (in particular, each p,
alone) as a “new” candidate. Note that the failure to prove pt A
p2 A ••• A pk an invariant says nothing about whether its subconjunc¬
tions are invariants. Theoretically, any nonempty subconjunction is a
legitimate candidate and should be checked independently.
For the next three heuristics, we refer back to Figure 2.3.

4.3. Pushing Candidates Backward

Let us assume that p(x, y) is an established invariant at L and


q'(x, y) is a candidate invariant at N. If the inductive step around the
loop has been shown to establish q'(x, y) at N, then the only dif¬
ficulty could be that p(x, y) did not imply q'(x, g(x, y)). We then try
p'(x,y): q'(x,g(x,y))
as a new candidate at L. This will “fix” the problem with q'(x, y),
but of course we must now prove p\x, y) an invariant at L. Note
that in any case p'(x, y) must be an invariant at L if we are to suc¬
ceed in showing that q'(x, y) is an invariant at N and in this sense is
the “weakest” possible precondition for the base case of the induc¬
tion for q\x, y).
A similar technique can also be used to generate candidates at N:
Let us assume that q(x, y) is an established invariant at N and
s'(x, y) is a candidate invariant at M. Since s'(x, y) is reached only
from N, the reason we were not able to prove it an invariant must be
that q(x, y)/\t(x, y) could not be shown to imply s'(x, h(x, J7)). Thus
we would like to find a candidate q'(x, y) at N such that
[q(x, y) A q'(x, y) A t(x, y)] D s'(x, h(x, y)). (2.5)

Among the many possible choices of q\x, y) which satisfy this


condition are
q'(x, y): t(x, y) D s'(x, h(x, y)),
Generation of Invariants: Heuristic Approach 111

or q'(x, y): s'{x, h(x, y)).

This first possibility is, just as above, the weakest possible assertion
which satisfies (2.5), while the second is the strongest possible. A
very useful third alternative to the above suggestions takes advantage
of the transitivity of certain inequality or equality relations. For
example, if we need a q such that

q /\B<C A A<C
where A, B, and C are any terms, then the relation A < B is a natural
candidate for q'.
Any candidate for q\x, y) obtained from Equation (2.5) must be
checked. Unfortunately, there are no clear-cut criteria for finding a
q\x, y) which will be easy to prove. If we fail to show some candi¬
date q'(x, y) an invariant at TV, some weaker version may nevertheless
succeed. On the other hand, because of the “induction phenomenon”,
mentioned after Lemma A, it is quite possible that a stronger candi¬
date q'(x, y) actually could be more easily proven.

4.4. Pushing Invariants Forward

Assuming that p(x, y) is an established invariant at L, a straight¬


forward heuristic is to try to find a candidate q'(x, y) at TV such that
p(x, y) D q'{x, g(x, y)).

The above equation ensures that the first time TV is reached, q'ix, y)
is true. Of course, in order to complete the proof that q'ix, y) is an
invariant, the corresponding formula for the path around the loop
must be considered.
Note that immediately after every assignment yt «-/(x, y) where
fix, y) does not include yt itself, we know that yt = fix, y) is an
invariant. Also, after every text t(x, y) we can add the invariant
t(x, y) on theT-branch, and ~t(x, y)on the F-branch. Such invariants
can also be pushed forward to generate useful candidates at the
cutpoints.

4.5. Bounding Variables

One useful type of candidate for q'(x, y) at TV is constructed by


finding upper or lower bounds for the variables, expressed only in
112 LOGICAL ANALYSIS OF PROGRAMS

terms of constant expressions with respect to the block. That is, the
bounds contain only constants, input variables, or other program
variables which are unchanged inside the loop of the block.
Suppose that by considering f(x, y) and the invariant q(x, y) at
N, we are able to identify a variable yj which never decreases along
the path around the loop. Now, if we can infer from p(x, y ) an initial
value yj(0) = E for y}- at N, where E is a constant expression with
respect to the block, then we can conclude that y} > E is an invariant
at N. Similarly, if y} never increases, then ys < E is invariant.
A similar heuristic tries to establish that the variables maintain
some data type, such as integer or real, during execution.
We will first illustrate the application of the heuristics in obtain¬
ing some additional invariants for the program of Example A, and
then present a new example which will illustrate the possible inter¬
play between the algorithmic and heuristic techniques.

Example A (continued). Let us consider again Program A of Figure


2.5. Applying the disjunct elimination rule of Section 4.1 to the
invariant

(A6) xx <yx +2y2 V xx >yxatN,

we check first whether x1 < yx + ly2 is itself an invariant. From


Lemma A, we can show that

(a) Vx [0<Xj <x2 A 0 <x3 D x1 < 0 + 2x2 ] and


(b) VxVy [xx <yx + 2y2 A y3 >x3 A xx <y1 +y2
Dx1 < yx +y2],

Vx Vk [xx <yx + 2y2 A y3 >x3 A xx >yx +y2

2) x-l < yi + y2 + y2] ■

Since all of the conditions are true, we have the invariant

(A7) xx <yx + 2y2 atN.

For xx >yx , the second disjunct of (A6), we can show that

(a) Vx[0<xx <x2 A 0 < x3 D xx > 0],


Generation of Invariants: Heuristic Approach 113

(b) V a- V y Ui > y1 A y3 > x3 A x1 < y1 + y2 D xx > yx ],

Vx vy[xx >yx A y3 >x3 A xx >yx +y2 2) xx >yx +y2].

Since these conditions are all true, we have shown that the second
alternative is also an invariant, i.e.,

(A8) xx >yx at TV.

We can combine the invariant (A4), yx = 2x2*y4, with (A8) and


the input assertion 0 < x2 to obtain an upper bound on y4 in terms
of x, i.e., the invariant

(A9) y4 <x1/(2x2) at TV.

This invariant will be of special use later, in Sections 5 and 6, and in


practice would be generated only when a need for such a bound
arises.
Now, by pushing forward to H the invariants (Al) to (A9) at TV,
and adding the exit test y3 < x3 , we obtain

(A10) y3 <x3 A y2 = x2/2n A y3 = 1/2” A y2 = x2-y3

A yx- 2x2-y4 A (n = 0 V 2y3 > x3) A xx <yt + 2y2

A x± > y± Ay4 <x1/(2xz) atH. □

Example B. The program B shown in Figure 2.6 is supposed to per¬


form integer division in a manner similar to computer hardware. For
every integer input xx ^ 0 and x2 > 0, we would like to have as out¬
put yx = rem(xlt x2) and y4 = div(x1, x2), i.e., xx = y4 'x2 + yx A
0 <yi < x2 Ayj.^4 G{integers}. This program differs from the pre¬
vious example in that it contains two loops, one after the other. The
upper block, with counter n and cutpoint N, consists of a simple
loop, while the lower block, with counterm and cutpoint M, consists
of a loop with two paths. For convenience, we have added an addi¬
tional cutpoint L between the blocks.
Our strategy will be to gather initially as many invariants as pos¬
sible at TV. The algorithmic techniques will be used to generate in¬
variants at TV directly, and then some of the heuristics presented above
will be used to suggest additional invariants. We then push the invari¬
ants forward to cutpoint L, so that we have as many invariants as we
114 LOGICAL ANALYSIS OF PROGRAMS

( START )

i input speci ficot i on i


! *,3>0 a *2>0 a |

; *| , *2 «{'Regers)

I
(y, ,y2 ,y3.y4 ) — *2 .' ,0)1

vToX
oN
-L-,

n♦1 . F fy

' m
i___j
0
y3) — (2y2.2y3)
t
T > F
!—yf - y2 1-
i—
m I
i_
(y, .y4)- vy, - y2 y«*y3>

m *
_L_
i y3

i- T i
i output specification >
!(y2,y3)^(y2/2.y3/2)
* < =y-* • * 2 *- y. *
0 < y, < x 2 a
y, , y4 « (integers}

halt

Figure 2.6. Program B. Hardware Integer Division.

can when the second block is first reached. Then we will employ the
algorithmic techniques to generate invariants at M. Finally, we use
heuristic techniques based on the invariants at L and M and the can¬
didates implied by the output specification at H to generate addi¬
tional invariants at M. We will not go into the problem of which
heuristic rule to use first, but simply indicate how some candidates.
Generation of Invariants: Heuristic Approach 115

which will indeed be useful invariants, can be found by using various


heuristics.
Applying the algorithmic techniques for finding invariants at N,
we obtain the equations
n

b2(«) =^2(0)-n 2=y2(0)-2" = x22n atN,


i= 1

J'3(«)=J'3(0)-JJ 2 = p3(0)-2n = 2" at N.


i= 1

Tims we can obtain the invariants

(Bl) y2 = x2-2n A y3 - 2n at TV.

These can be combined to give

(B2) y2 =x2-y3 atN.

By pushing forward the information in <fi(x) and the initial asssign-


ments (using the heuristic of Section 4.4), we get the additional
invariants

(B3) yx - xx A y4 = 0 A yx , y2, y3, y4 G {integers} at N.

Using the bounding variables rule of Section 4.5, we note thatp2 an^
y3 are always increasing around the loop and, since y2(^) ~ x2 ar)d
p3(0) = 1 at N, we obtain the invariants

(B4) y2 >x2 Ay3 > 1 atN.

Note that (B4) could also be obtained directly from (Bl) using the
implicit invariant n > 0.
Using the T-branch of the test yx < y2 and pushing forward to L
the invariants at N, we have the invariants

(B5) p2 = *2-2" A y3 = 2n Ap2 = x2-y3 Apj = Ay4 = 0 A


y’i,3/’2,P3,y;4 G{ integers} A y2 >x2 A y3 > 1 A yx <y2 at L.
116 LOGICAL ANALYSIS OF PROGRAMS

Generating invariants directly from the statements of the lower


block, we first have the relations

y2(m)=y2(0)/2m at M,
L3O0 = y3(0)/2m at M.
Using the invariants y2 = x2-2n and y3 = 2n from (B5) to establish
y2(0) andy3(0) at M, we obtain the invariants

(B6) y2 =x2-2n/2m Ay3=2n/2m A y2=x2-y3 at M.

Using the same technique for y1 and y4, we obtain the recurrence
relations

y1(m)=y1(m - 1) + [ify1(m - 1 )>y2(m - 1)


then —y2(m — 1)
and else0] atM>
y±{m)=y±(m - 1) + [if y^m - 1) >y2(m - 1)
theny3(m - 1)
else 0] at M.

Writing these equations as a summation, then using (B6) to replace


the occurrence of —y2(m — 1) by— x2-y3(m — 1) and factoring out
-x2, we obtain

in

Li(w)=y1(0) [ifjPiC/ - \)>y2(i- 1)


!=1 then y3 0‘— 1)
and else01
m

L4(^)=L4(0) + 2 [ifLi(i — l)>y20- 1)


1=1 theny3(/—1)
else 0] at M.
Combining these formulas, we get

Li (m) — Li(0) = —x2-[y4(m) — y4(0)] atM. (2.6)


We will again use invariants from (B5) at L, namely = Xl and
L4 - 0, to evaluate yx(0) and y4(0) at M. There are two possible
paths from L to M. If the right branch is used, clearly

Li(0) = A y4 (0) = 0 at M.
Generation of Invariants: Heuristic Approach 117

On the other hand, if the left branch is taken, the additional


invariants from (B5), yx < y2, y2 = x2-y3 and yx = Xl at L, to¬
gether with the fact that yx > y2 along this path, yield thatpx =y2
and y3 = y2/x2 = yi/x2 = xx/x2. Therefore, after the assignments
yi >’i —y2 andy4 <~y4+y3, we have that

yx (0) = 0 and y4 (0) = xx /x2 at M.

Substituting both possibilities for y1 (0) and y4(0) into the above
equation (2.6), we obtain in both cases yx{m) — xx = — x2’y4(m).
Thus we have the invariant

(B7) xx =y4-x2 +yx atM.

Turning to the tests, following Equation (2.4) we have


m = 0

V [y3(m - 1)^ 1 A y^m - l)>y2(m - l)/2] (left path)


V [y3(m - 1)^ 1 A y1(m - 1) <y2(m - l)/2] (right path).
We try to substitute, using the recurrence equations for the left path
in y3(m — 1) ¥= 1 A yx(jn — 1) ^ y2(m — l)/2, and the recurrence
equations for the right path in y3(m - 1) =£ 1 A y1{m - 1) <
y20n — 1 )/2. For the left path, we have the recurrence relations

y1(m)=y1(m - 1) -y2(m - l)/2


y2(m) = y2(m - l)/2 (left path),
y3(m)=y3(m - l)/2
and for the right path we have

yi(m)=y1(m - 1)
y2(m)=y2(m - l)/2 (right path).
y3(m)=y3(m - l)/2

Using these equations we obtain

m = 0
V [2y3(m) 1 Ayx (m) > 0] (left path)

V [2y3(m) # 1 /\y1(m) <y2(m)] (right path).


118 LOGICAL ANALYSIS OF PROGRAMS

Equivalently, we can write


[m = 0 V 2y3(m) 1 ]A
[m = 0 V(m) > 0 V Ti (ra) <L2(m)] • (2.7)
For the first conjunct, we can eliminate the m = 0 alternative because
by (B5) we have 73(0) > 1 at M, and therefore 2y3(0) 1 at M. We
therefore have the invariant

(B8) 2y3 1 at M.

For the second conjunct of (2.7), we can eliminate the m = 0 alter¬


native because, as shown earlier, y3 (0) at M is either 0 or xx, and
thus .Vi (m) > 0 is true when m = 0. We have the invariant

(B9) yt >0Wy1 <y2 atM.

So far we have used only the algorithmic techniques on the lower


block, and have directly generated invariants (B6), (B7), (B8), and
(B9) at M. Now we illustrate how some of the heuristic methods
could be applied in order to obtain additional invariants.
Turning to the rule of Section 4.1, we consider each disjunct
of (B9) separately. It is straightforward to show that both y1 > 0
and yx <y2 are invariants at M; i.e., we may add

(BIO) y1 >0Ay1 <y2 atM.

Using the rule of Section 4.4, we push forward to M the in¬


variants in (B5), and among the candidates obtained isy3 > 1. Since,
using the invariant y3 = 2n/2m from (B6), we prove that for both
paths around the loop

\/y[y3 > 1 Ay3 = 2n/2m f\y3 =£ 1 D y3/2>1],


we have the new invariant

(B11) y3 > 1 at M.

(A consequence of (B11) is that n > m at M.) In turn, (B11) can be


used, along with invariants y2 = x2'2n/2m and p3 = 2"/2m from
(B6), to show that the candidate y2, y3 E {integers} is an invariant.
Correctness and Incorrectness 119

We can obtain that ylt y4 E {integers} by pushing forward the in¬


variants at L; thus

(B12) y1,y2,ya,y4 integers} atM.

Observe that if we had used the heuristic of Section 2.3 to push


the given output specification at H backwards to M at an early
stage, we could have obtained the important candidate invariants
*1 ~y4**2 +yi, 0 <>’!, ylt y4E{integers}, andyx <x2 directly
by this method. As shown, the first three candidates are indeed in¬
variants at TV, while any attempt to establish the fourth candidate
y4 < x2 will fail.
Now, by pushing forward to H the invariants (B6) to (B12) at M,
adding the exit testy3 = 1, and simplifying, we obtain the invariants

(B13) y3 = 1 A y2 = x2 A n = m A x4 = y4-x2 + yx A

0 <yx<x2 /\y1,y2,y3,y4 ^{integers} atH. □

5. Correctness and Incorrectness

As indicated in the Introduction, invariants may be used to prove


the correctness or incorrectness of a program. In order to place these
properties in their proper framework, we first present some basic
definitions and lemmas [which follow Manna (1969, 1974)].

(a) A program P terminates over 0(F) if for every input a such


that 0(a) is true, the execution reaches a HALT statement.
(b) A program P is partially correct w.r.t. 0(F) and \p(x, y) if for
every input a such that 0(a) is true, whenever the program
terminates with some b as the final value of y, 0(a, b) is true.
(c) A program P is totally correct w.r.t. 0(F) and \p(x, y) if for
every input a such that 0(a) is true, the program terminates
with some b as the final value of y and 0(a, b) is true.

We are interested in proving that a program is either totally correct


(correct) or not totally correct (incorrect). We introduce termination
and partial correctness because together they are equivalent to total
120 LOGICAL ANALYSIS OF PROGRAMS

correctness, and, as we shall see, for a proof technique based on in¬


variants it is easier to prove these two properties separately rather
than to prove total correctness directly.
The Lemmas B-D and B'-D' (Table 2.1) use the invariants
{qh (x, y)} at the haltpoints to provide criteria for proving termination,
partial correctness, total correctness, and their negations. For clarity
we have used an informal abbreviated notation. Lemma B, for
example, should be stated as:

Lemma B. A program P terminates over f if and only if for every set


of invariants {q( (x, y) } and every input x such that 0(x) is true,
there exists a haltpoint h such that 3y[qh(x, y)\ is true.
Proof. If the program terminates, then for every input x satisfy¬
ing 0(x) some haltpoint h must be reached, and y will naturally have
some value b at h. Then, by the definition of an invariant, for every
set of invariants, qh(x, b) must be true, i.e., By[qh(x, y)] is true.
In order to prove the Lemma B in the other direction, we intro¬
duce the notion of a minimal invariant at cutpoint i, denoted by
mfx, y). A minimal invariant /rqCa, b) is true for some input a
satisfying f(a) and for some b if and only if during execution with
input a the cutpoint i is reached with y = b. Thus mfx, y) denotes
the exact domain of the y values which occur at i during execution
of the program with input x.3
Now we assume that for every set of invariants and every x such
that 0(x) is true, there exists a haltpoint h such that 3y[qh(x, y)] is
true. Then, in particular, this is true for the set of minimal invariants.
By the definition of minimal invariant, since there exists a y such
that mh (x, y) is true, that y value actually occurs during execution at
the haltpoint h, i.e., h must be reached, and the program must there¬
fore terminate. □

The other lemmas may be proved by using similar arguments.


The six lemmas of Table 2.1 can be divided into two groups. The
first group, Lemmas B', C, and D', are expressed in terms of the ex¬
istence of a single set of invariants {g,-(x, y) } (an “3q formula”).
They therefore may be used to prove nontermination, partial correct-

3 Note that from its definition mf(x, y) always exists as a predicate; for our purposes it
is irrelevant how this predicate is expressed.
Correctness and Incorrectness 121

TABLE 2.1
Applications of the Invariants {q ,-(x, y)}

Lemma B. P terminates over 0 if and only if Lemma B'. P does not terminate over 0
VqVx3h3y[qh(x,y)]. if and only if
3q3x\/hVy[~qh(x, y)].

Lemma C. P is partially correct w.r.t. 0 and Lemma C'. P is not partially correct w.r.t.
0 if and only if 0 and 0 if and only if
3qVxVhVy[qh(x, y) O 0(x, y)). Vq3.x3h3y[qh(5c,y) A ~0(x,y)] •

Lemma D. P is (totally) correct w.r.t. 0 and Lemma D'. P is incorrect w.r.t. 0 and 0
0 if and only if if and only if
\/qVx3h3y [qh(x, y) A 0(x, y)]. 3q3x\/h\/y[qh(x, y) D ~0(x, y)].

Vq means “for every set of invariants {q( (x, y)}”.


3q means “there exists a set of invariants {<7,- (x, y)}”.
Vx means “for every input x such that 0(x) is true.”
3x means “there exists an input x such that 0(x) is true”.
\/h means “for every haltpoint h”.
3h means “there exists a haltpoint h”.

ness, and incorrectness, respectively, by demonstrating a set of invar¬


iants which satisfies the appropriate formula. The techniques of
Sections 3 and 4 can be used to produce such a set of invariants.
Lemmas B, C', and D, on the other hand, are expressed in terms of
every possible set of invariants {qt (x, y) } (a “V<7 formula”), and
may not be used directly with our techniques.
Since total correctness is expressed by a formula, we try to
prove this property by showing partial correctness and termination
separately. Lemma C uses an 3q formula, and therefore is used to
prove partial correctness. This lemma in fact represents “Floyd’s
method” for proving partial correctness. The problem of termina¬
tion, however, remains, since it is expressed in terms of a \/q formula.
Termination must therefore be treated by other means, which will be
discussed at the end of this section.
Incorrectness, on the other hand, is expressed by an 3q formula,
and therefore can be proven directly by our techniques, using Lem¬
ma D'. Note that the formula of this lemma can be expressed alter¬
natively as

3q3x \/h \/y[~qh (x, y) V ~ ip(x, y)],


122 LOGICAL ANALYSIS OF PROGRAMS

i.e., for some input 3c, either the program does not terminate or the
final result is incorrect.
We first illustrate the use of Lemma C for proving partial cor¬
rectness.

Example B (continued). We would like to show that program B of


Figure 2.6 is partially correct w.r.t.
0(3c) : xx >0Ax2 > 0 A x1, x2 G {integers},
and
0(*. y) :x1 = y4-x2 +y1 A 0 < y x <x2 /\yx,y4 G {integers}.
Using invariants (B1) to (B4) at N, (B5) at L, and (B6) to (B12) atM,
we have established the invariants (B13) at H (the only haltpoint of
the program). Since (B13) contains the invariants

*1 =>V*2 +y 1 A 0 <yx <x2 A ylty4 G{integers},


we clearly have that

VxVJUcM*. y) => 0(3t, y)].


Thus, by Lemma C, program B is partially correct w.r.t. 0 and 0.
(Note that (B13) actually contains additional information about the
final values of the variables, namely that y3 = l,y2 = x2, and n = m
at the haltpoint H.) □
Thus, to prove partial correctness, we merely exhibit the invariants
at the haltpoints which fulfill Lemma C. On the other hand, in order
to prove incorrectness we must provide, in addition to appropriate
invariants, an input value 3c0 satisfying 0(xo) such that the formula in
Lemma D' is true. We would like to develop candidates for x0
in a systematic manner, similar to the way invariants were generated
in Sections 3 and 4. For this reason, it is desirable to find a predicate
0'(x) which specifies a nonempty subset of the legal inputs for which
the program is incorrect, rather than merely demonstrating the
incorrectness for a single T. That is, to establish incorrectness we
prove that for some 0'(3c),

V*[0'(x) D 0(3c)] A
3x<j)'(x) A
3q\/x\Jh\/y[(l)\x) A qh(x, y) D ~0(3c, y)].
In general, a proof which establishes incorrectness for a large set
Correctness and Incorrectness 123

of input values is also more useful for the diagnosis and correction
of the logical errors than an incorrectness proof for a single input
value (see Section 6).
We will develop candidates for 0'(x) by starting with 0(x) and add¬
ing conjuncts (restrictions) to 0(x) one after another as the need
arises. Thus 0'(x)D0(x) will be guaranteed true. In case there are
several alternative restrictions at some stage of the process, we prefer
adding the weakest possible, so that 0'(x) will allow maximal free¬
dom in choosing additional restrictions later. At each stage, of
course, it is necessary to demonstrate that 0'(x) is satisfiable.
Note that all invariants which have been proven for 0(3c) will
remain true for any 0'(x) specifying a subset of 0(x). Moreover,
at each stage of the process we now may discover additional invar¬
iants which are true for every x satisfying 0'(x) but are not neces¬
sarily true for every x satisfying 0(x).

Example A (continued). An attempt to prove the partial correctness


of program A (Figure 2.5) will not succeed. Although the invariants
(A 10) at H can be used to establish y4 <x1/x2 because
\/x\/y[y4 <x1/(2x2) Dy4 <x1/x2],
we are unable to establish x4 /x2 — x3 < y4. Thus we turn to incor¬
rectness, trying to show that for some 0'(x) which specifies a non¬
empty subset of the legal inputs, and for some invariants qH(x,y)
at H, we have
\/x\/y[(p'(x) AqH(x, y) Dx4/x2 -x3 >y4].
We first could try to show that the program is incorrect for every
legal input x, i.e., let 0'(x) be 0(x) itself, but such an attempt will
fail. To find a better candidate 0'(x), we notice that the “desired"
conjunct isp4 <x4/x2 — x3, and that the invariant y4 <Xi/(2x2) at
H of (A 10) also provides an upper bound of y4 in terms of x. This
suggests using the transitivity of inequalities to find an r(x) such that

[y4 <x1/(2x2) A r(x)] D y4 <x4/x2 -x3.

The “most general" candidate for r(x) is clearly x1/(2x2) <


Xi/x2 — x3, or, equivalently,
r(x) : x3<x1/(2x2).
124 LOGICAL ANALYSIS OF PROGRAMS

The trial 0'(x) will therefore be 0(T) Ar(x), i.e.,

4>'(x) : 0<xx <x2 A0<x3 Ai3 <x1/(2jc2).

From the development of 0'(v), it is obvious that y4 < xxjx2 — x3


is an invariant at H for every x satisfying 4>'(x). Thus to establish
incorrectness it only remains to show that 0'(v) is satisfiable. Since
we may first choose any xt and x2 such that 0 < x4 < x2, and then
choose any x3 such that 0<v3 <xt/(2x2), the satisfiability of 0'(T)
is obvious. □

Recall that we have not yet provided a practical method for prov¬
ing termination. The difficulty arose from the fact that Lemma B of
Table 2.1 requires proving a “Vq formula”. Therefore we clearly
need a special method for proving termination.
The traditional method suggested by Floyd (1967) involved
choosing a well-founded set (W, >), where > is a partial ordering hav¬
ing the property that there is no infinitely descending chain of ele¬
ments wl > w2 > ••• from W. For every cutpoint i, one must find a
partial function u^x, y) which maps the elements of the variables’
domain into W, and an invariant q{(x, y) which serves to restrict the
domain of ut. A proof of termination requires showing that each
time control moves from cutpoint i to cutpoint / (along a path which
includes no other cutpoints and which is a part of some loop),
ui(x, y) > Uj(x, y). Intuitively, since by definition there is no in¬
finitely decreasing chain of elements in any well-founded set, the
proot implies that no execution path of the program can be infinitely
long.
The use ot Floyd s method entails choosing the appropriate well-
founded set (IF, >), the functions {u^x, y)}, and the invariants
{Qi(x, y)}. We will suggest an alternative method for proving termi¬
nation which will be strongly oriented toward the use of invariants,
so that we may take advantage of the techniques of Sections 3 and 4.
We present the method briefly.
As explained in the Preliminaries section, it is assumed that we can
divide the given program into blocks in such a way that every block
has only one top-level loop (in addition to possible “lower-level”
loops already contained in inner blocks). We treat the inner-most
blocks first, and work outwards. Thus, for each block we can consider
only its top-level loop (with a unique cutpoint), assuming its inner
blocks are known to terminate.
Correctness and Incorrectness 125

We suggest proving termination of a block with cutpoint i and


counter n (assuming that the inner blocks terminate) by finding in¬
variants which will imply that n is absolutely bounded from above
at i. That is, n < ct at i for some constant ct. Therefore,the cutpoint
cannot be reached infinitely many times during computation. Note
that it is actually sufficient to show afx, n) < bfx) where afx, n) is
an integer-valued function mono tonic in n [ i.e., if n increases in
value, so does afx, /?)] • We therefore state

Lemma E (termination). A program P terminates if and only if there


exists a set of invariants {qt} and functions {af and {b{} such that
for every block B with cutpoint i and counter n,
V x V y V n [qfx, y, h) D afx, n) < bfx)], (2.8)
where afx, n) is an integer-valued function mono tonic in n.

The practical importance of the above Lemma E is that we may


use invariants which link n to the program variables to derive directly
the appropriate functions at- and b(. Recall that in such programs, we
have the “built-in” invariant that n is a strictly increasing non¬
negative integer. We shall use these properties in our examples with¬
out explicit indication. Although n and afx, n) are integers, bfx)
can be any number, and the input variables x and program variables
y need not even be numeric; this technique is perfectly applicable to
programs with lists, strings, etc. Lemma E can be proved formally by
reduction to Floyd’s method.
One can weaken the termination condition (2.8) of Lemma E in
several different ways. For example, we can often generate R(x, y),
the disjunction of the conditions for following a path from Cutpoint
i around the loop and back to i in B. We may then use it in proving
that the counter in bounded, since it R(x, y) is false, the loop will
terminate anyway. Another possibility is to use in u,- and bi all those
variables of y (and all those counters), denoted by y \ which are not
changed in B. Thus it actually suffices to prove the weaker condition

\/x\/y\/n[qfx, y, n) A R(x, y) 3 afx, y, n) < bfx, y')]. (2.9)

Example A (continued). Consider again Program A of Figure 2.5.


From 0(x) and invariant (A2) we note that 0 < x3 Ay3 = 1/2" is
126 LOGICAL ANALYSIS OF PROGRAMS

an invariant at N. Thus since


\/x\/y\/ « [0 <x3 A y3 = 1/2" Ay3 >x3 D 2n<\/x3]
is true, it follows by Lemma E that the program terminates over0(T).

Example B (continued). Consider Program B of Figure 2.6. Using


the known invariant (Bl), y2 ~x2 '2n at N, and <p(x), we obtain
\/x\/y\/n[x2 >0 A y2 = x2-2n A y2 <y1 D 2" <yx/x2].
Since yx is unchanged in the upper block, it follows by 2.9 that the
upper block terminates.
For the lower block we use the invariants (B6) and (B11),
y3 = 2n /2m A y3 > \ at M, and obtain

\/x\/y\/n\/m[y3 = 2n /2m A y3 > 1 D 2m < 2n ].

Since n is unchanged in the lower block, the termination of this


block also follows by 2.9. □

The reader should not be misled into assuming that proving termi¬
nation is always as trivial as it seems here. The method of Lemma E
is examined in greater detail (and presented with some nontrivial
examples) in Katz and Manna (1975).
Note that the method of Lemma E, as well as Floyd’s original
method, is useful only for showing termination. If we want to prove
nontermination, both methods are inapplicable (again, all possible
qt s must be checked). Thus Lemma B' should be used.
Another important side benefit of using counters lies in the infor¬
mation provided on the time complexity of the given program. By
analyzing the invariants at the cutpoints, upper bounds may be
obtained on the number of times the loops can be executed. It is
sometimes feasible also to discover an invariant of the form r < n at
a point immediately after the exit from the loop, thus yielding a
lower bound on the number of times the loop will be executed.

Example A (continued). Using the invariants (A2) and (A5) of pro¬


gram A (Figure 2.5),

y3 = l/2n and n = 0 V 2y3 >x3 atN,


we obtain the upper bound n = 0 V 2n < 2/x3.
Automatic Debugging 127

The exit test and invariant (A2) imply that


y3 = 1/2" and y3 <x3 at H.
Therefore \/x3 < 2" is a lower bound upon exit from the loop. That
is, in this program the relations
n = 0 V 1/jc3 < 2" < 2/x3

are satisfied, and the exact number of executions of the loop can be
computed as a function of the input. a

Example B (continued). In order to obtain an upper bound on the


number of executions of the first loop of Program B (Figure 2.6), we
need to generate an additional invariant which was not needed previ¬
ously. Using the technique for generating invariants from tests, we
obtain
n = 0 V y1(n - \)>y2(n - 1).
Since yx(n) = yx(n — 1) andy2<A) = 2y2(n - 1), the resulting invari¬
ant is
n = 0 V yx >y2/2 atN.
Using the invariants (B1) and (B3),jq = xx and y2 =x2‘2n at N, the
upper bound n = 0 V 2" < 2xx /x2 is obtained.
From (B5) we have
y2=x2*2" A yx=xx A yx <y2 at L.
This gives a lower bound of xx \x2 < 2" . Thus, for the first loop the
relations
n = 0 V jcx /x2 < 2" < 2xx /x2

are satisfied.
Since n = m at H by (B13), it follows that the second loop is ex¬
ecuted the same number of times as the first. a

6. Automatic Debugging

In this section, we suggest a method for debugging based on the


invariants generated from the program. The technique we describe
uses the invariants and information about how they were generated
in order to modify the program systematically.
128 LOGICAL ANALYSIS OF PROGRAMS

As explained in the Introduction, failure to prove correctness


leaves us unable to decide whether the program is actually incorrect
or has merely eluded our efforts to prove its correctness. Two
different philosophical approaches to automatic debugging can be
applied as soon as we are unable to prove correctness of a program.
Following what may be termed the conservative approach, we
would insist on a proof of incorrectness before proceeding to modify
the program. This is a reasonable view, and, as will be indicated
below, a proof of incorrectness can aid in debugging. The method
presented for proving incorrectness of programs was motivated by
this approach.
However, proofs of incorrectness are often difficult to obtain, in
particular for subtle errors, since the needed (p'(x) (a class of inputs
leading to incorrectness) must be produced. Thus an alternative to
the conservative approach, a radical approach, can also be justified.
In this approach, we will “fix” the program so that a proof of cor¬
rectness is guaranteed to succeed, even without having proven that
the original program is incorrect. In effect, under this approach
we modify a program we merely suspect of being incorrect, taking
the risk of modifying an already correct program.
The basic debugging technique using invariants is common to both
approaches. We shall first describe the technique as it is used in the
radical approach. The slight differences which arise if the conserva¬
tive approach has been used (i.e., if a proof of incorrectness is avail¬
able) are pointed out later in this section. At the end of the section
we briefly compare the two approaches.
For simplicity we will again deal with a simplified model: a single
block having no inner blocks, with a cutpoint L at the entrance, N
inside the loop, and M at the exit, as in Figure 2.3 or 2.4. In addition
to the candidates produced and invariants proven for each cutpoint
during the process of invariant generation, we assume candidates
s"(x, y) at M which would guarantee partial correctness of the pro¬
gram were they actually invariants. For the case in which M is a
haltpoint, s"(x, y) would naturally be the output specification
itself.
To effectively use the invariants for debugging, it is necessary to
record in an invariant table all the information required to establish
each invariant, e.g., the rule applied, and precisely how the program
Automatic Debugging 129

statements and other invariants were used in its derivation. In addi¬


tion, the uses of that invariant for proving other invariants must be
noted. In general there will be an entire invariant table associated with
each cutpoint. However, there is usually an essential difference in the
complexity of the table for cutpoints on a loop, like N, and for those
not on a loop, like M. All of the invariants at M, for example, will be
obtained simply by “pushing forward” either invariants at N or the
exit condition of the block. Thus below we concentrate on the more
interesting case of the invariant table at N.
For clarity, we will use a more pictorial representation for
the invariant table at N and arrange the invariants generated in
the form of a directed acyclic graph (dag). We use terminology
similar to that of trees, talking about the “ancestors” or “descend¬
ants” of an invariant, and of moving “up” or “down” the graph,
and we refer to the graph as an invariant tree. We will have invari¬
ants from previous blocks given in p{x,y), the initial assignment state¬
ments of the block, and the statements of the loop at the top of the
tree. Each invariant q(x, y) at N is the descendant of the loop state¬
ments, initial assignments, and other invariants used to establish
q(x, y).
By examining such an invariant tree, we can see both how a de¬
sired change in any given statement will affect the various invariants,
and (conversely) how a desired change in an invariant can be achieved
by changing statements.
The basic steps in correcting the program are as follows (again
referring to Figures 2.3 or 2.4):

1. Using the heuristic methods of Section 4, such as 4.3, generate


candidates for invariants q"(x, y) at N which would allow
proving the candidates s"(x, y) at M to be invariants, and thus
would allow proving partial correctness.4 It is also possible to
generate candidate exit tests t'(x, y) or candidate exit func¬
tions h'(x, y) which would guarantee partial correctness along
with the existing invariants at N. In the continuation, we dis¬
cuss changing only the invariants at N, although similar con¬
siderations apply to changing the exit test or exit function.

4 The possibility that the program is partially correct but nonterminating will not be
treated in our discussion; actually it would lead to a correcting process similar to that
described here.
130 LOGICAL ANALYSIS OF PROGRAMS

2. Find actual invariants q(x, y) in the invariant tree which are


“similar” to those candidates q"(x, y) which guarantee correct¬
ness. The precise definition given to “similarity” will have a
direct influence on the kinds of errors which may be corrected,
and there are obviously many possibilities. We here assume that
two predicates are similar if they differ only in constant (non¬
zero) coefficients of variables, a constant term, or other minor
perturbations in the relation involved, such as < in place of <.
When we have succeeded in finding invariants qix, y) in the
tree similar to candidates q"(x, y), the candidates will be called
the goal candidates at N, and denoted q*(x, y).
3. Attempt to replace q(x, y) by the similar goal candidates
q*(x, y), moving up the tree and modifying the ancestors of
q(x, y) so that the new q*(x, y) will be derived rather than
the former q(x, y).
4. When a statement has been modified in order to allow deriving
a goal candidate, inspect (by moving down the tree) the effect
of the modification on all other invariants derived from it. This
is necessary in order to ensure that no other part of the proof
of partial correctness or the proof of termination is disturbed.
The inspection could require making additional “compensa¬
tory” changes in other statements, or abandoning a possible
change.
Example A (continued). Consider once again Program A of Figure
2.5. The invariant tree for the program is shown in Figure 2.7. For
simplicity, we have merely listed the number of the rule which was
applied to obtain each invariant, rather than including more informa¬
tion. A brief review of the generation of invariants for this example
(in Sections 3 and 4) should make the tree clear [except for (All),
which should be momentarily ignored]. We have added the “termina¬
tion” and “partial correctness” boxes at the bottom of the tree to
emphasize which statements and invariants were used to prove termi¬
nation (with bound 2" < l/x3) and partial correctness (w.r.t. y4 <
x1/x2). Recall that we were unable to prove partial correctness for
x1/x2 — x3 <y4, the first conjunct of the output specification. In
order to demonstrate the radical approach, we momentarily ignore
the fact that in Section 5 we actually have proven this program
incorrect.
Automatic Debugging 131

Figure 2.7. The Invariant Tree for Cutpoint N of Program A


132 LOGICAL ANALYSIS OF PROGRAMS

The problematic part of the output specification, x4 /x2 — x3 <


y4, is automatically a candidate invariant at H. Using the heuristic of
Section 4.3, we can generate candidate invariants atTVby pushing back
the candidate at H (assuming temporarily that the exit testy3 <x3
is correct). The strongest candidate at N is xx/x2 — x3 < y4 itself.
We may also use the transitivity of inequalities with x4/x2 — x3 <y4
and the exit condition y3 <x3 to suggest another natural candidate.
We need a q(x, y) such that

q(x,y)/\y3 <x3 Dxx/x2-y4<x3,


and see easily that the most general q(x, y) which will do this is
*i/*2 y4 <Ta, orx1/x2 <y3 +y4.
Naturally, if either of these candidates could be proven to be an
invariant at N, the program already would have been proven correct.
Now we turn to the invariant tree in order to modify the program so
that a correctness proof is possible. We look for invariants already in
the tree which are similar to the above candidates, and also try to
combine existing invariants into new ones similar to the candidates.
For the candidate x4jx2 — x3 < y4, we find no similar invariant.
For the second candidate, x4/x2<y3 + y4, we may combine (A3),
(A4), and (A7), giving

ly2 =*2->’3 A y4 =2x2-y4 A x4 <y4 +2y2] D

Xi/x2 < 2y3 + 2y4, (2.10)

i.e., we have the new invariant

(All) x4/x2 < 2y3 + 2y4 atN.

This is similar to the candidate, which we now will refer to as the


goal candidate

(All*) xt/x2 <y3 +y4 at N.

We have thus found a place to “hang” the candidate on the tree, and
now must adjust the ancestors of (All) [i.e., (A3), (A4), or (A7)] so
that (All*) will be derived instead. By examining Equation (2.10), it
is not difficult to see that two of the most direct modifications
Automatic Debugging 133

among the many possibilities are

(a) leave (A3) and (A4) unchanged, but change

(A7) xx <y1 + 2y2 to (A7') 2xx <yx + 2y2 ;

or
(b) leave (A7) unchanged, but change
(A3) y2 =x2-y3 to (A3') 2y2 = x2-y3

and
(A4) yx =2x2-y4 to (A4') yx = x2-y4.

Possibility (a) will be considered first. The invariant tree shows


that (A7) was derived from the invariant

(A6) <yx +2y2 Vx1 >yx atAf

by using the Disjunct Elimination Rule of Section 4.1 to strengthen


the invariant. To obtain (A7'), we will first modify (A6) to

(A6') 2xx <yx +2y2 V h(x,y),

where h(x,y) is the part of (A6') not of interest to us at the mo¬


ment. By tracing back through the derivation of (A6) (which used
the algorithmic rule of Section 3.2), the left alternative of (A6) can
be seen to originate as
(i) xx <yx{n - 1) +y2(n - 1)

(from the test xx <yx + y2, using the right path),

(ii) y1(n)=y1(n- 1)
(from the fact thaty! is unchanged along the right path),

(iii) y2(n)=y2(n - l)/2


(from the assignment y2 +-y2/2).

These clearly were combined to yield the alternativexx <yx + 2_v2-


134 LOGICAL ANALYSIS OF PROGRAMS

To obtain 2xx < yx + 2y2 instead, we replace (i) by 2x1 <


yx(n — 1) + y2(n — 1), i.e., change the test statementxx <yx +y2
to 2xx <y i + y2. This suggested change was built to yield an accept¬
able left alternative of (A6'). Checking 2x1 <yx +2y2 alone, we may
conclude that with this suggested change (A7') is indeed an invariant,
and thus, so is the goal (All*).
We must now check whether any other vital invariants are affected.
From the tree it is clear that the only effect could be on the right
alternative of (A6) and its descendants. Using the new test statement,
it is easy to see that the left path leads to

2*i >yi(n - 1) +y2(n - 1)

(from the test 2xx < yx +y2 , using the left path),

yi(«) =yi(n - \)+y2(n - 1)

(from the assignment yx <- yx + y2 on the left path).

These clearly combine to yield 2xx >yx(n), so that h(x, y) is 2xx >
yi, and we have the invariant

(A6') 2xx <yx + 2y2 V 2xx >yx at N.

Examining the descendants of (A6'), we can see that (A8) must be


replaced by

(A8') 2xx >yx at Nr

which is an invariant of the modified program. In turn, this com¬


bined with (A4) will yield the invariant

(A9') y4 <xx/x2 at N.

Thus we also have the invariant y4 <xx/x2 instead ofy4 <xx/(2x2)


at H.
However, this invariant serves just as well as the original y4 <
xxI(2x2) to guarantee partial correctness for the output specification

y 4 ^ a'i/*2- Thus the suggested correction leads to the goal (All*)


Automatic Debugging 135

and does not disturb any other aspect of the proof of correctness,
i.e., the modified program is guaranteed correct. In Figure 2.8 we
show the invariant tree at N of the modified program, which is totally
correct. Thus, to summarize:

Replace the test xx < y1 + y2 by 2xx < yx +y2.

Possibility (b) for achieving the goal (All*) will now be considered,
i.e. we would like to replace (A3) and (A4) by (A3') and (A4'),
respectively (again referring to the original invariant tree of Figure
2.7). We immediately note that since (A3) is an ancestor of (A4),
any change in (A3) will influence (A4). The invariant (A4) was
obtained by bringing two summations involving if-then-else to an
identical form, so that yx and y4 could be connected. If during
the manipulations of the relations, 2y2 = x2 'y3 is used for substitu¬
tion instead ofy 2 = *2 ’T3, the new (A4) becomes exactly yx = x2 74,
i.e., the (A4') we require. Thus if we can change (A3) to (A3'), we
“automatically” have changed (A4) to (A4').
Examining the invariant tree, it is clear that we may achieve (A3')
by changing either (A 1) or (A2), i.e., either

(Al) y2 - x2\2n to (Al') y2 =x2/2n+1


or

(A2) y3 = 1/2" to (A2') y3 = 2/2".

Since y2 = y2(0)/2" and y2(0) = x2, the first possibility can be


achieved by letting y2(0) = x2/2, i.e., by changing the initialization
y2 +- x2 to y2 <- x2/2. Now we check the possible effect of this
change on other invariants. This initialization was used to establish
(A6) and (A7) the first time N is reached, but the new initialization
also does the same job. Tracing other paths down from this suggested
change, we see that (A4) was used to establish y4 < xx/(2x2) at H.
However, the new (A4'), yx = x2‘y4, may still be combined with
(A8), yx < xx, to show that y4 < xx /x2 at N and thus at H. There¬
fore this change is also safe, and we have
Replace the initialization y2 <-x2 by y2 <~x2/2.

The change in (A2), from y3 = 1/2" to y3 = 2/2", is also easy


to achieve, since y3 = y3(0)/2". Thus we set y3(0) = 2 instead of
136 LOGICAL ANALYSIS OF PROGRAMS

rin
I A\ I

correctness'
i Partial
i

correctness;
ipar*iai
Termination |

W t—

L~ J

Figure 2.8. The Invariant Tree for Outpoint N of the


Modified Program A (correction 1).
Automatic Debugging 137

y3 (0) = 1, i.e., change the initialization y3 1 to y3 <- 2. This


change will slightly affect the termination, but the counter n can
now be bounded by 2n < 2/x3. Again (A4f) can be shown not to
disturb the correctness for y4 < x4 /x2 . Thus a third safe change is
Replace the initialization y3^\byy3-^2. □
So far in this section, we have ignored the possibility that we have
already proven the program incorrect. Now we briefly consider how
a proof of incorrectness can aid in the automatic debugging process
described above.
We assume that when unable to prove correctness, the conservative
approach was followed and a proof of incorrectness was produced.
Although the existence of this proof has surprisingly little effect on
the basic debugging technique, it can be of some aid. Clearly, any
change in the program which is intended to correct the error must
change at least one of the invariants used in the incorrectness proof.
Thus the paths up the tree from the goal candidate can be restricted
to those which will influence invariants from the proof of incorrect¬
ness. This is valuable because one of the difficulties with the use of
the tree is the need for further guidance in the selection of likely
paths.

Example A (continued). Let us review the proof of incorrectness of


program A of Figure 2.5. We used the invariant y4 < x1/(2x2) at
H [one of the invariants of (A 10)] to find an r(x) such that
[y4<xi/(2x2) a r(x)] D y4<x1/x2-x3.
This suggested taking r(x) : x4 /(2x2) < /x2 — x3 , since
[y4<x 1/(2x2) A x1/(2x2) <x4/x2 -x3] D

y4 <x4/x2 -x3. (2.11)

This r(x) then led to


0'(x) : 0<x1 <x2 A0<x3 /\x1/(2x2) <x4/x2 -x3

which was then simplified and shown to be satisfiable.


Now y4 < x1/(2x2) at H was the only invariant used in the proof
of incorrectness and was obtained directly from the invariant (A9),
y4 < x1/(2x2) at N. Thus it follows that any correction of the
program must change invariant (A9). D
138 LOGICAL ANALYSIS OF PROGRAMS

Let us briefly compare the two approaches.


Because we guarantee correctness, the “radical” approach of mod¬
ifying without first proving incorrectness is not as dangerous as it
might seem. In fact, the only objection would seem to be that in the
case of a program which actually was originally correct, the effi¬
ciency of execution may be reduced in a modified (also correct)
version. From our experience with hand simulations, we believe that
if we are able to find goal candidates similar to the invariants, the
program is very likely incorrect, and it is worthwhile to follow the
radical approach without first proving incorrectness.
However, for programs with a large number of errors (or a small
number of very gross errors), it is unlikely that the required similar¬
ity will be found. “Gross” errors could actually be defined as those
which lead to invariants completely irrelevant to a proof of the
specification.
In any case, the proof of incorrectness would be a valuable aid to
the user, even if an automatic correction could not be made. It pro¬
vides what could be called logical diagnostics about the program.
From the conjuncts of the output specification which were contra¬
dicted, the general effect of the error is obtained. From cp'(x), the
user obtains a class of inputs for which the program is incorrect.
Most important, from the invariants directly used in the proof
of incorrectness, the user can identify the problematic relations in
the program.

7. Conclusion

In this paper we have presented an overview of how invariants can


be produced and used. The basic concept of an invariant is, of course,
not new. We have, however, tried to present a new perspective which
shifts emphasis from the limited task of verifying a correct program
to the more general framework of logical analysis. From our point
of view, invariants are independent entities which can be used
for many purposes, of which proving partial correctness is only one.
Numerous improvements and refinements are clearly possible to
the invariant-generating techniques presented. In particular, it is
necessary to further guide the heuristics in Section 4, so that they
will not be applied indiscriminately. For example, only when the need
Conclusion 139

for an invariant involving certain variables has become evident, should


candidates involving those variables be generated.
The general problem of generating invariants for a given program
is unsolvable. Programs clearly exist with relations among the
variables based implicitly on deep mathematical theorems which
could not conceivably be rediscovered by any general invariant¬
generating algorithm.
In a practical implementation, the user would be encouraged to
provide his own ideas about what the intermediate invariants should
be (“comments”), and these will automatically be considered as
candidate invariants. The system could also ask the user to provide
suggestions as the need arises for invariants involving specific pro¬
blematic variables with unclear relationships at a certain cutpoint. We
expect that a reasonably sophisticated system based on the tech¬
niques presented here, with some aid from the user whenever neces¬
sary, could produce sufficient invariants to conduct the logical
analysis of some nontrivial programs.
Several other efforts have been made to attack the problem of
finding assertions which prove partial correctness. The earliest work
was by Floyd (private communications, 1967) and Cooper (1971).
Elspas (1974) was the first to consider using recurrence relations.
Wegbreit (1974) has developed independently some rules similar to
our heuristic approach, and a method using a weak interpretation ot
the program. Katz and Manna (1973) suggested additional heuristics
to treat arrays. Greif and Waldinger (1974) also described a method
for generating assertions which moves backwards from the output
specification.
The idea of adding variables, such as counters, to the program in
order to facilitate proofs of partial correctness or termination is not
new. Knuth (1968) uses a “time clock” incremented at every state¬
ment to prove termination. Elspas et al. (1973) also discuss how such
counters can be used in termination proofs. Other related work on
termination is that of Cooper (1971) and Sites (1974).
The possibility of using a program verifier to debug programs
was first discussed informally by King (1970). Sussman (1975)
stresses the importance of systematically eliminating bugs in the
context of program synthesis. An attempt to establish incorrectness
by finding counterexamples was outlined by Floyd (1971) as part ot
his proposed system for interactive program writing.
j 4Q LOGICAL ANALYSIS OF PROGRAMS

In our presentation we have basically considered the debugging of


a program with a single loop. For more complicated programs, with
multiple loops, additional research problems present themselves.
What we have introduced here is clearly just a first step toward the
use of invariants in debugging.

Acknowledgment

We are indebted to Ed Ashcroft, Nachum Dershowitz, Barnard Elspas,


Stephen Ness, Tim Standish, and Richard Waldinger for their critical reading of
the manuscript.
Chapter 3
Knowledge and Reasoning in
Program Synthesis
Zohar Manna and Richard Waldinger

1. Introduction

In this paper we describe some of the knowledge and the reasoning


ability that a system must have in order to construct computer pro¬
grams automatically. We believe that such a system needs to embody
a relatively small class of reasoning and programming tactics com¬
bined with a great deal of knowledge about the world. These tactics
and this knowledge are expressed both procedurally (i.e., explicity
in the description of a problem-solving process) and structurally (i.e.,
implicitly in the choice of representation). We consider the ability
to reason as central to the program synthesis process, and most of
this paper is concerned with the incorporation of common-sense
reasoning techniques into a program synthesis system.
We regard program synthesis as a part of artificial intelligence.
Many of the abilities we require of a program synthesizer, such as
the ability to represent knowledge or to draw common-sense con¬
clusions from facts, we would also expect from a natural-language
understanding system or a robot problem solver. These general
problems have been under study by researchers for many years, and
we do not expect that they will all be solved in the near future.
However, we still prefer to address these problems rather than

This is a revised version of a previously published article by the same name which ap¬
peared in Artificial Intelligence, vol. 6, pp. 175-208. Copyright 1975 by North-Holland
Publishing Company, Amsterdam. Reprinted by permission of publisher.

141
142 PROGRAM SYNTHESIS

restrict ourselves to a more limited program synthesis system without


those abilities.
Thus, although implementation of some of the techniques in this
paper has already been completed, others require further develop¬
ment before a complete implementation will be possible. We imagine
the knowledge and reasoning tactics of the system to be expressed in
a planner-type language (Hewitt, 1971); our own implementation
is in the qlisp language (Wilber, 1976). Further details on the imple¬
mentation are discussed in Section 5.1.
Section 2 of the paper gives the basic techniques of reasoning for
program synthesis. They include the formation of conditional tests
and loops, the satisfaction of several simultaneous goals, and the
handling of instructions with side effects. Section 3 applies the
techniques of Section 2 to synthesize a nontrivial “pattemmatcher”
that determines whether a given expression is an instance of a given
pattern. Section 4 demonstrates the modification of programs. We
take the pattern matcher we have constructed in Section 3 and adapt
it to construct a more complex program; a “unification algorithm”
that determines whether two patterns have a common instance. In
Section 5 we give some of the historical background of automatic
program synthesis, and we compare this work with other recent efforts.

2. Fundamental Reasoning

In this section we will describe some of the reasoning and program¬


ming tactics that are basic to the operation of our proposed synthe¬
sizer. These tactics are not specific to one particular domain; they
apply to any programming problem. In this class of tactics, we
include the formation of program branches and loops and the hand¬
ling of statements with side effects.

2.1. Specification and Tactics Language

We must first say something about how programming problems are


to be specified. In this discussion we consider only correct and exact
specifications in an artificial language. Thus, we will not discuss
input-output examples [cf. Hardy (1975), Summers (1976)] traces
[cf. Biermann and Krishnaswamy (1976)], or natural-language de-
Fundamental Reasoning 143

scriptions as methods for specifying programs: nor will we consider


interactive specification of programs [cf. Balzer (1972)]. Neither
are we limiting ourselves to the first-order predicate calculus [cf.
Kowalski (1974)]. Instead, we try to introduce specification con¬
structs that allow the natural and intuitive description of program¬
ming problems. We therefore include constructs such as
Find x such that P(x)
and the ellipsis notation, e.g.,
A[\], A[2\, ..., A[n],
Furthermore, we introduce new constructs that are specific to cer¬
tain subject domains. For instance, in the domain of sets we use

{* I P(x)}
for “the set of all x such thatP(x)”. As we introduce an example we
will describe features of the language that apply to that example.
Since the specification language is extendible, we can introduce
new constructs at any time.
We use a separate language to express the system’s knowledge and
reasoning tactics. In the paper, these will be expressed in the form
of rules written in English. In our implementation, the same rules are
represented as programs in the qlisp programming language. When a
problem or goal is presented to the system, the appropriate rules
are summoned by “pattern-directed function invocation” (Hewitt,
1971). In other words, the form of the goal determines which rules
are applied.
In the following two sections we will use a single example, the
synthesis of the set-theoretic union program to illustrate the for¬
mation both of conditionals and of loops. The problem here is to
compute the union of two finite sets, where sets are represented as
lists with no repeated elements.
Given two sets, 5 and t, we want to express
union(s t) = {x \ x E s or x €= t]
in a lisp-like language. We expect the output of the synthesized pro¬
gram to be a set itself. Thus
union((A B) (B C)) - (ABC).
We do not regard the expression {x | x G s or x £ t) itsell as a proper
144 PROGRAM SYNTHESIS

program: the operator { | ...} is a construct in our specification


language but not in our lisp-like programming language. We assume
that the programming language does have the following functions:

head(l) = the first element of the list/. Thushead((A B C D))= A.


tail(l) = the list of all but the first element of the list /. Thus
tail((A B CD)) = (B CD).1
add(x s) = the set consisting of the element x and the elements of
the set s. Thus add(A (B C D)) = (A B C D) whereas
add(B (B CD)) = (B CD).
empty(s) is true if s is the empty list, and false otherwise.

Our task is to transform the specification for union into an equiva¬


lent algorithm in this programming language.
We assume the system has some basic knowledge about sets, such
as the following rules:

(1) x G s is false if empty(s).


(2) 5 is equal to add(head(s) tail(s)) if ~empty(s).
(3) x G add(s t) is equivalent to (x = s orx G t).
(4) {x | x G 5}is equal to s.
(5) {x | x = a or Q(x)} is equal to add(a {x \ £>(x)}).

We also assume that the system knows a considerable amount of pro-


positional logic, which we will not mention expliticly.
Before proceeding with our example we must discuss the forma¬
tion of conditional expressions.

2.2. Formation of Conditional Expressions

In addition to the above constructs, we assume that our program¬


ming language contains conditional expressions of the form

rf , \r if p is false,
(it p then q else r) = I
[ q otherwise.

The conditional expression is a technique for dealing with uncer-

1 Since sets are represented as lists, head and tail may be applied to sets as well as lists.
Their value then depends on our actual choice of representation.
Fundamental Reasoning 145

tainty. Suppose, in constructing a program, we want to know if con¬


dition p is true or false, when in fact p may be true on some occa¬
sions and false on others, depending on the values of the program’s
arguments. The human programmer faced with this problem is likely
to resort to “hypothetical reasoning”: he will assume p is false and
write a program r that solves his program in that case; then he will
assume p is true and write a program q that works in that case; he
will then put the two programs together into a single program

(if p then q else r).

Conceptually he has solved his problem by splitting his world into


two worlds: the case in which p is true and the case in which p is
false. In each of these worlds, uncertainty is reduced. Note that we
must be careful that the condition p on which we are splitting the
world is computable in our programming language; otherwise, the
conditional expression we construct also will not be computable.
We can now proceed with the synthesis of the union function.
Our specifications were
union(s t) = {x IxGiorxGf}.

We begin to transform these specifications using our rules. Rule (1)


applies to the subexpression x G s, generating a subgoal, empty is).
We cannot prove s is empty-this depends on the input-and there¬
for e this is an occasion for a hypothetical world split. (We know that
emptyis) is a computable condition because empty is a primitive
in our language.) In the case in which s is empty, the expression

{x UGioriGf}

therefore reduces to
(x | false orxGf},

or, by propositional logic,


{x | x € t}.
Now rule (4) reduces this to t, which is one of the inputs to our
program and therefore is itself an acceptable program segment in our
language.
In the other world-the case in which s is not empty-we cannot
solve the problem without resorting to the recursive loop formation
146 PROGRAM SYNTHESIS

mechanism, which is the subject of the next section. However, we


know at this point that the program will have the form

unionis t) = if empty(s)
then t
else. .
where the else clause will be whatever program segment we construct
for the case in which s is not empty.

2.3. Formation of Loops

The term "‘loop” includes both iteration and recursion; however,


in this paper we will only discuss recursive loops [cf. Manna and
Waldinger (1971)]. Intuitively, we form a recursive call when, in the
course of working on our problem, we generate a subgoal that is
identical in form to our top-level goal. For instance, suppose our top-
level goal is to construct the program reverse(/) that reverses the
elements of the list / [e.g., reverse(A (B Q D)=(D (B C) A)]. If in the
course of constructing this program we generate the subgoal of
reversing the elements of the list tail(l), we can use the program we
are constructing to satisfy this subgoal. In other words, we can
introduce a recursive call reverse(tail(l)) to solve the subsidiary prob¬
lem. We must always check that a recursive call does not lead to an
infinite recursion. No such infinite loop can occur here, because the
input tailil) is “shorter’’ than the original input I.
Let us see how the technique applies to our union example. Con¬
tinuing where we left off in the discussion of conditionals, we
attempt to expand the expression

(x | x G 5 or x G t)

in the case in which s is not empty. Applying rule (2) to the sub¬
expression s, we can expand our expression to

(x | x G add(head(s) tail(s)) or xG/}.


This is transformed by rule (3) into

(x I x = head(s) orx G tail(s) orxGf}.


Using rule (5), this reduces to

add(head(s) {x |xG tail(s) orx G t}).


Fundamental Reasoning 147

If we observe that
{x | x £ tail(s) orx £ t]
is an instance of the top-level subgoal, we can reduce it to
union(tail(s) t).

Again, this recursive call leads to no infinite loops, since tail(s) is


shorter than 5. Our completed union program is now

union(s t) = if empty(s)
then t
else add(head(s) union(tail(s) t)).

As presented in this section, the loop formation technique can


only be applied if a subgoal is generated that is a special case of the
top-level goal. We shall see in the next section how this restriction
can be relaxed.

2.4. Generalization of Specifications


When proving a theorem by mathematical induction, it is often
necessary to strengthen the theorem in order for the induction to
“go through”. Even though we have an apparently more difficult
theorem to prove, the proof is facilitated because we have a stronger
induction hypothesis. For example, in proving theorems about
lisp programs, the theorem prover of Boyer and Moore (1975)
often automatically generalizes the statement of the theorem in
the course of a proof by induction.
A similar phenomenon occurs in the synthesis of a recursive pro¬
gram. It is often necessary to strengthen the specifications of a
program in order for that program to be useful in recursive calls.
We believe that this ability to strengthen specifications is an
essential part of the synthesis process, as many of our examples will
show.
For example, suppose we want to construct a program to reverse a
list. A good recursive reverse program is
reversed) = rev(l ()),
where
rev(l m) = if empty{l)
then m
else rev(tail(l) head(l)-m).
148 PROGRAM SYNTHESIS

Here () is the empty list, and x • / is the list formed by inserting x


before the first element of the list / [e.g,,A • (B C D) = (A B CD)}.
Note that rev(l m) reverses the list / and appends it onto the list ra,e.g.,
rev((A B Q (D E )) = (C B A D E).
This is a good way to compute reverse: it uses very primitive lisp
functions, and its recursion is such that it can be compiled without
the use of a stack. However, writing such a program entails writing
the function rev, which is apparently more general and difficult
to compute than reverse itself, since it must reverse its first argu¬
ment as a subtask. Actually, the more general program rev is easier to
construct, and the synthesis of the reverse function involves
generalizing the original specifications of reverse into the specifica¬
tions of rev.
The reverse function requires that the top-level goal be generalized
in order to match the lower-level goal. Another way to strengthen
the specifications is to propose additional requirements for the pro¬
gram being constructed. For instance, suppose in the course of the
synthesis of a function f(x), we generate a subgoal of the form P(f(a)),
where f(a) is a particular recursive call. If we cannot prove P(f(a)), it
may still be possible to strengthen the specifications for/O) so as to
also satisfy P(f(x)) for all x This step may require that we actually
modify portions of the program /that have already been synthesized
in order to satisfy the new specification P. The recursive call to the
modified program will then be sure to satisfy P(f(a)). This process
will be illustrated in more detail during the synthesis of the pattern
matcher in Section 3.
The recursion-introduction mechanism presented here has been
developed independently by Burstall and Darlington (1977).

2.5. Conjunctive Goals

The problem of solving conjunctive goals is the problem of con¬


structing an output that satisfies two (or more) constraints. The
general form for this problem is

Find z such that ,P(z) and Q(z).

The conjunctive-goal problem is difficult because, even if we have


Fundamental Reasoning 149

methods for solving the goals


Find z such that P(z)
and

Find z such that Q(z)


independently, the two solutions may not merge together nicely
into a single solution. Moreover, there seems to be no way of
solving the conjunctive-goal problem in general; a method that works
on one such problem may be irrelevant to another.
We will illustrate one instance of the conjunctive-goal problem:
the solution of two simultaneous linear equations. Although this
problem is not itself a program synthesis problem, it could be re¬
phrased as one. Moreover, the difficulties involved and the technique
to be applied extend to many real synthesis problems, such as
the pattern-matcher synthesis of Section 3. Suppose our problem
is the following:

Find (zx, z2> such that 2 z2 = z2 + 1 and 2z2 = zx +2.

Suppose further that although we can solve single linear equations


with ease, we have no built-in package for solving sets of equations
simultaneously. We may try first to find a solution to each equation
separately. Solving the first equation, we might come up with

<Zi , z2> = <1, 1),


whereas solving the second equation might give

<Zi, z2> = (2, 2).

There is no way of combining these two solutions. Furthermore, it


does not help matters to reverse the order in which we approach the
two subgoals. What is necessary is to make the solution of the tirst
goal as general as possible, so that some special case of the solution
might satisfy the second goal as well. For instance, a “general”
solution to the first equation might be

<1 + vv, 1 + 2w> for any w.

This solution is a generalization of our earlier solution <1, 1>. The


problem is now to find a special case of the general solution that also
solves the second equation. In other words, we must find a w such
150 PROGRAM SYNTHESIS

that
2(1 + 2w) = (1 + w) + 2.
This strategy leads us to a solution.
Of course, the method of generalization does not apply to all
conjunctive-goal problems. For instance, the synthesis of an integer
square-root program has specifications

Find z such that


z is an integer and
z2 < x and
(z + l)2 >x,
where x > 0.

The above approach of finding a general solution to one of the


conjuncts and plugging it into the others is not effective in this
case.

2.6. Side Effects

Up to now we have been considering programs in a lisp-like lan¬


guage: these programs return a value but effect no change in any
data structure. In the next two sections we will consider the syn¬
thesis of programs with “side effects” that may modify the state
of the world.
For instance, a Lisp-like program to sort two variables x and y
would return as its value a list of two numbers, either (x y) or
(y x), without altering the contents of x and y. On the other hand,
a program with side effects to sort x and y might change the contents
of x and y.
In order to indicate that a program with side effects is to be con¬
structed, we provide a specification of form

Achieve P.

This construct means that the world is to be changed so as to make


P true. For instance, if we specify a program

Achieve x = y.

we intend that the program actually change the value of x or y, say


by an assignment statement. However, if we specify

Find x such that x = y,


Fundamental Reasoning 151

the program constructed would return the value of y, but would not
change the value of x ory.
Many of the techniques we used in the synthesis of lisp-like
programs also apply to the construction of programs with side effects.
In particular, we can use pattern-directed function invocation to re¬
trieve tactical knowledge. The synthesis of the program in the follow¬
ing example has the same flavor as our earlier union example, but
involves the introduction of side effects.
The program sort(x y) to be constructed is to sort the values of
two variables x and y. For simplicity we will allow the use of the
statement interchangeix y) to exchange the values of x and y, instead
of the usual sequence of assignment statements. Our specification
will be simply

Achievex <y.

Strictly speaking, we should include in the specification the addi¬


tional requirement that the set of values of x and y after the sort
should be the same as before. However, we will not consider such
compound goals until Section 2.8, and we can achieve the same
effect by requiring that the interchange statement be the only
instruction with side effects that appears in the program.
The first step in achieving a goal is to see if it is already true.
(If a goal is a theorem, for instance, we do not need to construct a
program to achieve it.) We cannot prove x < y, but we can use it as
a basis for a hypothetical world split. This split corresponds to a
conditional expression in the program being constructed. In How-
chart notation the conditional expression is written as a program
branch:

At point 2 our goal is already achieved. At point 3 we know that


~(x < y), i.e., x > y. To achieve x < y, it suffices to establish x <
y, but this may be achieved by executing interchange^ y). Thus we
152 PROGRAM SYNTHESIS

have jc < y in both worlds, and the final program is therefore:

This example introduced no difficulties that our lisp-like program


synthesis techniques could not handle. However, in general, programs
with side effects must be given special treatment because of the
necessity for representing changes in the world. It is important to
be able to determine whether a given assertion is always true at a
given point in a program. To this end we study the relationship
between assertions and program constructs in the next section.

2.7. Assertions and Program Constructs

Suppose a program contains an assignment statement

x
’1
*- y

and we wish to determine if x < 3 at Point 2. In order to do this it


suffices to check if what we know at Point 1 implies that y < 3. In
general, to determine an assertion of form P(x) at Point 2, check
P(y) at Point 1. We will say that the assertion P(y) is the result of
“passing back” the assertion P(x) from Point 2 to Point 1. [This
is precisely the process outlined by Floyd (1967) and Hoare (1969).]
Furthermore, if our program contains the instruction

1'

interchange (x y)

2
Fundamental Reasoning 153

and we wish to establish x < y at Point 2 we must check if y < x


at Point 1. In general, an assertion of form P(x y) results in an
assertion of form P(y x) when passed back over interchange(x y).
Suppose the program being constructed contains a branch

To determine if an assertion Q is true at Point 2, it suffices to check


whether

Q if P
(i.e., P D Q) is true at Point 1. In order to determine if R is true at
Point 3, it suffices to check whether

R if
(i.e., ~P 2) R) is true at Point 1.
Suppose two control paths join in the program being constructed:

Thus to determine if assertion P is true at Point 3, it is sufficient to


check that P be true at both Point 1 and Point 2.
Assertions may be passed back over complex programs. For in¬
stance, let us pass the assertion y<z back over the program sort{x y)
which we constructed in the previous section. (See Figure 3.1.) By
combining the methods that we have just introduced for passing
assertions back over program constructs, we can see that in order to
establish y < z at Point 5, it is necessary to check that (y < z if x <
y) and (x < z if ~(x < y)) are true at Point 1.
Often the specification of a program will require the simultaneous
satisfaction of more than one goal. As in the case of conjunctive
goals in lisp-like programs, the special interest of this problem lies in
154 PROGRAM SYNTHESIS

y < z if x < y

-I x < z if ~(x < y)

T F

3 x < z

y « z-'2 interchange (x y)

4 --y < z

5 -y < z

Figure 3.1.

the interrelatedness of the goals. The techniques of this section will


now be applied to handle the interaction between goals.

2.8. Simultaneous Goals

A simultaneous-goal problem has the form


Achieve P and Q.
Sometimes P and Q will be independent conditions, so that we can
achieve P and Q simply by achieving P and then achieving Q. For
example, if our goal is

Achieve x = 2 and y = 3,
the two goals x = 2 and y = 3 are completely independent. In this
section, however, we will be concerned with the more complex case
in which P and Q interact. In such a case we may make P false in the
course of achieving Q.
Consider for example the problem of sorting three variables
x, y, and z. We will assume that the only instruction we can use is
the subroutine sort(u v), described in the previous section, which
sorts two variables. Our goal is then

Achieve x < y and y < z.


We know that the program sort(u v) will achieve a goal of form u <
Fundamental Reasoning 155

v. If we apply the straightforward technique of achieving the con¬


junct x first, and then the conjunct y < z, we obtain the program

sortix y)
sortiy z).

However, this program has a bug in that sorting y and z may disrupt
the relation x < y: if z is initially the smallest of the three, in inter¬
changing y and z we make y less than x. Reversing the order in which
the conjuncts are achieved does not solve the problem.
There are a number of ways in which this problem may be re¬
solved. One of them involves the notion of program modification
[cf. Sussman (1975)]. The general strategy is as follows: to achieve
P and Q simultaneously, first write a program to achieve P; then
modify that program to achieve Q as well. The essence of this strat¬
egy, then, lies in a technique of program modification.
Let us see how this strategy applies to the simple sort problem.
The specification is

Achieve x < y and y < z.

It is easy to achieve x < y; the program sort(x y) will do that im¬


mediately. We must now modify the program sort(x y) to achieve
y < z without disturbing the relation x < y we have just achieved.
In other words, we would like to “protect” the relation x <y. We
have seen that simply achieving y < z after achieving x <y is impos¬
sible without disturbing the protected relation. Therefore we will
pass the goal y < z back to the beginning of the program sortix y)
and try to achieve it there, where there are no protected relations.
We have seen in the previous section that the goal y < z passed
back before the program sort(x y) results in two goals:

(i) y < z if x < y, and


(ii) x < z if ~(jc < y).

Both of these goals must be achieved before applying sort{x y). We


can achieve (i) by applying sort(y z). (This will achieves < z whether
156 PROGRAM SYNTHESIS

or not jc < y.) Our program so far is thus

|
3I
sort (y z)

sort (x y)

We still need to achieve goal (ii) at Point 2; we can achieve this goal
simply by inserting the instruction sortix z) before Point 2.

This modification will not effect Relation O'), “y < z if jc < y”,
which is protected at Point 2, because after executing sortiy z)
and sort(x z), the value of z will be the largest of the three. Thus the
desired program is

sort(y z)
sort(x z)
sort{x y).

If the subgoals are pursued in a different order, different variations


on this program are obtained.
The program-modification strategy seems to be a fairly general
approach to the simultaneous-goal problem. It also is a powerful
Program Synthesis: The Pattern Matcher 157

program synthesis technique in general, as we will see when we de¬


velop the unification algorithm in Section 4.
This concludes the presentation of our basic program synthesis
techniques. In the next part we will show how these same techniques
work together in the synthesis of a more complex example.

3. Program Synthesis: The Pattern Matcher

We will present the synthesis of a simple pattern matcher to show


how the concepts discussed in the previous section can be applied to
a nontrivial problem. Later, in Section 4, we shall show how we can
construct a more complex program, the unification algorithm of
Robinson (1965), by modifying the pattern-matching program we
are about to synthesize. We must first describe the data structures
and primitive operations involved in the pattern matching and
unification problems.

3.1. Domain and Notation

The main objects in our domain are expressions and substitutions.

3.1.1. Expressions
Expressions are atoms or nested lists of atoms; e.g., (A B (X C) D) is
an expression. An atom may be either a variable or a constant. (In
our examples we will use A, B, C, . . . for constants and U, V, W, . . .
for variables.) We have basic predicates atom, var, and const to
distinguish these objects:
atom(l) = / is an atom,
var(l) = l is a variable,
const(l) = / is a constant.

To decompose an expression, we will use the primitive functions


headil) and tail(l), defined when / is not an atom.

head(l) is the first element of /.


tail(l) is the list of all but the first element of /.
158 PROGRAM SYNTHESIS

Thus
head(((A (X) B) C (D X))) = (A (X) B),
tail«(A (X) B) C (D X))) = (C (D X)).
We will abbreviate head(l) as lh and tail(l) as /f.
To construct expressions we have the function: if / is any
expression and m is a nonatomic expression, l * m is the expression
formed by inserting / before the first element of m. For example,
A • (B CD) = (A B CD),
(A (X) B) • (C (D X)) = (C4 (X) B) C (D X)).
The predicate occursin(x l) is true if x is an atom that occurs
in expression / at any level, e.g.,
occursin(A (C (B (A) B) C)) is true,
but
occursin{X Y) is false.
Finally, we will introduce the predicate constexp(l), which is true
if / is made up entirely of constants. Thus
constexp((A (B) C (D E))) is true.
but

constexp(X) is false.
Note that constexp differs from const in that constexp may be true
for nonatomic expressions.

3.1.2. Substitutions

A substitution replaces certain variables of an expression by other


expressions. We will represent a substitution as a list of pairs. Thus

«X (A B)) (Y (C n»
is a substitution.
The instantiation function inst(s l) applies the substitution s to the
expression /. For example, if

s = ((X(A B)) (Y (C T)» and / = (X (A Y) X),


then

inst(s l) = ((A B) (A (C T)) (A B)).


Program Synthesis: The Pattern Matcher 159

Note that the substitution is applied by first replacing all occurrences


of X simultaneously by (A B) and then all occurrences of Y simulta¬
neously by (C Y). Thus, if

s’ = ((X Y)(Y O),


then
inst(s' l) = (C (A C) C).
The empty substitution A is represented by the empty list of pairs.
Thus for any expression I,
inst( A /) = /.
We regard two substitutions ,sy and s2 as equal (written Sj = s2)
if and only if
inst(s1 l) = inst(s2 l)
for every expression /. Thus
((XY)(YO) and «A C) (Y C»
are regarded as equal substitutions.
We can build up substitutions by using the functions pair and o
(composition): If v is a variable and t an expression, pair(v t) is the
substitution that replaces v by t; i.e.,
pairiv t) = ((v t)).
If sy and s2 are two substitutions, .sy o s2 is the substitution with the
same effect as applying .sy followed by s2 . Thus

instisx ° s2 /) = inst(s2 instisx /)).


For example if
Sl = ((X A) (Y B )) and s2 =((ZO(XD)),
then
st ° s2 = ((X A) (Y B) (Z C)).
Note that for the empty substitution A,
A°s = s°A = s

for any substitution s.

3.2. The Specifications

The problem of pattern matching may be described as follows. We


are given two expressions, pat and arg. While pat can be any expres-
160 PROGRAM SYNTHESIS

sion, arg is assumed to contain no variables; i.e., constexp(arg) is


true. We want to find a substitution z that transforms pat into arg,
i.e., such that
inst(z pat) = arg.

We will call such a substitution a match. If no match exists, we want


the program to return the distinguished constant NOMATCH. For
example, if
pat is (X A (Y B)) and arg is (C A (D B)),
we want the program to find the match
((X C) (Y D)).
On the other hand, if
pat is (X A (X B)) and arg is (B A (D B)),

then no substitution will transform pat into arg, because X cannot be


matched with both B and D. Therefore the program should yield
NOMATCH. (This version of the pattern matcher is simpler than the
pattern matching algorithms usually implemented in programming
languages because of the absence of “sequence” or “fragment”
variables. Our variables must match exactly one expression, whereas
a fragment variable may match any number of expressions.)
In mathematical notation the specifications for our pattern
matcher are:

Goal 1. match(pat arg) = Find z such that inst(z pat) = arg


else z = NOMATCH,

where “Find z such that P(z) else Q(z)” means construct an output z
satisfying P(z) if one exists; otherwise, find a z such that Q(z).
The above specifications do not completely capture our inten¬
tions; for instance, if
pat is (X Y) and arg is (A B),
then the substitution

z = ((X A) (Y B) (Z C>)
will satisfy our specifications as well as

z = ((XA) <YB».
Program Synthesis: The Pattern Matcher 161

We have neglected to include in our specifications that no sub¬


stitutions should be made for variables that do not occur in pat.
We will call a match that satisfies this additional condition a most
general match.
An interesting characteristic of the synthesis we present is that
even if the user does not require that the match found be most
general, the system will strengthen the specifications automatically
to imply this condition, using the method outlined in Section 2.4.
Therefore we will begin the synthesis using the weaker specifications.

3.3. The Base Cases

Rather than list all the knowledge we require in a special section at


the beginning, we will mention a rule only when it is about to be
used. Furthermore, if a rule seems excessively trivial we will omit
it entirely. The general strategy is to first work on

Goal 2. Find z such that inst{z pat) = arg.

If this is found to be impossible (i.e., if it is proven that no such


z exists), we will work on

Goal 3. Find a z such that z = NOMATCH;

which is seen to be trivially satisfied by taking z to be NOMATCH.


Thus, from now on we will be working primarily on Goal 2. How¬
ever, in working on any goal we devote a portion of our time to
showing that the goal is impossible to achieve. When we find cases in
which Goal 2 is proven impossible, we will automatically return
NOMATCH, which satisfies Goal 3.
We have in our knowledge base a number of rules concerning inst,
including

Rule 1. inst(s x) = x for any substitution 5 if constexp(x)


Rule 2. inst(pair(u t) v) - t if var(u).

We assume that these rules are retrieved by pattern-directed func¬


tion invocation on Goal 2. Rule 1 can be applied only in the case
that constexp(pat) and pat = arg. We cannot prove either of these
162 PROGRAM SYNTHESIS

conditions; their truth or falsehood depends on the particular inputs


to the program. We use these predicates as conditions for a hypothet¬
ical world split. In the case that both of these conditions are true,
Rule 1 tells us that any substitution is a satisfactory match. We will
have occasion to tighten the specifications of our program later;
as they stand now, we will simply return “any”, so as not to restrict
our choice. The portion of the program we have constructed so far
reads
match(pat arg) =
if constexp(pat)
then if pat = arg
then any
else ... .
On the other hand, in the case constexp(pat) and pat =£ arg, Rule 1
tells us that
inst(z pat) = pat arg

for any z. Hence we are led to satisfy Goal 3 by returning


NOMATCH.
We now consider the case
~ const expipat).
Rule 2 establishes the subgoal
var(pat).
This is another occasion for a hypothetical world split. When var(pat)
is true, the program must return pair(pat arg); the program we have
constructed so far is

match(pat arg) =
if constexp(pat)
then if pat = arg
then any
else NOMATCH
else if var(pat)
then pairipat arg)
else ... .

Hencefore we assume ~var(pat). Recall that we have been assuming


also that ~constexp(pat). To proceed we make use of the following
Program Synthesis: The Pattern Matcher 163

additional knowledge about the function inst:

Rule 3. inst(s x • y) = inst(s x) • inst(s y)


for any substitution s.

This rule applies to our Goal 2 if pat = x • y for some expressions


x and y. We have some additional knowledge about expressions in
general:

Rule 4. u = uh • ut if ~atom{u).

Recall that uh is an abbreviation for headiu) and ut is an abbrevi¬


ation for tail(u).

Rule 5. u A u • w for any v and w if atom(u).

Using Rule 4, we generate a subgoal

~atom(pat).

Since we have already assumed ~ const exp (pat) and ~var(pat), we


can actually prove ~atom(pat) using knowledge in the system. There¬
fore pat = path * Patt, and using Rule 3 our Goal 2 is then reduced to

Goal 4. Find z such that inst(z path) • inst(z patt) = arg.

We now make use of some general list-processing knowledge.

Rule 6. To prove x • y = u • v, prove x = u and y = v.

Applying this rule, we generate a subgoal to show that

arg = u • v

for some u and v. Applying Rule 4, we know this is true with u =


argh and v - argt if
~atom(arg).

This is another occasion for a hypothetical world split.


164 PROGRAM SYNTHESIS

Thus, by Rule 6, in the case that ~atom(arg), our subgoal reduces


to

Goal 5. Find z such that

inst(z path) = arSh


and

inst(z patt) = argt.

We will postpone treatment of this goal until after we have con¬


sidered the other case, in which
atom(arg)
holds. In this case Rule 5 tells us that
inst(z path) • inst(z patt) A arg

for any z. Hence, our goal is unachievable in this case, and we can
return NOMATCH.
The program so far is

match(pat arg) =
if constexpipat)
then if pat = arg
then any
else NOMATCH
else if var(pat)
then pair{pat arg)
else if atom(arg)
then NOMATCH
else... .

For the as yet untreated case neither pat nor arg is atomic. Hence¬
forth using Rule 4 we assume that pat is path * patt and arg is
argh • argt.

3.4. The Inductive Case

We will describe the remainder of the synthesis in less detail,


because the reader has already seen the style of reasoning we have
been using. Recall that we had postponed our discussion of Goal 5
Program Synthesis: The Pattern Matcher 165

in order to consider the case in which arg is atomic. Now that we


have completed our development of that case, we resume our work
on Goal 5:

Find z such that inst(z path) = argh and inst(z patt) = argf.

This is a conjunctive goal, and is treated analogously to the goal in


the simultaneous-linear-equations example (Section 2.5): The system
will attempt to solve the first conjunct, using a recursive call to the
pattern-matcher itself.
The interaction between the two conjuncts is part of the challenge
of this synthesis. It is quite possible to satisfy each conjunct separ¬
ately without being able to satisfy them both together. For example,
if pat = {X X) and arg = (A B), then path = X, patt = (A), argh =
A, and argt = (B). Thus z = ((X A)) satisfies the first conjunct, z =
((X B)) satisfies the second conjunct, but no substitution will satisfy
both conjuncts, because no substitution can match X against both
A and B. Some mechanism is needed to ensure that the expression
assigned to a variable in solving the first conjunct is the same as the
expression assigned to that variable in solving the second conjunct.
There are several ways to approach this difficulty. For instance,
the programmer may satisfy the two conjuncts separately and then
attempt to combine the two substitutions thereby derived into a
single substitution. Or he may actually replace those variables in patt
that also occur in path by whatever expressions they have been
matched against, before attempting to match patt against argt. Or
he may simply pass the substitution that satisfied the first conjunct
as a third argument to the pattern matcher in working on the second
conjunct. The pattern matcher must then check that the matches
assigned to variables are consistent with the substitution given as the
third argument.
We will examine in this section how a system would discover the
second of these methods. A similar system could also discover the
third method. We will not consider the first method here because it
is not easily adapted to the unification problem.
Our strategy for approaching the conjunctive goal is as follows.
We will consider the first conjunct independently:

Goal 6. Find z such that inst(z path) = argh .


166 PROGRAM SYNTHESIS

If we find a z that satisfies this goal, we will substitute that z into


the second conjunct, giving

Goal 7. Prove inst(z patt) = argt.

If we are successful in Goal 7, we are done; however, if we fail,


we will try to generalize z. In other words, we will try to find a
broader class of substitutions that satisfy Goal 6 and from these
select one that also satisfies Goal 7. This is the method we intro¬
duced to solve conjunctive goals in Section 2.5.
Applying this strategy, we begin work on Goal 6. We first use a
rule that relates the construct
Find z such that P(z)
to the construct

Find z such that P(z) else Q(z).

Rule 7. To find z such that P(z), it suffices to find zx such that


P(z1) else Q(zx), and ~(?(z1) for some predicate Q

This rule, applied to Goal 6, causes the generation of the subgoal

Goal 8. Find zx such that inst(zx path) = argh else Q(zx), and
~Q(z1)-

The first conjunct of this subgoal matches the top-level Goal 1,


where Q{zx) is zx = NOMATCH. This suggests establishing a recur¬
sion at this point, taking

zx = match(path argh).
Henceforth we will use zx as an abbreviation for match{path argh).
Termination is easily shown, because both path and argh are proper
subexpressions of pat and arg, respectively. It remains to show,
according to Rule 7, that zx =£ NOMATCH. This causes another
hypothetical world split: in the case zx = NOMATCH (i.e., no
substitution can cause path and argh to match)5 we can show that
no substitution can cause pat and arg to match either, and hence
can take z = NOMATCH.
Program Synthesis: The Pattern Matcher 167

We have thus constructed the following new program segment

zx match(path argh)
if zx = NOMATCH
then NOMATCH
else. . .

We have used zx as a program variable to improve readability.


The actual program constructed would use match(path argh) itself
in place of zx.
On the other hand, if zx i= NOMATCH, we know that zx satisfies
the first conjunct (Goal 6). Thus, in keeping with the conjunctive
goal strategy, we try to show that zx satisfies the second conjunct
(Goal 7) as well, i.e.,
inst(zx patt) = argt.
However, we fail in this attempt; in fact we can find sample inputs
pat and arg that provide a counterexample to Goal 7 [e.g., pat =
(A X), arg = (A B), zx = A]. Thus we go back and try to generalize
our solution to Goal 6.
We already have a solution to Goal 6: we know inst(zx path) =
argh. We also can deduce that constexp(argh), because we have
assumed constexp{arg). Hence Rule 1 tells us that
inst(z2 argh) = argh

for any substitution z2 . Therefore


inst(z2 inst{zx path)) = argh ,

i.e.,
inst(zi ° z2 path) = argh

for any substitution z2. Thus having one substitution zx that satisfies
Goal 6, we have an entire class of substitutions, of form zx 0 z2,
each of which satisfies Goal 6. These substitutions may be considered
to be “extensions” of zx ; although zx itself may not satisfy Goal 7,
perhaps some extension of zx will.
The above reasoning is straightforward enough to justify, but
further work is needed to motivate a machine to pursue it.
It remains now to find a single z2 such that zx ° z2 satisfies Goal
168 PROGRAM SYNTHESIS

7, i.e.,

Goal 9. Find zx such that inst{z1 ° z2 patt) = argt, or equivalently,


Find z2 such that inst(z2 inst(z1 patt)) = argt.
Applying Rule 7, we establish a new goal:

Goal 10. Find z2 such that inst(z2 inst{z1 patt)) = argt else Q(z2),
and ~0(z2).

The first conjunct of this goal is an instance of our top-level goal,


taking pat to be inst(z1 patt), arg to be argt, and Q(z2) to be z2 =
NOMATCH. Thus we attempt to insert the recursive call

z2 <- match{inst{z1 patt) argt)


into our program at this point. (Again, the introduction of z2 as a
program variable is for notational simplicity.) However, we must
first establish

~Q(z2),
i.e.,
z2 =£ NOMATCH.

We cannot prove this: it is true for some examples and false for
others. Therefore we split on this condition.
In the case z2 A NOMATCH Goal 10 is satisfied. Thus z2 also
satisfies Goal 9, and z = z1 ° z2 satisfies Goal 7.
Our program so far is

matchipat arg) =
if constexp(pat)
then if pat = arg
then any
else NOMATCH
else if var(pat)
then pair(pat arg)
else if atom(arg)
then NOMATCH
else z1 •*- match(path argh)
if Zj = NOMATCH
Program Synthesis: The Pattern Matcher 169

then NOMATCH
else z2«- match(inst(z1 patt) argt)
ifz2 = NOMATCH
then...
else z1 ° z2.

3.5. The Strengthening of the Specifications

We have gone this far through the synthesis using the weak specifi¬
cations, i.e., without requiring that the match found be most
general. In fact, the match found may or may not be most general,
depending on the value taken for the unspecified substitution “any”
produced in the very first case. The synthesis is nearly complete.
However, we will be unable to continue it without strengthening
the specifications and modifying the program accordingly. We now
have only one case left to consider. This is the case in which
z2 = NOMATCH,
i.e.,
match{inst(zx patt) argt) = NOMATCH.
This means that no substitution w satisfies
inst(w inst(zx patt)) = argt,
or, equivalently,

inst(z1 ° w patt) ^ arSt for every substitution w.


This means that no substitution of form zx ° w could possibly satisfy

inst(zx ° w pat) = arg.

We here have a choice: we can try to find a substitution s not of


form Zj ° w that satisfies.
inst(s path) = argh
and repeat the process; or we could try to show that only a substitu¬
tion s of form Zj ° w could possibly satisfy
inst(s path) - argh,
and therefore we take z = NOMATCH.
Pursuing the latter course, we try to show that the set of substitu-
170 PROGRAM SYNTHESIS

tions s of form zx ° w is the entire set of solutions to


inst(s path) = argh .
In other words, we show that for any substitution
if inst(s path) = argh then s = zx °w for some w.

This condition is equivalent to saying that zx is a most general


match. We cannot prove this about zx itself; however, since zx is
match(patH argh), it suffices to add the condition to the specifi¬
cations for match, as described in Section 2.4. The strengthened
specifications now read

Find z such that


{inst(z pat) = arg and
for all s [if inst(s pat) = arg
then s = z ° w for some w]}
else z = NOMATCH.

Once we have strengthened the specifications it is necessary to go


through the entire program and see that the new, stronger specifi¬
cations are satisfied, modifying the program if necessary. In this
case no major modifications are necessary; however, the assignment
z •«- any
that occurs in the case in which pat and arg are equal and constant
is further specified to read
z «- A.
Our final program is therefore

matchijpat arg) =
if constexp(pat)
then if pat = arg
then A
else NOMATCH
else if var(pat)
then pair(pat arg)
else if atom(arg)
then NOMATCH
elsez! <- match(path argh)
if zx = NOMATCH
Program Modification: The Unification Algorithm 171

then NOMATCH
elsez2 ynatch{inst{zx patt)argt)
if z2 = NOMATCH
then NOMATCH
else z1 ° z2.

The above pattern matcher is only one of many pattern-matchers


that can be derived to satisfy the same specifications. In pursuing
the synthesis the system has made many choices; some of the alter¬
native paths result in a failure to solve the problem altogether,
whereas other paths result in different, possibly better programs.

4. Program Modification: The Unification Algorithm

In general, we cannot expect a system to synthesize an entire


complex program from scratch, as in the pattern-matcher example.
We would like the system to remember a large body of programs that
have been synthesized before, and the method by which they were
constructed. When presented with a new problem, the system should
check to see if it has solved a similar problem before. If so, it may be
able to adapt the technique of the old program to make it solve the
new problem.
There are several difficulties involved in this approach. First, we
cannot expect the system to remember every detail of every syn¬
thesis in its history. Therefore, it must decide what to remember and
what to forget. Second, the system must decide which problems are
similar to the one being considered, and the concept of similarity is
somewhat ill defined. Third, having found a similar program, the
system must somehow modify the old synthesis to solve the new
problem. We will concentrate only on the last of these problems in
this discussion. We will illustrate a technique for program modifica¬
tion as applied to the synthesis of a version of Robinson’s unification
algorithm (1965).

4.1. The Specifications

Unification may be considered to be a generalization of pattern-


matching in which variables appear in both pat and arg. The problem
is to find a single substitution (called a “unifier”) that, when applied
172 PROGRAM SYNTHESIS

to both pat and arg, will yield identical expressions. For instance, if
pat = (X A)

and
arg = (B Y),

then a possible unifier of pat and arg is


((XB)(YA)).
The close analogy between pattern-matching and unification is
clear. If we assume that the system remembers the pattern-matcher
we constructed in Sections 3.2 through 3.5 and the goal structure
involved in the synthesis, the solution to the unification problem is
greatly facilitated.
The specifications for the unification algorithm, in mathematical
notation, are
unify(pat arg) =
Find z such that inst(z pat) = inst(z arg)
else z = NOUNIFY.

4.2. The Analogy with the Pattern Matcher

For purpose of comparison we rewrite the match specifications:


match(pat arg) =
Find z such that inst(z pat) = arg
else z = NOMATCH.

In formulating the analogy, we identify unify with match, pat with


pat, the arg in unify (pat arg) with arg, inst(z arg) also with arg, and
NOMATCH with NOUNIFY. In accordance with this analogy, we
must systematically alter the goal structure of the pattern-matcher
synthesis. For example, Goal 5 becomes modified to read

Find z such that inst(z path) = inst(z argh)


and inst(z patt) = inst (z argt).

In constructing the pattern matcher, we had to break down the


synthesis into various cases. We will try to maintain this case struc¬
ture in formulating our new program. Much of the saving derived
Program Modification: The Unification Algorithm 173

from modifying the pattern matcher instead of constructing the


unification algorithm from scratch arises because we do not have
to deduce the case splitting all over again.
A difficult step in the pattern matcher synthesis involved the
strengthening of the specifications for the entire program. We added
the condition that the match found was to be “most general”. In
formulating the unification synthesis, we will immediately strengthen
the specifications in the analogous way. The strengthened specifica¬
tions read
unify (pat arg) =
Find z such that
{ inst(z pat) = instfz arg) and
for all s [if inst(s pat) = inst(s arg)
then s = z ° w for some w]}
else z = NOUNIFY.

Following Robinson, we will refer to a unifier satisfying the new


condition as a “most general unifier”.
Note that this alteration process is purely syntactic; there is no
reason to assume that the altered goal structure corresponds to a
valid line of reasoning. For instance, the mere fact that achieving
Goal 2 in the pattern-matching program is useful in achieving Goal 1
does not necessarily imply that achieving the corresponding Goal 2'
in the unification algorithm will have any bearing on Goal 1'. The
extent to which the reasoning carries over depends on the soundness
of the analogy. If a portion of the altered goal structure proves to be
valid, the corresponding segment of the program can remain; other¬
wise, we must construct a new program segment.

4.3. The Modification

Let us examine the first two cases of the unification synthesis in


full detail, so that we can see exactly how the modification process
works. In the pattern matcher, we generated the subgoal (Goal 2)
Find z such that inst(z pat) = arg.

The corresponding unification subgoal is


Find z such that inst(z pat) = inst(z arg).
174 PROGRAM SYNTHESIS

In the pattern matcher we first considered the case constexpipat)


where pat = arg. In this case the corresponding program segment
will return A. This segment also satisfies the modified goal in this
case, because
inst(A pat) = inst(A arg).
The system must also check that A is a most general unifier, i.e.,

for all s [if inst(s pat) = inst(s arg)

then s = A ° w for some w].


This condition is easily satisfied, taking w = s. Thus, in this case, the
program segment is correct without any modification.
The next case does require some modification. In the pattern
matcher, when constexpipat) is true and pat =£ arg, z is taken to be
NOMATCH. However, in this case in the unification algorithm
we must check that
inst(s pat) ¥= inst(s arg),
i.e.,
pat =£ inst(s arg)

for any 5, in order to take z = NOUNIFY. Since for the unification


problem arg may contain variables, this condition cannot be satisfied.
We must therefore try to achieve the specifications in some other
way. In this case [where constexpipat)], the specifications of the
unification algorithm reduce to
Find z such that
{pat = inst(z arg) and
for all 5 [if pat = instfs arg)
then s = z 0 w for some w]}
else z = NOUNIFY.
These specifications are precisely the specifications of the pattern
matcher with pat and arg reversed; consequently, we can invoke
match(arg pat) at this point in the program.
The balance of the modification can be carried out in the same
manner. The derived unification algorithm is

unify{pat arg) =
if constexp(pat)
then if pat = arg
then A
else match{arg pat)
Discussion 175

else if var(pat)
then if occursin{pat arg)
then NOUNIFY
else pair(pat arg)
else if atom(arg)
then unify{arg pat)
else Zi <- unify{path argh)
if zx = NOUNIFY
then NOUNIFY
else z2 unify{inst{z1 patt) inst(zx argt))
if z2 = NOUNIFY
then NOUNIFY
else zx ° z2.

Recall that occursin(pat arg) means that pat occurs in arg as a sub¬
expression.
The termination of this program is considerably more difficult
to prove than was the termination of the pattern matcher. However,
the construction of the unification algorithm from the pattern
matcher is much easier than the initial synthesis of the pattern
matcher itself.
Note that the program we have constructed contains a redundant
branch. The expression
if pat = arg
then A
else match(arg pat)

could be reduced to
match(arg pat).
Such improvements would not be made until a later optimization
phase.

5. Discussion

5.1. Implementation
Implementation of the techniques presented in this paper is under¬
way. Some of them have already been implemented. Others will
require further development before an implementation will be
possible.
176 PROGRAM SYNTHESIS

We imagine the rules, used to represent reasoning tactics, to be


expressed as programs in a planner-type language. Our own imple¬
mentation is in qlisp (Wilber, 1976). Rides are summoned by
pattern-directed function invocation.
World splitting has been implemented using the context mecha¬
nism of qlisp , which was introduced in QA4 [Rulifson et al., (1972)].
Although the world splitting has been implemented, we have yet to
experiment with the various strategies for controlling it.
The existing system is capable of producing simple programs such
as the union function, the program to sort two variables from
Section 2, or the loop-free segments of the pattern-matcher from
Section 3.
The generalization of specifications (Section 2.4 and 3.5) is a
difficult technique to apply without its going astray. We will develop
heuristics to regulate it in the course of the implementation. Simi¬
larly, our approach to conjunctive goals (Section 2.5) needs further
explication.

5.2. Historical Context and Contemporary Research

Early work in program synthesis [e.g., Simon (1963), Green (1969),


Waldinger and Lee (1969)] was limited by the problem-solving capa¬
bilities of the respective formalisms involved (the General Problem
Solver in the case of Simon; resolution-theorem-proving in the case
of the others). Our paper on loop formation (Manna and Waldinger,
1971) was set in a theorem-proving framework, and paid little
attention to the implementation problems.
It is typical of contemporary program synthesis work not to
attempt to restrict itself to a formalism; systems are more likely to
write programs the way a human programmer would write them.
For example, the recent work of Sussman (1975) is modeled after
the debugging process. Rather than trying to produce a correct
program at once, Sussman s system rashly goes ahead and writes
incorrect programs which it then proceeds to debug. The work re¬
ported in Green (1976) attempts to model a very experienced program¬
mer. The system relies on built-in knowledge rather than on infer¬
ence or problem solving ability.
The work reported here emphasizes reasoning more heavily than
Discussion 177

the papers of Sussman and Green. For instance, in our synthesis of


the pattern matcher we assumed no knowledge about pattern
matching itself. Of course we do assume extensive knowledge of lists,
substitutions, and other aspects of the subject domain.
Although Sussman’s debugging approach has influenced our treat¬
ment of program modification and the handling of simultaneous
goals, we tend to rely more on logical methods than Sussman.
Furthermore, Sussman deals only with programs that manipulate
blocks on a table; therefore he has not been forced to deal with
problems that are more crucial in conventional programming, such
as the formation of conditionals and loops.
The work of Buchanan and Luckham (1974) is closest to ours in
the problems it addresses. However, their system forms iterative
loops that must be specified in advance by the user as “iterative
rules”, whereas in our system the loops are recursive and are
introduced by the system itself when it recognizes a relationship
between the top-level goal and a subgoal. Buchanan and Luckham’s
methods for forming conditionals and for treating programs with side
effects are also somewhat different from ours.

5.3. Conclusion and Future Work

Some of the approaches to program synthesis that we feel will be


most fruitful in the future have been given little emphasis in this
paper because they are not yet fully developed. For example, the
technique of program modification, which occupied only one
small part of the current paper, we feel to be central to future
program synthesis work. The retention of previously constructed
programs is a powerful way to acquire and store knowledge.
Furthermore, program optimization [cf. Darlington and Burstall
(1973)] and program debugging are just special cases of program
modification.
We hope we have managed to convey in this paper the promise
of program synthesis, without giving the false impression that auto¬
matic synthesis is likely to be immediately practical. A computer
system that can replace the human programmer will very likely have
human intelligence in other respects as well.
178 PROGRAM SYNTHESIS

Acknowledgments

We wish to thank Robert Boyer, Nachum Dershowitz, Bertram Raphael, and


Georgia Sutherland for detailed critical readings of the manuscript. We would
also like to thank Peter Deutsch, Richard Fikes, Akira Fusaoka, Cordell Green
and his students, Irene Greif, Carl Hewitt, Shmuel Katz, David Luckham, Earl
Sacerdoti, and Ben Wegbreit for conversations that aided in formulating the
ideas in this paper. The set-theoretic expression-handler is based on work of
Jan Derksen.
The research reported herein was sponsored by the National Science Founda¬
tion primarily under Grant GJ-36146 (SRI Project 2245) and partially under
Grant GK-35493 (SRI Project 2323).
Postscript

Since the original appearance of the papers in this collection, much


related research has been done. In this postscript we do not catalogue
all the papers that have appeared on these topics, but we do mention
some closely related work and some general trends.

Program Verification

Since the paper “Reasoning about Programs” was written, several


more powerful verification systems have appeared using the same
invariant-assertion technique. The system of Suzuki (1975), for
example, can handle a wider class of programs and is significantly
faster than the system we describe. Good et al. (1975), on the
other hand, allow much more interaction between the user and the
theorem prover. Much work has been done also in extending the
technique to allow a wider class of data and control structures in
the verified programs. At the same time, alternate techniques for
program verification besides the invariant-assertion method have
been proposed.

Analysis of Programs

Much work has also been done related to the topic of the second
paper, “Logical Analysis of Programs.” German and Wegbreit (1975)
have implemented some heuristic approaches for generating invariant
assertions. The counters approach to proving program termination
has been incorporated into the program verification system of Luck-
ham and Suzuki (1975). A system to analyze the running time of
programs has been implemented by Wegbreit (1975). Efforts have
been made to use such analyses as a basis for the optimization of
programs and as a guide for their synthesis.

179
180 POSTSCRIPT

Program Synthesis

The recursion-introduction technique that appears in “Knowledge


and Reasoning in Program Synthesis” has been further developed by
Siklossy (1974) and Darlington (1975). The simultaneous goal
strategy has been elaborated on and implemented by Warren (1974)
and Waldinger (1977). Other approaches to the automatic construc¬
tion of computer programs are being pursued, under the general
rubric of automatic programming. Most of these approaches are less
formal than ours in specifying the program to be constructed, and
less systematic in developing it. For a comprehensive survey of this
field, see Biermann (1976).
References

Balzer, R. M. (Sept. 1972). Automatic Programming. Technical Report. Infor¬


mation Science Institute, University of Southern California, Marina del
Rey, Ca.

Biermann, A. W. (1976). Approaches to automatic programming. In Advances in


Computers, vol. 15. New York: Academic Press (to appear).
Biermann, A. W. and R. Krishnaswamy (Sept. 1976). Constructing programs
from example computations. IEEE Transactions on Software Engineering,
2 (3): 141-153.

Boyer, R. S., and J S. Moore (Jan. 1975). Proving theorems about LISP func¬
tions. JACM, 22(1): 129-144.

Buchanan, J. R., and D. C. Luckham (May 1974). On Automating the Construc¬


tion of Programs. Technical report. Artificial Intelligence Laboratory,
Stanford University, Stanford, Ca..

Burstall, R. M., and J. Darlington (Jan. 1977). A transformation system for


developing recursive programs, JACM, 24 (1): 44-67.

Cooper, D. C. (1971). Programs for mechanical program verification. In Machine


Intelligence 6, New York: Elsevier North-Holland, pp. 43-59.
Darlington, J. (July 1975). Applications of program transformation to program
synthesis. In Colloques IRIA on Proving and Improving Programs. Arc et
Senans, France, pp. 133-144.

Darlington, J., and R. M. Burstall (Aug. 1973). A system which automatically


improves programs. In Proceedings of the Third International Joint Con¬
ference on Artificial Intelligence, Stanford, Ca., pp. 479-485.
Deutsch, L. P. (June 1973). An Interactive Program Verifier. Ph.D. thesis. Uni¬
versity of California, Berkeley, Ca.

Elspas, B., K. N. Levitt, and R. J. Waldinger (Sept. 1973). An Interactive System


for the Verification of Computer Programs. Technical report. Stanford
Research Institute, Menlo Park, Ca..

181
182 REFERENCES

Elspas, B. (July 1974). The Semiautomatic Generation of Inductive Assertions


for Proving Program Correctness. Technical report. Stanford Research
Institute, Menlo Park, Ca.

Floyd, R. W. (1967). Assigning meanings to programs. In Proceedings of the


Symposium in Applied Mathematics, vol. 19 (J. T. Schwartz, ed.), Ameri¬
can Mathematical Society, Providence, R. I., pp. 19-32.

Floyd, R. W. (1971). Towards interactive design of correct programs. In Pro¬


ceedings of IFIP Congress, vol. 1, Amsterdam: North-Holland, pp. 7-10.
German, S. M., and B. Wegbreit (Mar. 1975). Proving loop programs. IEEE
Transactions on Software Engineering, 1 (1): 68-75.
Good, D. I., R. L. London, and W. W. Bledsoe (Mar. 1975). An interactive pro¬
gram verification system. IEEE Transactions on Software Engineering,
1 (1): 59-67.

Green, C. (May 1969). Application of theorem proving to problem solving,


In Proceedings of International Joint Conference on Artificial Intelligence,
Washington, D.C. pp. 219-239.

Green, C. (Oct. 1976). The design of PSI program synthesis system. In Proc¬
eedings of Second International Conference on Software Engineering.
San Francisco, Ca., pp. 4- 18.

Greif, I. and R. Waldinger (April 1974). A more mechanical heuristic approach


to program verification. In Proceedings of International Symposium on
Programming, Paris, pp. 83-90.
Hardy, S. (Sept. 1975). Synthesis of LISP programs from examples, In Proceed¬
ings of the Fourth International Joint Conference on Artificial Intelligence,
Tbilisi, Georgia, USSR, pp. 240-245.

Hewitt, C. (April 1911). Description and Theoretical Analysis (Using Schemata)


of PLANNER: A Language for Proving Theorems and Manipulating
Models in a Robot. Ph.D. thesis M.I.T., Cambridge, Mass.
Hoare, C. A. R. (July 1961). Algorithm 65: FIND, CACM, 4 (7): 321.
Hoare, C. A. R. (Oct. 1969). An axiomatic basis of computer programming,
CACM, 12 (10): 576-580, 583.

Hoare, C. A. R. (Jan. 1971). Proof of a program: FIND. CACM, 14 (1): 39-45.


Igarashi, S., R. L. London, and D. C. Luckham (1975). Automatic program
verification I: A logical basis and its implementation. Acta Informatica,
4 (2): 145-182.

Katz, S. M., and Z. Manna (Aug. 1973). A heuristic approach to program verifi¬
cation. In Proceedings of the Third International Conference on Artificial
Intelligence, Stanford University, Stanford, Ca., pp. 143-155.
REFERENCES 183

Katz, S. M., and Z. Manna (1975). A closer look at termination. Acta Infor-
matica, 5 (4): 333-352.
Katz, S. M., and Z. Manna (Apr. 1976). Logical analysis of programs. CACM,
19 (4): 188-206. (The second paper in this collection.)

King, J. C. (1969). A Program Verifier. Ph.D. thesis. Carnegie-Mellon University,


Pittsburgh, Pa.

King, J. C. (1970). A verifying compiler. In Debugging Techniques in Large


Systems (Randall Rustin, ed.), Englewood Cliffs, N.J.: Prentice-Hall,
pp.17-39.

Knuth, D. E. (1968). The Art of Computer Programming, Volume 1: Funda¬


mental Algorithms. Reading, Mass: Addison-Wesley.
Knuth, D. E. (1969). The Art of Computer Programming, Volume 2: Semi-
numerical Algorithms. Reading, Mass: Addison-Wesley.
Kowlaski, R. (March 1974). Logic for Problem Solving. Technical report. Univer¬
sity of Edinburgh, Edinburgh.

Luckham, D. C., and N. Suzuki (Oct. 1975). Proof of Termination within a


Weak Logic of Programs, Technical report. Stanford University, Stan¬
ford, Ca.

Manna, Z. (May 1969). The correctness of programs. JCSS, 3 (2): 119-127.

Manna, Z. (1974). Mathematical Theory of Computation. New York: McGraw-


Hill.

Manna, Z., and A. Pnueli (July 1970). Formalization of properties of functional


programs. JACM, 17 (3): 555-569.

Manna, Z., and R. Waldinger (March 1971). Toward automatic program syn¬
thesis. CACM, 14(3): 151-165.

Manna, Z., and R. Waldinger (1975). Knowledge and reasoning in program syn¬
thesis. Artificial Intelligence, 6 (2): 175-208. (The third paper in this
collection.)

McCarthy, J. (1962). Towards a mathematical science of computation. In


Information Processing, Proceedings ofIFIP Congress 1962 (C. M. Popple-
well, ed.). Amsterdam: North-Holland, pp. 21-28.

McCarthy, J., P. W. Abrahams, D. J. Edwards, T. P. Hart, and M. I. Levin (Aug.


1962). LISP 1.5 Programmer’s Manual. Cambridge, Mass.: M.I.T. Press,

Naur, P. (1966). Proof of algorithms by general snapshots. BIT, 6: 310-316.

Robinson, J. A. (Jan. 1965). A machine oriented logic based on the resolution


principle. JA CM, 12(1): 23-41.
184 REFERENCES

Rulifson, J. F., J. A. Derksen, and R. J. Waldinger (Nov. 1972). QA4: A Pro¬


cedural Calculus for Intuitive Reasoning. Technical report. Stanford
Research Institute, Menlo Park, Ca.
Siklossy, L. (Nov. 1974). The synthesis of programs from their properties, and
the insane heuristic. In Proceedings of the Third Texas Conference on
Computing Systems, Austin, Texas.
Simon, H. A. (Oct. 1963). Experiments with a heuristic compiler. JACM, 10
(4): 493-506.
Sites, R. L. (May 1974). Proving that Computer Programs Terminate Cleanly.
Ph.D. thesis. Stanford University, Stanford, Ca.
Summers, P. D. (Jan. 1976). A methodology for LISP program construction
from examples. In Proceedings of the Third ACM Symposium on Prin¬
ciples of Programming Languages, Atlanta, Ga., pp. 68-76.
Sussman, G. J. (1975). A Computer Model of Skill Acquisition. New York:
Elsevier North-Holland.

Suzuki, N. (Apr. 1975). Verifying programs by algebraic and logical reduction. In


Proceedings of the International Conference on Reliable Software, Los
Angeles, Ca., pp. 473-481.
Teitelman, W. (Dec. 1975). INTERLISP Reference Manual, Xerox PARC, Palo
Alto, Ca.

Turing, A. M. (Jan. 1950). Checking a large routine. In Report of a Conference


on High Speed Automatic Calculating Machines, University of Toronto,
Canada, pp. 66-69.

von Neumann, J., and H. H. Goldstine (1963). Planning and coding problems
for an electronic computer instrument, Part 2. In Collected Works of John
von Neumann, vol. 5, New York: Macmillan, pp. 91-99.
Waldinger, R. J. (1977). Achieving several goals simultaneously. In Machine
Intelligence 8: Machine Representations of Knowledge (E. W. Elcock
and D. Michie, eds.). New York: John Wiley and Sons, Inc.

Waldinger, R. J., and R. C. T. Lee (May 1969). PROW: A step toward automatic
program writing, In Proceedings of International Joint Conference on
Artificial Intelligence, Washington, D.C., pp. 241-252.

Waldinger, R. J., and K. N. Levitt (1974). Reasoning about programs. Artificial


Intelligence, 5: 235-316.(The first paper in this collection.)

Warren, D. H. D. (June 1976). Warplan: A System for Generating Plans. Memo.


University of Edinburgh, Edinburgh, Scotland.
REFERENCES 185

Wegbreit, B. (Feb. 1974). The synthesis of loop predicates. CACM, 17 (2):


163-167.

Wegbreit, B. (Sept. 1975). Mechanical program analysis. CACM, 18 (9): 528-


539.

Wensley, J. H. (1958). A class of non-analytical interactive processes. Computer


Journal, 1: 163-167.
Wilber, B. M. (March 1976). A QLISP Reference Manual. Technical report.
Stanford Research Institute, Menlo Park, Ca.
Name Index

Abrahams, P.W., 15,22 Igarashi, S., 1,30

Balzer, R.M., 143 Katz, S.M.,7,126,139


Biermann, A.W., 142,180 King, J.C., 1,19,35,36,93,139
Bledsoe, W.W.,93,179 Kowalski, R., 143
Boyer, R.S., 1,147 Knuth, D.E.,3,15,139
Buchanan, J.R., 177 Krishnaswamy, R., 142
Burstall, R.M., 148,1 77,
Lee, R.C.T., 176
Levin, M.I., 15 22
Cooper, D.C., 139
Levitt, K.N.,2,93,139
London, R.L., 1,30,93,179
Darlington, J., 148,177,180 Luckham, D.C., 1,30,177,179
Derksen, J.A.C.,2,38,176
Deutsch, L.P., 1,30,93
Manna, Z.,7,25,96,119,126,139,146,
176
Edwards, D.J., 15,22 McCarthy, J., 15,19,22
Elspas, B.,2,7,139 Moore, J.S., 1,147

Floyd, R.W.,3,8,37,93,121,124,139, Naur, P.,3


140,152 von Neumann, J.,3

Pnueli, A., 25
German, S.M.,7,179
Goldstine, H.H.,3
Good, D.I.,93,179 Reboh, R.,36
Green, C., 176,177 Robinson, J.A.,22,23,157,171
Greif, I., 139 Rulifson, J.F., 2,38,176

Hardy, S.,142 Sacerdoti, E.D.,36


Hart, T.P., 15,22 Siklossy, L., 180
Hein, P., 1 Simon, H.A., 176
Hewitt, C., 13,142,143 Sites, R.L., 139
Hoare, C.A.R., 1,3,28,30,35,152 Summers, P.D., 142

187
188 NAME INDEX

Sussman, G.J., 139 155,176,177 Waldinger, R.J.,2,38,93,139,146,176,


Suzuki, N.,93,179 180
Warren, D.H.D., 180
Wegbreit, B.,7,139,179
Teitelman, W.,2 Wensley, J.H.,20,21,74,105
Turing, A.M.,3 Wilber, B.M.,36,142,176
Subject Index

access function, 19f, 55 conjunct elimination (invariant genera¬


adaptation (of programs). See modifi¬ tion rule), 110
cation. conjunction function. See and func¬
addition function. See plus function. tion.
ALGOL (programming language), 28ff conjunctive goals, 148ff, 165ff, 176.
analogy (in program synthesis), 172ff (See also simultaneous goals.)
and function, 16, 38 construction (of programs). See syn¬
arrays, 18ff, 27ff, 5 5 ff, 84ff thesis.
assertions, 3 ff contexts (in QA4), 17, 34f, 43, 48,
input, 3, 93ff 176
intermediate, 6. see also invariants, correctness of programs, 2ff, 93ff,
output, 3, 93ff 119ff
in QA4, 16ff, 41 counter-examples (for incorrect pro¬
assignment statements grams), 37
and synthesis, 15 Off counters (for proving termination),
and verification, 4, 9, lOlff 95 ff, 124ff, 139, 179
associativity, 1 5, 53 cutpoints (of loops), 95ff
automatic programming, 180

data structures, 15ff, 179


backtracking (in QA4), 12ff, 36ff, 38, debugging (of programs), 94, 123,
47 127 ff, 139f, 176f
bags (in QA4), 15, 18, 45, 47, 62f demon (in QA4), 14, 48ff
blocked programs, 95 depth-first search, 36
blocks world, 177 development (of programs). See syn¬
bounding variables (invariant genera¬ thesis.
tion rule), 111 f diagnosis (of programs). See debugging,
difference equations, 102
cancelation, 53 difference function (numerical). See
case analysis. See hypothetical rea¬ minus function.
soning. difference function (on bags), 58, 62f
change function, 19f, 55, 58 disjunct elimination (invariant genera¬
commutativity, 15 tion rule), 109
complete set (of cutpoints), 96 disjunction function. See or function,
composition function, 66, 159 distributive law, 52f
conditional expressions, 46, 103ff divide function, 12f, 47f, 53
formation of, 142, 144ff, 15Iff, division algorithm, integer (example),
177 20ff, 74ff, 105, 113ff, 122ff

189
190 SUBJECT INDEX

division algorithm, real number (exam¬ invariants


ple), 105 f f, 112f, 123ff candidate, 96
generation of, 37, 94, 101 ff, 109ff,
139,179
ellipsis notation, 6, 8ff, 143, 181
minimal, 120
equal function, 1, 12ff, 16, 27, 36f,
table of, 128ff
39ff, 46f, 67ff
tree, 129ff
equality. See equal function,
equivalence relations, 1 5
Euclidean algorithm (example), 35
Leibnitz’s law, 40f
exchange sort (example), 36
linear equation (example), 149
exchanging values of variables (exam¬
linear inequalities (example), 36
ple), 3, 36
linear strategy (for simultaneous-goal
exponentiation program (example), 35,
problem), 155
53f
expressions, 22ff, 157f LISP (programming language), 1,2, 15,
22ff, 36, 143
factorial program (example), 35 list processing, 22ff
failure (in QA4), 12,38,67 logical analysis (of programs), 93ff, 179
FIND program (example), 1, 28ff, 35, loop programs, 5ff, 22ff
84ff
floating-point numbers, 20ff
matching. See pattern matching,
maximum of array (example), 6, 18f,
general solutions, 149, 166ff 35, 69ff
generalization minus function, 14, 37, 42, 47ff
of invariants, 100
modification (of programs), 142, 155ff,
of specifications, 147f, 161, 169ff, 17Iff, 177 (See also debugging.)
173,176 monotonicity, 48
General Problem Solver, 176
most-general match, 161, 170
generation of invariants. See invariants, most-general unifier, 173
goal mechanism (in QA4), 11 ff, 36f, multisets. See bags.
38,66f multiplication function. See times
function.
hypothetical reasoning, 43, 145, 151, multiplication program, integer (exam¬
162, 176 ple), 35

identity, 50, 52
nontermination of programs, 94, 120ff.
incorrectness (of programs), 37, 93ff, See also termination.
119ff, 138, 140
induction (mathematical), 27, 100, 147
inequalities. See ordering relations, optimization (of programs), 94, 175,
instantiation function, 23, 63ff, 158f 177,179
interaction, 18,41,67, 179 ordering relations, 8, 15f, 19, 22, 28ff,
INTERLISP (programming language), 35,37, 39ff,47ff, 61
2, 36 or function, 16, 39
SUBJECT INDEX
191

partial correctness (of programs), 93, recursion,formation of, 142, 146ff, 165,
119f 176f, 180
PASCAL (programming language), 1 recursive programs, verification of,
passing backward. See pushing back¬ 22ff
ward. reflexivity, 18
path (in program), 6ff representation of concepts, 15ff
pattern and (in QA4), 50 resolution theorem proving 1, 176
pattern-directed function invocation reversing a list (example), 148
12ff, 143 running time (of programs), 126f, 130,
pattern matcher (example), 22ff, 35, 179
63ff, 78ff, 142, 149, 157ff, 172f,
176f
sets, 143ff
pattern matching (in QA4), 1 Iff
in QA4, 15f, 18,45
percentiles (example), 28ff
side-effects, 142, 150ff, 177
permutation property (of sort pro¬
simplification, 1, 13f, 27, 37, 4Iff,
gram), 18
49 ff
PLANNER (programming language),
simultaneous goals, 142, 153ff, 176,
142, 176
180. (See also conjunctive goals.)
plus function, 14, 15, 37, 42, 47ff
sort programs (example), 18, 28ff, 36,
procedural representation (of lan¬
56 ff
guages), 141
exchange, 36
product function. See times function,
three-variable, 154ff
program verifier. See verification sys¬
two-variable, 151 ff, 176
tems.
specification language, 18ff, 142ff
property lists, 15f, 40, 45
square-root, integer (example), 150
protection (in program modification),
straight-line programs, 3ff
15 5ff
strengthening. See generalization,
pushing backward
strip operator, 19
in generating verification condi¬
structural representation of knowl¬
tions, 9
edge, 141
invariant generation rule, 11 Of, 139
structured programming, 95
in synthesis, 152ff
substitutions, 22ff, 63ff, 78ff, 157ff,
pushing forward (invariant generation
17 Iff
rule), 111
subtract function. See minus function,
symmetry, 16
QA4 (programming language), 2, 3, synthesis (of programs), 37, 140, 14Iff,
11 ff, 22, 36, 176 179f
QLISP (programming language), 36,
142f, 176 termination (of programs), 7, 93f,
quotient program, integer (example),
119ff, 124ff, 146, 175, 179
35 tests, in generating verification condi¬
quotient program, real number (exam¬ tions, 9. (See also conditional
ple), 20ff, 35 expressions.)
times function, 12f, 15, 37, 47f, 52f
recurrence equations, 102ff, 139 total correctness (of programs), 119ff
192 SUBJECT INDEX

transitive law, 8, 40. (See also ordering verification conditions, 4, 96f


relations.) generation of, 5ff
tuples (in QA4), 15, 45 verification systems, 1, 93, 179

unification algorithm (example), 22,


27ff, 35, 63ff, 142, 157, 165, weak interpretation (of programs), 139
17 Iff well-founded set method (for proving
union of two sets (example), 143ff, termination), 124, 146
176
Date Due

, A,

%
/ f i: •' '- •> Q
-•»

OCT SO 1991
MOV
iJJV 1? ?
£
QA 76.6 .M357
Manna, Zohar. 010101 000
Studies in automatic programmi

63 0 26483 8
TRENT UNIVERSITY

QA76.6 .M357
Hanna, Zohar
Studies in automatic
programming logic

%&7l(?y
Other outstanding series in

THE COMPUTER SCIENCE LIBRARY


Theory of Computation Series
PATRICK C. FISCHER, Editor

Borodin and Munro The Computational Complexity


of Algebraic and Numeric Problems
Machtey and Young An Introduction to the General Theory
of Algorithms (in prep.)

Computer Design and Architecture Series


EDWARD J. McCLUSKEY, Editor

Salisbury Microprogrammable Computer Architectures

Svobodova Computer Performance Measurement and Evaluation


Methods: Analysis and Applications

Wakerly Error Detecting Codes, Self-Cheeki ng Circuits


and Applications (in prep.)

Operating and Programming Systems Series


PETER J. DENNING, Editor

Halstead A Laboratory Manual for Compiler


and Operating System Implementation

Spirn Program Behavior: Models and Measurements

Halstead Elements of Software Science

Franta A Process View of Simulation

Organick and Hinds The B1726 Interpreting Machine (in prep.)

Programming Languages Series


THOMAS E. CHEATHAM, Editor

Heindel and Roberto LANG-PAK—An Interactive Language


Design System
Wulf et al. The Design of an Optimizing Compiler
Maurer The Programmer's Introduction to SNOBOL
Cleaveland and Uzgalis Grammars for Programming Languages
Hecht Flow Analysis of Computer Programs

0 444 00224-3
- -

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy