Foundations — Why Correctness, and the Logic of Propositions

The opening move of Gries’ The Science of Programming: why we bother to prove programs correct at all, and the propositional-logic algebra you must own before a single Hoare triple makes sense.

Every serious treatment of program correctness begins not with code but with logic. David Gries’ The Science of Programming (1981) is emphatic about this: you cannot reason precisely about a program until you can reason precisely about the predicates that describe its states. This page covers the two foundational chapters — the motivation (why correctness is worth proving, and what a program really is mathematically) and the propositional calculus that becomes the everyday tool for that reasoning. Get the algebra of , , ¬, , and into your fingers here, and the later machinery — predicates with quantifiers in the predicate calculus, and weakest preconditions in the wp calculus — follows almost mechanically.

This is an original study summary for quick reference. Notation follows Gries: T/F for the boolean constants, ∧ ∨ ¬ ⇒ ≡ for the connectives, and predicates read as sets of states. It is a companion to the book, not a substitute — see the original for the full, rigorous development and exercises.

Contents

  1. Why prove programs correct
  2. Propositions
  3. Equivalence transformations
  4. Natural deduction (briefly)
  5. Stronger and weaker predicates
  6. Summary

Why prove programs correct

The case for correctness proofs rests on a single, uncomfortable observation, due to Dijkstra and quoted by Gries: “Testing can show the presence of bugs, but never their absence.” A test exercises one path through one input; passing it tells you nothing about the untried inputs, and for any non-trivial program the untried inputs vastly outnumber the tried ones. A proof, by contrast, argues over all states at once. It attacks the absence of bugs directly, which no finite battery of tests can do.

The economic argument reinforces the logical one. A defect caught while reasoning about a specification costs a moment’s thought; the same defect caught in production — after it has shipped, corrupted data, and woken an on-call engineer — costs orders of magnitude more. The later a bug is found, the more expensive it is to remove, and testing by its nature finds bugs late.

Gries’ thesis, stated in the book’s preface and carried through every chapter, inverts the usual habit:

A program and its proof should be developed hand in hand,
with the proof usually leading the way.

You do not write the code and then hope to verify it afterward. You start from a specification — a precondition and a postcondition — and let the proof obligations drive each construct you add, so the finished program is correct by construction. Debugging is replaced by derivation.

✗ Code-first, test-later
# write it, run it, patch what breaks
def abs_val(x):
    return x         # "usually positive, ship it"
# is it right for x < 0?  x = 0?
# you find out only when a test fails —
# and only for the inputs you happened
# to try. The rest stay silent bugs.
✓ Spec-first, correct by construction
# state the contract, derive the body
# pre:  T           (any integer x)
# post: r = |x|,  i.e.
#       (r = x or r = -x) and r ≥ 0
# the two guards x ≥ 0 and x ≤ 0
# fall out of the postcondition —
# every case is covered by design.

What a program is, mathematically. To reason this way you need a precise model of what a program means. Gries builds it from two ideas:

Why logic comes first: a specification is a pair of predicates, and a proof is a chain of implications between predicates. If your predicate algebra is shaky, program proofs are impossible. So before any programming, master the calculus of propositions below.

Propositions

A proposition is a boolean expression. Its atoms are the two constants T (true) and F (false) together with boolean variables, combined by five connectives.

ConnectiveSymbolRead asTrue exactly when
Negation¬P“not P”P is false
ConjunctionP ∧ Q“P and Q”both P and Q are true
DisjunctionP ∨ Q“P or Q”at least one of P, Q is true
ImplicationP ⇒ Q“P implies Q”P is false or Q is true
EquivalenceP ≡ Q“P iff Q”P and Q have the same value

Implication is the connective people misread most often. P ⇒ Q is not a claim of cause; it is simply ¬P ∨ Q. In particular it is true whenever P is false — “false implies anything.” Equivalence (also written ) is boolean equality: P ≡ Q holds when both sides carry the same truth value.

Precedence

To cut parentheses, the connectives bind in this order, tightest first. Use explicit parentheses whenever clarity is at stake.

¬  (tightest)  >  ∧  >  ∨  >  ⇒  >  ≡  (loosest)

