The examples at the end of the previous chapter showed that the semantics of assignment in computer language is different from the semantics of an equation in algebra --- in algebra, x = x + 1 is a self-contradiction, whereas in computer language, it is a new assertion, cancelling a previously known one. In this chapter, we make precise the semantics of assignment for the knowledge that travels through a computer program.
=================================================== hours = 4 """{ 1. hours == 4 premise }""" minutes = hours * 60 """{ 1. hours == 4 premise 2. minutes == hours * 60 premise 3. minutes == 240 algebra 1 2 }""" print hours, minutes ===================================================From now on, we enclose our logical and algebraic reasoning steps in set braces to distinguish it from the text of the program. We will number each reasoning step; premises are facts that are immediate from the assignment command or facts that were previously established. In Line 3 of the above example, algebra, like that we studied in the previous chapter, was applied to the assertions in Lines 1 and 2 to deduce the result.
Since we are mixing program assignments and algebraic equations, we will use the == sign, like in programming, to denote algebraic equality. We will also use other programming notation, like * for multiplication.
In the example, we calculated the assertions that hold true
after each command is executed. For example, the assertions that
hold true just before the print command are
hours == 4, minutes == 60*hours, and minutes == 240.
In languages like C and Python, a programmer can insert assert
commands into the program script, like this, to force
that a logical property holds true during the execution:
===================================================
hours = readInt("Type an int > 2: ")
assert hours > 2
# execution reaches here _only if_ the value assigned to hours is > 2
minutes = hours * 60
# here, we _know_ that minutes > 120
print hours, minutes
===================================================
(Note: Our programming examples will be coded in Python.
This helper function will often be used:
def readInt(message = "Type an int: ") :
"""readInt is a helper function that reads an int from the display.
parameter: message - a string, the input prompt; default is 'Type an int:'
returns an int, typed by the user"""
needInput = True
answer = ""
while needInput :
text = raw_input(message)
if text.isdigit() :
answer = int(text)
needInput = False
return answer
)
When the computer executes the program, it checks that the number
read and assigned to hours is indeed greater than 2. If it is,
the computer proceeds; if it is not, the computer halts the program
with an assert exception. We can use the assert commands in
our analysis of the program, like this:
===================================================
hours = readInt("Type an int > 2: ")
assert hours > 2
"""{ 1. hours > 2 premise (from the previous assert) }"""
minutes = hours * 60
"""{ 1. hours > 2 premise (previous fact, unaltered)
2. minutes == hours * 60 premise (from the assignment)
3. minutes > 120 algebra 1 2
}"""
print hours, minutes
===================================================
As noted at the end of the previous chapter, when we violate
single assignment, our reasoning goes wrong. Consider
===================================================
hours = 4
"""{ 1. hours == 4 premise }"""
minutes = hours * 60
"""{ 1. hours == 4 premise
2. minutes == hours * 60 premise
3. minutes > 120 algebra 1 2
}"""
hours = 5
"""{ 1. hours == 5 premise
2. hours == 4??? (the previous fact is corrupted by the assignment)
}"""
===================================================
Of course, we cannot retain the assertion that hours == 4
at the same time that we assert hours == 5 --- the new assertion
cancels the old one. There is a similar problem with the assertion,
minutes == hours * 60 --- it is based on the cancelled assertion,
hours == 4, and therefore it must be cancelled, too.
Therefore, a first modification of our reasoning for an assignment,
x = e,
says that we must remove (cancel) all facts that mention the
old value of x.
For the example, we can deduce
===================================================
hours = 4
"""{ 1. hours == 4 premise }"""
minutes = hours * 60
"""{ 1. hours == 4 premise
2. minutes == hours * 60 premise
3. minutes == 240 algebra 1 2
}"""
hours = 5
"""{ 1. hours == 5 premise
2. minutes == 240 premise
}"""
===================================================
This removes all information about the cancelled value of hours
and the relationship between minutes and
the cancelled value.
But this is not yet exactly correct.
Consider this tiny example:
===================================================
assert x > 0
"""{ 1. x > 0 premise }"""
x = x + 1
"""{ 1. x == x + 1 ???? }"""
===================================================
Clearly, we should not assert x == x + 1 --- this is a contradiction
in algebra --- yet, we should somehow deduce that x > 1. How?
Here is the example repeated, with the distinction made clear:
===================================================
assert x > 0
"""{ 1. x > 0 premise }"""
x = x + 1
"""{ 1. x == x_old + 1 premise (from the assignment)
2. x_old > 0 premise (from the previously established fact)
(x has changed, but its ``old'' value has useful properties)
3. x_old + 1 > 1 algebra 2
4. x > 1 subst 1 3 (we substitute the equality stated
in Line 1 into Line 3)
# Finally, we must delete/ignore all assertions that mention x_old ---
# this leaves only Line 4 that can be carried forwards as a premise
# for future reasoning.
}"""
===================================================
When we have an assertion, P, that holds true
just before an assignment, x = e, we calculate the assertions
after the assignment, in two stages:
This is what we did in the earlier example --- we deduced x > 1, which we can carry forwards. We then forget all the assertions that mentioned x_old.
The rule's schematic looks likes this:
"""{ ...
m. P
}"""
x = e
"""{ 1. x == [x_old/x]e premise
2. [x_old/x]P premise
...
n. Q (where Q must _not_ mention x_old)
}"""
Here is a second example, where we wish to show that the
value within variable x falls in a certain range:
===================================================
x = readInt()
# the precondition is that int x falls in the range, 1,2,...,99
assert x > 0 and x < 100
x = x + 1
"""{ 1. x_old > 0 and x_old < 100 premise
2. x_old > 0 ande 1
3. x == x_old + 1 premise
4. x_old + 1 > 1 algebra 1
5. x > 1 subst 3 4
6. x_old < 100 ande 1
7. x < 101 algebra 3 6
8. x > 1 and x < 101 andi 5 7
}"""
# the postcondition is that x falls in the range, 2,3,...,100
print x
===================================================
The starting assertion about x is sometimes called a
precondition, because it is the input property that the
program requires to operate successfully. The goal of the program
is its postcondition.
The example uses new deduction laws that let us
combine together and pull apart assertions connected
by and ("and").
There is also an explicit use of substitution (subst),
where the equality substituted and the formula substituted into
are both listed as line numbers.
Because our deductions often consist of multiple steps, it is convenient to use andi to collect together the facts we wish to carry forwards as premises for future use --- from here on, we will carry forwards only the last line of a proof as the premise for future use. If we want to carry forwards multiple facts, we connect them together with andi. Later, we use ande to extract the facts as needed.
Here is one more example, where we use
maintain the relationship between the quantity of dimes
inserted in a piggy bank and their monetary value;
this is called an invariant property:
===================================================
# the _invariant property_ is that money == dimes * 10 :
assert money == dimes * 10
"""{ 1. money == dimes * 10 premise }"""
# Say that one more dime is inserted into the bank:
dimes = dimes + 1
"""{ 1. dimes == dimes_old + 1 premise
2. money == dimes_old * 10 premise
3. dimes_old == dimes - 1 algebra 1
4. money == (dimes - 1) * 10 subst 3 2
}"""
# The amount of money is less that what it should be; we fix this:
money = money + 10
"""{ 1. money_old == (dimes - 1) * 10 premise
2. money == money_old + 10 premise
3. money_old == money - 10 algebra 2
4. money - 10 == (dimes - 1) * 10 subst 3 1
5. money == ((dimes - 1) * 10) + 10 algebra 4
6. money == dimes * 10 algebra 5
}"""
# The invariant property is restored: money == dimes * 10
print dimes, money
===================================================
The proof explains why we wrote
the assignments as we did --- we wanted to maintain the logical
property that money == dimes * 10.
This would be critical if the above software was embedded in
a chip within the piggy bank: when the child inserts a dime, this
activates the above code, which resets dimes and money so that
the invariant property is always holding true. Then, when the
values of dimes and money are displayed on the LED on the side
of the bank, their relationship, as stated by the invariant property,
is maintained.
Invariant properties are central to the development of real-time software, which iteratively monitors the values of input sensors and adjusts program variables so that invariants are maintained. This is the crucial concept within flight-control software, which uses inputs from a plane's sensors to adjust rudders, joystick, and fuel intake so that a plane flies stably and on course.
The form of reasoning (invariant maintenance) just seen occurs automatically and unconsciously in seasoned programmers all the time, every time assignments are written.
Sometimes we really must discuss the ``old'' value of a variable
after an assignment completes. Here is a small but tricky program that
swaps the values of the its two variables, x and y:
===================================================
x = readInt()
y = readInt()
temp = x
x = y
y = temp
print x, y
===================================================
At the end of the program, we want to assert that y has x's
value and x has y's. To do this, we may invent dummy constants
called x_in and y_in and pretend they are the input values
to x and y respectively:
===================================================
x = readInt()
y = readInt()
"""{ 1. x == x_in premise
2. y == y_in premise
3. x == x_in and y == y_in andi 1 2
}"""
temp = x
"""{ 1. temp == x premise
2. x == x_in and y == y_in premise
3. temp == x_in and x == x_in and y == y_in andi 1 2
}"""
x = y
"""{ 1. x == y premise
2. temp == x_in and x_old == x_in and y == y_in premise
3. y == y_in ande 2
4. x == y_in subst 3 1
5. temp == x_in and y == y_in ande 2
6. x == y_in and temp == x_in and y == y_in andi 4 5
}"""
y = temp
"""{ 1. y = temp premise
2. x = y_in and temp = x_in and y_old = y_in premise
. . . (you fill in the steps) . . .
4. y = x_in and x = y_in
}"""
print x, y
===================================================
The dummy values x_in and y_in plus the assignment law
navigate us through the deduction.
The previous examples showed us a variety of facts calculated after each assignment step. When we analyze a program from start to finish, which facts should we try to deduce and carry forwards after each assignment? We cannot answer this question unless we know the goal assertion we are trying to prove at the program's end. For this reason, we should learn how to reason backwards from the goal at the end to the asserts at the beginning. This is called goal-directed reasoning, and there is a simple, beautiful deduction rule for assignments that takes the guesswork out of goal-directed reasoning.
For example, say that at a program's end, x > 2 must hold, and the program
ends like this:
. . .
"""{ subgoal: ??? }"""
x = y + 1
"""{ goal: x > 2 }"""
What is the subgoal needed for success?
It appears that y > 1 must hold just before the assignment.
How did we calculate this?
Since the assignment ``equates'' x with
y + 1, it must be y + 1 that is greater than 2 ---
we substitute y + 1 into the goal, for x,
that is, we compute the subgoal as
[y + 1 / x] (x > 2) = (y + 1) > 2
that is, y > 1 is the subgoal:
. . .
"""{ subgoal: y > 1 }"""
x = y + 1
"""{ goal: x > 2 }"""
This reasoning is the basis of a ``backwards law'' for
assignment commands.
The formal statement of the backwards-assignment law is
this:
"""{ subgoal: [e/x]G }"""
x = e
"""{ goal: G }"""
The goal, G, is sometimes called the assignment's postcondition,
and the subgoal, [e/x]G, is the precondition.
It is a formal, proved result that every such precondition, postcondition pair
calculated by the backwards-assignment law
can be proved as a correct forwards deduction with
the forwards-assignment law.
We apply the new law in an example, taken from
Chapter 1. Say that this program simply must make
d is positive at its end:
===================================================
x = readInt("Type an int > 0: ")
y = readInt("Type an int > 1: ")
assert ???
a = x + y
b = 2 * a
d = b - (y + 1)
assert d > 0
print d
===================================================
What should we assert at the beginning?
Let's work backwards,
from the end of the program, and calculate what is needed to
achieve the goal of d > 0. This is like solving a maze puzzle,
where you are supposed to travel from the maze's entry to the exit,
by travelling instead from the exit to the entry --- it's
lots easier.
The first backwards step: we want d > 0, and this must hold true
after d = b - (y + 1). Therefore, we must have
(b - (y + 1)) > 0 holding true just before this assignment.
This gives us:
===================================================
x = readInt("Type an int > 0: ")
y = readInt("Type an int > 1: ")
assert ???
a = x + y
b = 2 * a
"""{ subgoal: (b - (y + 1)) > 0 }"""
d = b - (y + 1)
"""{ goal: d > 0 }"""
assert d > 0
print d
===================================================
How did we get this? Since the assignment ``equates'' d with
b - (y + 1), we substitute d = b - (y + 1) into the goal,
that is, we computed the new subgoal as
===================================================
[b - (y + 1) / d] ( d > 0 ) = (b - (y + 1)) > 0
===================================================
Let's reread this one step, forwards style and calculate the
forwards semantics with the forwards-assignment law:
===================================================
"""{ (b - (y + 1)) > 0 }"""
d = b - (y + 1)
"""{ 1. d == b - (y + 1) premise
2. (b - (y + 1)) > 0 premise
3. d > 0 subsitution 1 2
}"""
===================================================
The forwards-assignment law affirms that there is merely a substitution
step involved to go from the subgoal to the goal. Note that the forwards
step generated an extra fact, ((b - (y + 1)) > 0).
Returning to the example, we ask what subgoal is needed to
assert (b - (y + 1)) > 0 after b = 2 * a executes.
If we again do the substitution trick:
[2*a / b] ((b - (y + 1)) > 0) = ((2*a) - (y + 1)) > 0
we find the next subgoal:
===================================================
x = readInt("Type an int > 0: ")
y = readInt("Type an int > 1: ")
assert ???
a = x + y
"""{ subgoal: ((2*a) - (y + 1)) > 0 }"""
b = 2 * a
"""{ subgoal: (b - (y + 1)) > 0 }"""
d = b - (y + 1)
"""{ goal: d > 0 }"""
assert d > 0
print d
===================================================
(If we wish, we can again apply the forwards-assignment law to confirm
that this subgoal leads to the goal we want.)
We finish the subgoaling:
===================================================
x = readInt("Type an int > 0: ")
y = readInt("Type an int > 1: ")
assert ???
"""{ subgoal: [x + y / a ]( ((2*a) - (y + 1)) > 0 )
that is, ((2*(x + y)) - (y + 1)) > 0 }"""
a = x + y
"""{ subgoal: ((2*a) - (y + 1)) > 0 }"""
b = 2 * a
"""{ subgoal: (b - (y + 1)) > 0 }"""
d = b - (y + 1)
"""{ goal: d > 0 }"""
assert d > 0
print d
===================================================
The least restrictive (``weakest'') condition at the beginning
that ensures d > 0 at the end is
((2*(x + y)) - (y + 1)) > 0 .
We can simplify this to 2*x + y - 1 > 0, and this is the
missing assert:
===================================================
x = readInt("Type an int > 0: ")
y = readInt("Type an int > 1: ")
assert 2*x + y - 1 > 0
"""{ ((2*(x + y)) - (y + 1)) > 0 }"""
a = x + y
"""{ ((2*a) - (y + 1)) > 0 }"""
b = 2 * a
"""{ (b - (y + 1)) > 0 }"""
d = b - (y + 1)
"""{ d > 0 }"""
assert d > 0
print d
===================================================
When we read this proof forwards, we see how naturally the asserts
lead directly to the goal.
The technique works even when there is a self-referential assignment:
===================================================
assert x > 1
x = x + 1
"""{ goal: x > 2 }"""
===================================================
We calculate that the subgoal before the assignment must be
[x + 1 / x](x > 2), which is (x + 1) > 2. A small algebra
step completes the forwards proof of the backwards deduction:
===================================================
assert x > 1
"""{ 1. x > 1 premise
2. x + 1 > 2 algebra 1 }"""
x = x + 1
"""{ 1. x = x_old + 1 premise
2. x_old + 1 > 2 premise
3. x > 2 subst 1 2
}"""
===================================================
By reasoning backwards, we avoid the need to work directly with
x_old --- the subgoals we calculate by substitution
are correctly expressed in terms of x.
So, if your program has a clearly stated goal, use backwards reasoning to prove that the goal is achieved.
This conditional command sets max to the larger of numbers x and y:
===================================================
if x > y :
max = x
else :
max = y
===================================================
What can we assert when the command finishes, no matter what the
values of x and y might be?
First, when we analyze the then-arm, we have
===================================================
max = x
"""{ max == x }"""
===================================================
and when we analyze the else-arm, we have
===================================================
max = y
"""{ max == y }"""
===================================================
These two deductions imply that, when the conditional finishes,
one or the other property holds true:
===================================================
if x > y :
max = x
else :
max = y
"""{ max == x or max == y }"""
===================================================
This illustrates the first principle of conditional commands:
the knowledge producted by the command is the disjunction
(or) of the knowledge produced by each arm.
Later, we will learn how to apply cases analyses on disjunctive
assertions to extract useful knowledge.
Recall that the intent of the conditional was to set max so that it holds the larger of x and y. The assertion we proved so far does not imply the desired goal. This is because we ignored a critical feature of a conditional command: By asking a question --- the test --- the conditional command generates new knowledge from the answer.
For the then arm, we have the new knowledge that x > y;
for the else-arm, we have that not(x > y), that is, y >= x.
We can embed these assertions into the analysis of the conditional
command, like this, and conclude that, in both cases,
max holds the maximum of x and y:
===================================================
if x > y :
"""{ 1. x > y premise }"""
max = x
"""{ 1. x > y premise
2. max == x premise
3. max >= x algebra 2
4. max >= y algebra 1 3
5. max >= x and max >= y andi 3 4
}"""
else :
"""{ 1. not(x > y) premise
2. y >= x algebra 1
}"""
max = y
"""{ 1. max == y premise
2. y >= x premise
3. max >= y algebra 1
4. max >= x algebra 1 2
5. max >= x and max >= y andi 4 3
}"""
"""{ 1. max >= x and max >= y premise (from the if-command) }"""
===================================================
Each arm generates the assertion that
max >= x and max >= y.
Now, in both cases of the or-formula, we can conclude merely
max >= x and max >= y, as listed in the proof's last line.
More precisely stated, the proof's last line is (max >= x and max >= y) or (max >= x and max >= y), but this is exactly the same as max >= x and max >= y.
We are not yet finished with this example: the desired goal is
truly
===================================================
max >= x and max >= y and (max == x or max == y)
===================================================
You should build a proof of this goal assertion by combining
the two partial proofs that we have already constructed.
Here is the schematic of the forwards law for conditionals:
"""{ ... P }"""
if B :
"""{ 1. B premise
2. P premise
... }"""
C1
"""{ ... Q1 }"""
else :
"""{ 1. notB premise
2. P premise
... }"""
C2
"""{ ... Q2 }"""
"""{ 1. Q1 or Q2 premise }"""
That is, given the assertion, P, at the conditional's start,
the then-arm, C1 uses P and B to generate assertion, Q1,
and the else-arm, C2, uses P and notB to generate
assertion Q2.
These two conclusions are joined at the conditional's conclusion.
When a conditional command lacks an else-arm, e.g.,
if x < 0 :
x = 0 - x
we analyze it with an empty one, e.g., with an else-arm that
holds pass:
===================================================
if x < 0 :
"""{ 1. x < 0 premise }"""
x = 0 - x
"""{ 1. x == 0 - x_old premise
2. x_old < 0 premise
3. x > 0 algebra 1 2
}"""
else :
"""{ 1. not(x < 0) premise
2. x >= 0 algebra 1
}"""
pass
"""{ 1. x >= 0 premise }"""
"""{ 1. (x > 0) or (x >= 0) premise
2. x >= 0 ore 1
}"""
===================================================
Here, the conclusion of the if-command is
(x > 0) or (x >= 0) , but clearly it is the case that x > 0 implies
x >= 0 (and clearly, the other case, x >= 0 implies x >= 0, holds), so
we conclude at the very last line that
x >= 0; the justification is ore --- ``or elimination'' ---
commonly known as proof by cases.
As usual, we motivate the law through an example.
Using the previously seen program that assigns to max,
perhaps we want to prove the
goal, max >= x and max >= y.
Our intuition tells us that this goal must be achieved through
through both paths of the conditional command.
That is, there are two subgoals to calculate, one for the then-arm
and one for the else-arm:
"""{ subgoal_1 : ??? }"""
max = x
"""{ goal: max >= x and max >= y }"""
and also
"""{ subgoal_2 : ??? }"""
max = y
"""{ goal: max >= x and max >= y }"""
Using the backwards assignment law, we calculate the subgoals for
the two arms:
===================================================
"""{ subgoal_1: x >= x and x >= y
that is, x >= y (since x >= x is always true) }"""
max = x
"""{ goal: max >= x and max >= y }"""
===================================================
and also
===================================================
"""{ subgoal_2: y >= x and y >= y
that is, y >= x (since y >= y is always true) }"""
max = y
"""{ goal: max >= x and max >= y }"""
===================================================
Now, given the extra knowledge that the conditional's
test is x > y, we can apply this knowledge to the subgoals.
First, the then-arm will achieve the final goal if the conditional's
test provides enough extra information to achive the subgoal:
x > y implies x >= y
The logical operator, implies (read as ``implies''),
can be read as a ``logical if-then'': ''if x > y is assumed
as a fact, then x >= y follows as a consequence.''
Or, read it as, ``when x > y holds true, then so must x >= y.''
Similarly, the else-arm will achieve the final goal if the
test provides enough extra information to achive the subgoal:
not(x > y) implies y >= x
Overall, the subgoal for the completed
if-command is
( x > y implies x >= y ) and ( not(x > y) implies y >= x )
Here is a summary of what we have uncovered:
===================================================
"""{ if's subgoal: (x > y implies x >= y) and (not(x > y) implies y >= x) }"""
if x > y
"""{ subgoal_1: x >= x and x >= y
that is, x >= y }"""
max = x
"""{ goal: max >= x and max >= y }"""
else :
"""{ subgoal_2: y >= x and y >= y
that is, y >= x }"""
max = y
"""{ goal: max >= x and max >= y }"""
"""{ final goal: max >= x and max >= y }"""
===================================================
The implication operator, implies, is new to us, and we must study it carefully, which we do in a later chapter. For now, pretend it is a ``logical if.''
Let's analyze the subgoal we calculated for the if-command.
First, is x > y implies x >= y
a true fact? Well, whenever x > y holds true,
then by algebra, x >= y must hold true, too --- the latter is a
consequence of the assumption that x > y holds true.
Similarly,
not(x > y) implies y >= x is a true fact, because
whenever not(x > y) holds, then y >= x follows.
So, the subgoal is true, that is,
(x > y implies x >= y) and (not(x > y) implies y >= x)
has the same meaning as t.
Here is an example where the desired goal is not always achieved:
===================================================
x = readInt()
if x > 0 :
x = 0 - x
else :
pass
"""{ goal: x >= 0 }"""
===================================================
Using backwards reasoning, we quickly deduce that
===================================================
x = readInt()
"""{ if's subgoal: (x > 0 implies -x > 0) and (not(x > 0) implies x >= 0) }"""
if x > 0 :
"""{ subgoal: -x >= 0 }"""
x = 0 - x
"""{ goal: x >= 0 }"""
else :
"""{ subgoal: x >= 0 }"""
pass
"""{ goal: x >= 0 }"""
"""{ goal: x >= 0 }"""
===================================================
We see immediately there is no way that the if-subgoal holds
true: clearly, (x > 0 implies -x > 0) is false, and
not(x > 0) implies x >= 0 is false also (for values of x that are
negative). So,
we cannot prove that the program achieves its goal.
Forwards reasoning also fails:
===================================================
x = readInt()
if x > 0 :
"""{ x > 0 }"""
x = 0-x
"""{ 1. x = 0 - x_old premise
2. x_old > 0 premise
3. x < 0 algebra 2
}"""
else :
"""{ 1.not( x > 0) premise
2. x <= 0 algebra 1
}"""
pass
"""{ 1. x <= 0 premise }"""
"""{ assert: x < 0 or x <= 0
There is no way we can deduce x >= 0 ... }"""
===================================================
In contrast, say that we consider this variation of the example:
===================================================
x = 0
if x > 0 :
x = 0 - x
else :
pass
"""{ goal: x >= 0 }"""
===================================================
We can prove, using either forwards or backwards reasoning,
that this particular program achieves its goal (because we
can deduce that the else-arm will be used).
The precise reasoning will be presented in a later chapter.
We of course use conditional commands to avoid error situations.
Consider
===================================================
x = readInt()
if x != 0 :
r = 1.0 / x
else
r = 0
print r
===================================================
When the input number assigned to x is nonzero, then the output,
r, will be x's reciprocal.
The forwards reasoning that asserts this fact goes as follows:
===================================================
x = readInt()
if x != 0 :
"""{ not(x == 0) }"""
r = 1.0 / x
"""{ not(x != 0) and r = 1.0/x }"""
else
"""{ x == 0 }"""
r = 0
"""{ x == 0 and r == 0 }"""
"""{ (not(x == 0) and r = 1.0/x) or (x == 0 and r = 0) }"""
print r
===================================================
Once we learn better the deductive laws (the ``algebra'') for and, or
and implies, we
can prove that
(not(x == 0) and r == 1.0/x) or (x == 0 and r == 0)
says exactly the same thing as
(not(x == 0) implies r == 1.0/x) and ((x == 0) implies r == 0)
which reflects the strategy we had in mind when we wrote the conditional
command.
Here is the deduction law for an assignment command:
"""{ ...
m. P
}"""
x = e
"""{ 1. x == [x_old/x]e premise
2. [x_old/x]P premise
...
n. Q (where Q must _not_ mention x_old
}"""
"""{ [e/x]G }""" x = e """{ G }"""That is, the subgoal (precondition) required to achieve (postcondition) G with the assignment is [e/x]G.
"""{ ... P }""" if B : """{ 1. B premise 2. P premise ... }""" C1 """{ ... Q1 }""" else : """{ 1. notB premise 2. P premise ... }""" C2 """{ ... Q2 }""" """{ 1. Q1 or Q2 premise }"""That is, if we deduce from precondition B and P and commands C1 a postcondition Q1 (and do the same for C2, we can assemble the postcondition Q1 or Q2 for the conditional. (Of course, if Q1 is identical to Q2, we can conclude just Q1.)
"""{ (B implies S1) and (notB implies S2) }""" if B : """{ S1 }""" C1 """{ G }""" else : """{ S2 }""" C2 """{ G }""" """{ G }"""That is, if we deduce that S1 is the subgoal required by C1 to attain goal G (similarly for C2), then the subgoal (precondition) needed to attain the goal (postcondition) by the conditional is (B implies S1) and (notB implies S2).