A Practical Theory of Programming
A Practical Theory of Programming
North-Holland
1. Intmductiub
Cur formalism for the description of computation is divided into two parts: logic
and timing. The logic is concerned with the question: What results do we get? lfhe
timing is concerned with the question: When do we get them? One possible answer
to the latter question is: never, or as we may say, at time infinity. Thus we place
termination within the timing part of our formalism. Nontermination is just the
extreme case of taking a tong time.
We shall begin with a very simple model of computation. There are some observ-
able quantities, called variables X, y, - . , each with some domain. Our input to a
l
com~utatiun is to provide initial values for the variables. After a time, the output
from the computation is the final values of the variables. Later we shall add
communi~tion during the course of a imputation, but at first we shall consider
only an initial input and a final output. Also, for exposition, we start by igno~ng
timing, and therefore also te~ination.
2, Spedbtifms
distinction between a variable and its initict ~~~ti~), FKHFY ;ix”cz&i%ialstate CF~=1
[x, y, . . .I9 a computation satisfies a specification by delivering a fkal state cr’=
[x’, y’, . f that satisfies the predicate. Here is an example. Suppose there are two
l l
(xF=x+l)t\(yF=y)
specifies the behavior of a computer that increases the value of x by 1 and ieaves
y unchanged.
(x:= e)
= (substitute e for x in ok)
= (x’= e)A(y’=y)A-
(S; R)
= 3o”.(substitute u” for u’ in S) A(substitute u” for u in R)
A practical theory of programming 135
The notation ok is the identity relation between pre- and poststate: it specifies
that the final values of all variables equal the corresponding initial values. It is
satisfied by a machine that does nothing. In the assignment notation, e is any
expression. For example,
(x:= x+y)
= W=x+yMy'=y)
specifies that the final value of x should be the sum of the initial values of x and
y, and the value of y should be unchanged. The if and semi-colon notations combine
specifications to make a new specification. They apply to all specifications, not just
implementable specifications. They are just logical connectives, like A and v. But
they have the nice property that if their operands are implementable, so is the result.
The specification if 6 thenS elseR can be implemented by a computer that behaves
according to either S or R depending on the initial value of 6. The specification
S; R is the relational composition (or relational product) of S and R It can be
implemented by a computer that first behaves according to S, then behaves according
to R, with the final values from S serving as initial values for R It is therefore
sequential composition.
Notational note
The identity relation ok is called skip by some. We have used ok because it is
shorter, less active, and not in conflict with usage in other programming languages.
Our operator precedence is as follows:
On level 5, the operators can be used in a continuing fashion. This means, for
example, that a = b = c is neither grouped to the !eft nor grouped to the right, but
means (a = b) A (b = c).
136 E.C. R. Hehner
3. Refinement
There is a similar story for postconditions. For example, although x > 4 is unim-
plementable in general:
(the postcondition for x > 4 to be refined by x := x + 1)
=Vx.((x>4)e(x:= x+ 1))
=vx.((x-,4)~(x’=x+~l))
= (x’-1>4) One-point Law
= (x3 5)
This means that a computation satisfying x := x + 1 will also satisfy x > 4 if and only
if it ends with x’) 5. If we are interested only in final states such that x’) 5, then
we should weaken our specification with that antecedent, obtaining the refinement:
(x’>5)*(04)::x:=x+l
A practical theory of programming t37
For easier understanding, it might be better to use the contrapositive law to rewrite
the specification (x’ > 5 )*(x > 4) as the logically equivalent specification (x s 4) *
(x” s 5).
P=((x>o)*(x’>x))A
((X=O)*(x’#O))h
((x<O)*(x’cx))
or equivalently,
Then
(the precondition for P to be refined by x := x + 1)
=Vx'.(Pe(x'=x+1))
=~x+1>x>o)v(x+1#x=o)v(x+1cxco)
=(x20)
We find that x := x + 1 specifies that x becomes further from zero if and only if x
starts nonnegative. Also,
Therefore x := x + 1 specifies that x becomes further from zero if and only if x ends
positive.
4. Programs
In (b) and (c) we have not stated which expressions are “implemented”; that set
may vary from one implementation to another, and from time to time. Likewise the
programming notations (a) to (d) are not to be considered definitive, but only an
example. Part (e) states that any implementable specification P is a program if a
program Q is provided such that P:: Q. To execute P, just execute Q. The refinement
acts as a procedure declaration; P acts as the procedure name, and Q as the
procedure body; use of the name P acts as a call. Recursion is allowed; in part (e)
we may use P as a program in order to obtain program O_
Here is an example. Let x be an integer variable. The specification x’ = 0 says
that the final value of variable x is zero. It becomes a program by refining it, which
can be done in many ways. Here is one:
5. Common laws
A law is a sentence form such that all sentences of the form are theorems. In
other words, it is a theorem schema. The laws we present are not axioms (postulates);
they can be proved from the definitions we have given. It is essential for programming
that the laws hold for all specifications, not just for programs. That way we can use
them for program construction, not just for verification.
Transformation laws
The following laws hold for all specifications P, Q N&L 8 and F+~“:%~~n
expressions 6:
(ok;P)=(P;ok)= P
(P;W;R))=UP;Q);R)
(if 6 then P else P) = P
(if 6 tbea P else Q) = (if 16 tben Q else P)
((if !I then P else 0); R) = (if 6 then (P; R) else (Q;R))
A practical theory of programming 139
Reformation laws
The following laws hold for all specifications P, Q and R, variables X,expressions
e and boolean expressions b:
In the Substitution Law, P must use only standard predicate notations, not program-
ming notations. Here are some examples of the Substitution Law:
(x:=y+z;y’>x’)=(y’>x’)
(X:=X+i;(y’>X)A(X’>X))=(J”>X+l)A(X’~X+1)
Statta laws
Let P and Q be any specifications, and let A be a predicate of the prestate, and
let A’ be the corresponding predicate of the poststate. Then:
AA(P;Q)::AA P;Q
(P;Q)AA’::P;QAA’
A+P;Q)::A+P;Q
(P; Q)*A’:: P; Q*A’
P;AAQ::PAA’;Q
P;Q::PAA’;A*Q
These two laws are so important for programming that we shall require all our
program constructors to be monotonic. So far we have only two, if and sequential
140 E.C. R. Hehner
composition, and they are both monotonic in both their specification operands. (In
fact, they are pointwise monotonic.)
6. Program developamt
We consider that we have solved the original problem, but now we have two new
problems to solve. One is trivial:
(nC2)*(n’=mdn 2)::ok
The final specification has already been refined, so we have finished programming
(except for timing). Each refinement is a theorem; programming is proving.
The last refinement could have been written as
evenn’=evenn::
p:=2;evenp*evenph(evenn’=evenn)
evenp*evenp’A(evenn’=evenn)::
n :=n-p;p:=p+p;
if n<p then ok else evenp+evenp’n (even n’= even n)
A pa&al theory t$ programming 141
When a compiler translates a program into machine language, it treats each refined
specification as just an identifier. Our program looks like:
There is no need for the programmer to perform this mechanical assembly, and we
need not worry about the number of refinements used. Also, the recursive calls to
A and E can be translated as just branches (as can 99% of calls), so we need not
worry about recursion being inefficient.
7. Timiog
So far, we have talked only about the result of a computation, not about how
long it takes. To talk about time, we just add a time variable. We do not change
the theory at all; the time variable is treated just like any other variable, as part of
the state. The state Q = [ t, X,y, . . .] now consists of a time variable t and some space
variables X, y, . . . . The interpretation of I as time is justified by the way we use it.
We use t for the initial time, i.e. the time at the start of execution, and I’ for the
final time, i.e. the time at the end of execution. To allow for nontermination we
take the domain of time to be a number system extended with 00. The number
system we extend can be the naturals, or the integers, or the rationals, or the reals.
The number 00 is maximum, i.e.,
-+ time is m&;se~table
Time cannot decrease, therefore a specification s yficS iff
Vu*3a’.S n ( t’a t)
Time increases as a program is executed. There are many ways to measure time.
We present just two: real time and recursive time.
142 E. C. R. Hehner
where w is the time required for the call and return. For a call that is implemented
“inline”, this time will be zero. For a call that is executed last in a refinement, it
may be just the time for a branch. Sometimes it will be the time required to push
a return address onto a stack and branch, plus the time to pop the return address
and branch back.
Step 4. Each refined specification can include time. For example, let f be a
function of the initial state m. Then
t’= t +fu
specifies that fo is the execution time,
t’s t +fu
specifies that fo is an upper bound on the execution time, and
t’a t+fu
specifies that fo is a lower bound on the execution time.
We could place the time increase after each of the programming notations instead
of before. By placing it before, we make it easier to use the Substitution IAVX
In an earlier section, we considered the example
x’=O::if x=Otbea ok eke (x:=x-l;x’=O)
Suppose that the conditional, the assignment, and the call each take time 1. Using
P for the specification, the refinement becomes
This is still a theorem when P = (x’= 0) as before, but we can now include time in
the specification. The refinement with time is a theorem when
P=(x’=O)h
((X~0)3(t’=t+3xx+1))h
((xcO)*(t’=oo))
Execution of this program always sets x to 0; when x starts with a nonegative value,
it takes time 3 x x + 1 to do so; when x starts with a negative value, it takes infinite
time. It is strange to say that a result such as x’= 0 is obtained at time infinity. This
is really just a way of saying that a result is never obtained. We could change our
theory of programming to prevent any mention of results at time infinity, but we
do not for two reasons: it would make the theory more complicated, and we will
need to distinguish among infinite loops later when we introduce communications.
P=(x’=o)A
((xaO)*(t’= t+x))ll
((xcO)*(t’=aD))
where
Q=(x’=l)~
((X>,l)+t-f++IbX))A
((xcl)*(t’=@)
( HJ= binary logarithm; div - division and round down). Execution of this program
always sets x to 1; when x starts with a positive value, it takes logarithmic time;
when x starts nonpositive, it takes infinite time. The proof breaks into six pieces:
144 E. C. R. Hchncr
thanks to the Partwise Refinement Law, it is sufficient to verify the three conjuncts
of Q separately; and for each there are two cases in the refinement. In detail,
x’=l::(x=l)n(x’=x)A~r’=r)
x8=I::(xf r)h(x’= 1)
A standard way to prove terrriiination is to find a variant (or bound function) for
each loop. A variant is an expression over a well-founded set whose value decreases
with each iteration. When the well-founded set is the natural numbers (a common
choice), a variant is exactly a time bound according to the recursive time measure.
We shall see in the next section why it is essential to recognize that a variant is a
time bound, and to conclude that a computation terminates within some bound,
rather than to conclude merely that it terminates.
Another common choice of well-founded set is based on lexicographic ordering.
For any given program, the lexicographic ordering used to prove termination can
be translated into a numeric ordering, and be seen as a time bound. To illustrate,
suppose, for some program, that the pair [n, m] of naturals is decreased as follows:
[n, m+ I] decreases to [n, m], and [n+ 1, 0] decreases to [ n,fi]. Then we consider
[n, m] as expressing a natural number, defined as follows.
[O,O]=O
[n,m+l]=[n,m]+l
[n+l,O]=[n,fn]+1
Well-founded sets are unnecessary in our theory; time can be an e~te~&d ;?“qtfl;
rational, or real. Consider the following infinite loop:
R::x:=x+l; R
We can, if we wish, use a measure of time in which each iteration costs half the
time of the previous iteration:
is a theorem when R = (t’ = t +2-“). The theory correctly tells us that the infinite
iteration takes finite time. We are not advocating this time measure; we are simply
showing the generality of the theory.
7.4. Termination
A customer arrives with the specification
(1)
He evidently wants a computation in which variable x has final value 2. I provide
it in the usual way: I refine his specification by a program and execute it. The
refinement I choose, including recursive time, is:
and execution begins. The customer waits for his result, and after a time becomes
impatient. 1 tell him to wait longer. After more time has passed, the customer sees
the weakness in his specification and decides to strengthen it. He want a computation
that fir,ishes at a finite time, and specifies it thus:
(x’=2)n(t’<al) (2)
We reject (2) because it is unimplementable: (2) A(1’a t) is unsatisfiable for t = 00.
It may seem strange to reject a specification just because it cannot be satisfied with
nondecreasing time when started at time ~0.After all, we never want to start at time
43. But consider the sequential composition P; Q with P taking infinite time. Then
Q starts at time 00 (in other words, it never starts), and the theory must be good
for this case too. An implementable specification must be satisfiable with nondecreas-
ing time for all initial states, even for initial time 00, So the customer weakens his
specification a little:
(x’=2)n((t<m),(t’<m)) (3)
He says he does not care how long it takes, except that it must not take forever- I
can refine (3) with exactly the same construction as (l)! Including recursive time,
my refinement is:
(X’=2)h((tcao)*(t’c~))::
t:= t+l;(X’=2)A((t<a)+t’<(X3))
Execution begins. When the customer becomes restless I again tell him to wait
longer. After a while he decides to change hia sp ;%c~&G +h:
(X’=2)A(t’=t) (4)
He not only wants his result in finite time, he wants it instantly. Now I must abandon
my recursive construction, and I refine:
(x’=z)A(t’= t)::X:=?
Execution provides the customer with the desired result at the desired time.
146 E. C. R. Hehner
8. Local variable
which says that z is unchanged. Variable x is not mentioned because it is not one
of the global variables, and variable y is not mentioned because its final value is
A practical theory of programming 147
unknown. However
(vatx:int;y:=x-x)
=(y’=O)/+‘=z)
In some languages, a newly declared variable has a special value called “the
undefined value” which cannot participate in any expressions. To write such
declarations as predicates, we introduce the elementary expression urrdejinedbut
we do not give any axioms about it, so nothing but trivialities like undefined =
undefined can be proved about it. Then
(vat x: T; P)
= 3x : undefined3x’: T, undefined.P
9. la-place refioemeot
The middle line means what its left-hand side says, hence the three lines together
are equal to:
y'= 235
We are not allowed to use the way y’> x is refined and conc!ude
(x’ = 5) A(y’ = 2’ = 6).
In-place refinement can be recursive, and thus serve as a loop. For example,
(x~o)*(y’=x!)A(z’=t+x)::
y:=l; _-
((x~0)3(y’=yxx!)A(t’=t+x)::
if x=0 theo ok
else (y:=yxx;x:=x-1;2:= t+l;
(X3O)+y’=yXX!)A(t’=t+x)))
Note that the body of the loop is not used in this theorem. The other is:
(xkO)~(y’=yxx!)A(t’=z+x)::
if x=0 theo ok
else (y:=yxx;x:=x-l;t:= t+l;
(XLO)+(y’=yXX!)A(t’=t+X))
148 E. C. R. Hehner
With in-place refinement we can indent the inner refinements and make our programs
look more traditional. This is not a good reason for using them. The only advantage
they offer is to save us from writing a specification one extra time.
10. Concurresrcy
We shall define the parallel composition 11of specifications P and Q so that P 11Q
is satisfied by a computer that behaves according to P and the same time, in parallel,
according to Q. The operands of 11are called processes. The connective IIis similar
to A (conjunction), but weaker, so that
PIIQ::PitQ
Conjunction is not always implementable, even when both conjuncts are. For
example, in variables x and y,
(x:= x+l)n(y:=y+l)
= (x’= X+~)A(f=J’)A(X’=.X)A(J7’=~~+~)
= I1as4
We shall define parallel composition so that P IIQ is implementable whenever P
and Q are. In particular,
(X:=X+~~~_V:=~V+~)=(X’=X+~)A(J”=_V+l)
(X :=x+l;x:=x-lIly:=x)=(okIly:=x)
According to the right-hand side of this equation, the final value of y is the initial
value of x. According to the left-hand side of the equaiton, it may seem that y’ = x + 1
should also be a possibility: the right-hand process y := x may be executed in b~*we
the two assignments x:= x + 1 and x:= x - 1 in the left-hand pro&ss. %%‘e faw a
simple choice: give up the Law of Transparency and with it aii familiar mathematics,
or give up the possibility that one process may use the intermediate states of another
process. We choose the latter; our processes will not communicate with each other
through shared variables, but through communication channels that do not violate
the Law of Transparency. We postpone communication to the next section, and in
this section consider non-interacting processes.
A practical themy of pogratnming 149
x v=x::P
According to P, the final value of x will be its initial value. Variable x is dep&ent
on specification P if it is not independent:
1(x’ =x:: P)
This says that x might change (is not guaranteed not to change) its value under
specification I? The precondition for independence
Vo’.((x’= x)e P)
tells us in which initial states K does not change its value under I? Its negation
1Vo’.((x’=x)e=P)
= 3a’.(x’ f x) h P
Parallel composition is just conjunction, except that each conjunct makes no promise
about the final values of variables that might be changed by the other process.
A parallel composition can be executed by executing the processes in parallel,
but each process makes its assignments to private copies of variables. Then, when
both processes are finished, the final value of a variable is determined as follows:
if both processes left it unchanged, it is unchanged; if one process changed it and
the other left it unchanged, its final value is the changed one; if both processes
changed it, its final value is arbitrary. This final rewriting of variables does not
require coordination or communication between the processes; each process rewrites
those variables it has changed. In the case when both processes have changed a
variable, we do not even require that the final value be one of the t!vo changed
values; the rewriting may mix the bits.
In a commonly occurring special case, there is no need to copy vz++.bles. When
both processes are expressed as programs, and neither assigns to any variable
appearing in the other, copying and rewriting are unnecessary. For the sake of
efficiency, it is tempting to make this case the definition of parallel composition.
But we must define our connectives for all specifications, not just for programs. We
must be able to divide a specification into processes before deciding how each
process is refined by a program.
150 E. C. R. Hehner
Common laws
(P::Q)*(P llR::QIIR) monotonic
(P::Q)*(RIlP:AqQ) monotonic
vvIQ)=wIl~) symmetric
~~ll(QIl~~)=~(~llQ)ll~) associative
(PIIok)=(okllP)=P identity
(~llQv~)=(~IIQ~v~~ll~) distributive
(PIIN 6 tbea Q else RI
= (if 6 then (P 11Q) else (P 11RI) distributive
Examples
Let the state consist of three integer variables x, y and z. Here are two unsurprising
examples:
3a’.(x’# x) A (x:= y)
= 3Xf.3_V'.3Z'.( X' # X) A (X’ = _I’)A (?” = JT)A (Z’ = Z)
=(xzy)
Variable y is in the dependent space of x := y when
&f.(_V’ # _V)A (X := y )
=3X'.3y'.3t'.(),'#y)A(X'=y)A(~'=y)A(Z'=Z)
= /las4
= flas4
In other words, when x = y the dependent space of x:= y is empty, i.e.,
QX=y)*(a,,,, = [nil])
and when x # y the dependent space of x := y is x, i.e.,
(x f y)*(o,:,, = [xl)
A ptacrical theory of ptvgtamming
11. Couunuaic8tion
Until now, the only input to a computation has been the initial state of the
variables, and its only output has been the final state. Now we consider input and
output during the course of a computation. We allow any number of named
communication channels through which a computation communicates with its
environment, which may be people or other computations running in parallel.
Communication on channel c is described by a list s, called the channel script,
and two extended natural variables r, and w, called the read and write cursors. The
script is the sequence of all messages-past, present and future-that pass along
the channel. The script is a constant, not a variable. At any time, the future meassages
on a channel may be unknown, but they can be referred to as items in the script.
The read cursor is a variable saying how many messages have been read, or input,
on the channel. The write cursor is a variable saying how many messages have been
written, or output, on the channel. Neither the script nor the cursors are programming
notations, but they allow us to specify any desired communications. If there is only
one channel, or if the channel is known from context, we may Gmit the subscripts
on s, r and w.
Here is an example specification. It says that if the next iapat e,;;:channel c is
even, then the next output on channel d will be 0, and otherwise it will be 1.
Formally, we may write:
if eilen (s,r,) then sdwd = 0 else sdwd= 1
or more briefly
mod ( SJ,) 2 = sdwd
152 EC. R. Hehner
c-sqr-I)
c!e=(sw=e)h(w:= w+l)
The predicate computution is the strongest predicate that describes all computa-
tions. If we are considering time, and there is one channel c, then
computation=(t’3t)n(r+rC)n(w:3H:)
A computation cannot take less than zero time, it cannot unread, and it cannot
unwrite. There are similar conjuncts for other channels. If there are no channels
and we are not considering time, then computution is the empty conjunction true.
In general, specification S is implementu6le iff
Vu3o’S h computation
For every program P
computation :: P
Examples
Input numbers from channel c, and output their doubles on channel d. Formally,
the specification is:
S=Wn:nat.(sd(wd+n)=2xsC(rC+n))
We cannot assume that the input and output are the first input and output ever on
channels c and d. We can only ask that from now on, starting at the initial read
cursor r, and initial write cursor w,,, the outputs will be double the inputs. This
specification can be refined as follows:
S::c?;d!2xc;S
The proof is as follows:
(c?;d!2xc;S)
=(r$= rC+l;(sdwd=2xsC(r,-l))h(wd:= wd+l);S)
= (sdw&#=2XsCrC)r\Vn:nat.(s,(w~+l+n)=2xs,(rC+l+n))
=S
For reasons stated earlier, parallel processes cannot communicate and cooperate
through variables. They must communicate through channels. Here is a first example,
though not a convincing one:
c!2(1c?;x:=c
A practical theory qfprogramming 153
The dependent space of the left-hand process is w. The dependent space of the
right-hand process is r, X. This example is equal to
(SH’=2)h(H”=H’+l)h
(P’= r + 1) A(x’ = sr) A(other variables unchanged)
We do not know that initially w = r, so we cannot conclude that finally X’= 2. The
parallel composition may be part of a sequential composition, for example,
c!l;(c!211c?;x:=c);c?
and the final value of x may be the 1 from the earlier output, with the 2 going to
the later input. In order to achieve useful communication between processes, we
shall have to introduce a local channel.
The first process begins with a computation described by P, and then outputs a 0
on channel c. The second process begins with an input on channel c. Even if we
decide that communication is free, we must still account for the time spent waiting
for input. We therefore modify the program by placing a time increment before
each input. For example, if
I’ =t+p::P
t’ = t+q::Q
then we modify the program as follows:
chaac:int;chand:int;(c?;d!211d?;c!3)
In the two processes, inserting a wait before each input yields
x := t
and
are allowed. We can look at the clock, but not reset it arbitrarily; all clock changes
must correspond to the passage of time.
We may occasionally want to specify the passage of time. For example, we may
want the computation to “wait until time w”. Let us invent a notation for it, and
define it formally as
wait until w
=(t:=mww)
wait until w ::
if ta w them ok else (t:= t+l;wait until w)
integer-valued, and used the recursive time measure. For practicality we should use
real values and the real time measure. The change is an easy exercise.)
where the iderhfler can be used within the program as a recursive call. (A fixed-point
is a solution to such an equation, considering the identifier as the unknown. The
least fixed-point is the weakest solution.) There are two difficulties.
We defined sequential composition as a connective between arbitrary
specifications, not just programs. This is essential for programming in steps: we
must be able to verify that P:: Q; R without referring to the refinements of Q and
I?.Similarly if, vat, ff and than apply to arbitrary specifications. For the same reason,
we must define a loop construct, if we choose to have one, for all specifications:
identifipr = specifka tion
the induction, the limit, and the continuity problem. Of course, these problems do
not disappear, but we prefer to avoid them when possible.
13. Conclusion
Acknowledgement