So ¬P ∧ Q ∨ R ⇒ S parses as ((((¬P) ∧ Q) ∨ R) ⇒ S). When in doubt, over-parenthesise; the algebra below never depends on precedence tricks.

Truth as a set of states

Here is the shift that makes the rest of the book work. Do not think of a proposition (or later, a predicate) as merely “true or false.” Think of it as the set of states in which it is true. Then is set intersection, is set union, ¬ is complement, and P ⇒ Q means “the state-set of P is contained in that of Q.” The constant T denotes the set of all states; F denotes the empty set. This picture is what turns implication into subset containment — the backbone of stronger and weaker below.

A small truth table

Every proposition is defined by its truth table — its value in each combination of its variables. Here is P ⇒ Q beside its equivalent ¬P ∨ Q, which is the cleanest way to see that the two are the same proposition.

  P    Q   |  ¬P   |  P ⇒ Q  |  ¬P ∨ Q
-----------+-------+---------+----------
  F    F   |   T   |    T    |    T
  F    T   |   T   |    T    |    T
  T    F   |   F   |    F    |    F
  T    T   |   F   |    T    |    T
# columns for  P ⇒ Q  and  ¬P ∨ Q  match in every row
# therefore  (P ⇒ Q) ≡ (¬P ∨ Q)   is a tautology

A proposition true in every row is a tautology (equivalent to T); one false in every row is a contradiction (equivalent to F). Truth tables are a decision procedure — finite and mechanical — but they blow up as 2n rows for n variables, which is exactly why the algebraic laws in the next section matter: they let you reason without enumerating.

Equivalence transformations

The working method for propositions is not truth tables but rewriting: replace a sub-expression by an equivalent one, over and over, the way you simplify algebra with a(b+c) = ab + ac. Because is genuine equality of propositions, any law below may be applied to a sub-expression in place, in either direction. The laws are your toolkit for simplifying preconditions, discharging proof obligations, and showing two specifications equal.

LawStatement
CommutativityP ∧ Q ≡ Q ∧ P  ;  P ∨ Q ≡ Q ∨ P
Associativity(P ∧ Q) ∧ R ≡ P ∧ (Q ∧ R)  ;  (P ∨ Q) ∨ R ≡ P ∨ (Q ∨ R)
Distributivity (∧ over ∨)P ∧ (Q ∨ R) ≡ (P ∧ Q) ∨ (P ∧ R)
Distributivity (∨ over ∧)P ∨ (Q ∧ R) ≡ (P ∨ Q) ∧ (P ∨ R)
De Morgan¬(P ∧ Q) ≡ ¬P ∨ ¬Q  ;  ¬(P ∨ Q) ≡ ¬P ∧ ¬Q
Negation / Excluded middleP ∨ ¬P ≡ T  ;  P ∧ ¬P ≡ F
Double negation¬(¬P) ≡ P
IdentityP ∧ T ≡ P  ;  P ∨ F ≡ P
Zero (domination)P ∧ F ≡ F  ;  P ∨ T ≡ T
IdempotencyP ∧ P ≡ P  ;  P ∨ P ≡ P
ImplicationP ⇒ Q ≡ ¬P ∨ Q
ContrapositiveP ⇒ Q ≡ ¬Q ⇒ ¬P
EquivalenceP ≡ Q  ≡  (P ⇒ Q) ∧ (Q ⇒ P)

A short worked simplification shows the style — each step names the law that licenses it, exactly as you will annotate proof steps later:

# Simplify:  ¬(P ⇒ Q)
  ¬(P ⇒ Q)
≡ ¬(¬P ∨ Q)        # Implication
≡ ¬(¬P) ∧ ¬Q      # De Morgan
≡ P ∧ ¬Q           # Double negation
# So the only way P ⇒ Q fails is  P true and Q false — as expected.
✗ Negating a conjunction by hand
# "not (0 ≤ i and i < n)"
# wrong: people write
#     0 > i and i ≥ n
# — they negated each part but
# kept the AND. That set is empty!
# (i can't be both < 0 and ≥ n)
✓ De Morgan flips the connective
# ¬(0 ≤ i ∧ i < n)
#   ≡ ¬(0 ≤ i) ∨ ¬(i < n)   (De Morgan)
#   ≡  i < 0  ∨  i ≥ n
# the AND became an OR — the
# out-of-bounds condition, correctly.

The Implication law (P ⇒ Q ≡ ¬P ∨ Q) is worth committing to memory above all others: it lets you eliminate entirely and reduce any proof obligation to the four basic connectives, where De Morgan and distribution do the rest.

Natural deduction (briefly)

Equivalence rewriting is one of two styles of formal proof. The other is natural deduction, which works not by substituting equals-for-equals but by applying inference rules that derive new propositions from ones already known. The distinction is worth naming:

The archetypal inference rule is modus ponens: from P and P ⇒ Q, conclude Q.

    P            # premise: P holds
    P ⇒ Q        # premise: P implies Q
  ---------      # modus ponens
    Q            # conclusion: Q holds

A formal proof in this style is a finite sequence of propositions in which every line is either a premise or follows from earlier lines by a named inference rule. Each step is justified — nothing is asserted without a rule that sanctions it. This is precisely the shape a program correctness proof takes: a chain of steps, each licensed by an axiom (assignment, composition) or an inference rule, from the precondition to the postcondition. In practice Gries leans mostly on equivalence rewriting for manipulating predicates, and reserves inference for stitching together the correctness argument at the level of triples — but both are the same underlying discipline of never taking an unjustified step.

Stronger and weaker predicates

Everything the book does later — and the whole point of weakest preconditions — rests on one ordering between propositions, defined purely by implication.

P is stronger than Q  ≡  P ⇒ Q
Equivalently: Q is weaker than P.

Under the set-of-states view, P ⇒ Q means
states(P) ⊆ states(Q):  stronger = fewer states = more restrictive.

The intuition, once the set picture is in hand, is clean. A stronger predicate makes a bigger claim, so it holds in fewer states — it is more demanding, more restrictive. A weaker predicate asks for less, so it holds in more states — it is more permissive. For example x = 5 is stronger than x > 0 (one state versus infinitely many), and x = 5 ⇒ x > 0.

The two extremes anchor the scale:

Now the payoff, which is the reason this page exists. When you ask “what must be true before command S so that R holds afterward?”, there are usually many correct preconditions — and among them, some demand more than necessary. The weakest precondition is the one that demands the least: the most permissive requirement on the starting state that still guarantees the desired result. Anything weaker would let in a state from which S fails to establish R; anything stronger needlessly excludes valid starting states. Choosing the weakest is what makes derivation exact rather than merely sufficient — and that is the operator wp(S, R) introduced in the weakest-precondition calculus. This section is the whole reason “weakest” is the word we want.

Keep the direction straight: “weaker” is not “less useful.” The weakest precondition is the best answer to a derivation question, because it rejects the fewest valid starting states while still being sufficient. Weak = permissive = maximally informative about what is truly required.

Summary

IdeaThe one-line takeaway
Why prove programsTesting shows the presence of bugs, never their absence — a proof argues over all states at once.
Gries’ thesisDevelop program and proof together, proof leading — correct by construction, not by patching.
Program as predicate transformerA state assigns values to variables; a program maps postconditions back to preconditions.
PropositionsConstants T/F plus ¬ ∧ ∨ ⇒ ≡; precedence ¬ > ∧ > ∨ > ⇒ > ≡.
Truth as a set of states=intersection, =union, ¬=complement, =subset; T=all states, F=none.
Equivalence transformationsReason by rewriting with laws (De Morgan, distribution, P⇒Q ≡ ¬P∨Q) — no truth-table blow-up.
Natural deductionInference derives what follows (modus ponens); a proof is a sequence of justified steps.
Stronger / weakerP stronger than Q iff P⇒Q; stronger = fewer states; F strongest, T weakest.
Why “weakest”The most permissive precondition that still works — the exact requirement, setting up wp.
The recurring theme of the foundations: logic is not a prelude you skip — it is the language of correctness. Predicates are sets of states, proofs are chains of implications, and “weakest” is a precise, desirable thing. With this algebra in hand, the predicate calculus adds quantifiers over arrays, and the wp calculus turns the whole apparatus into a program-derivation engine.