BCP 23,25: There are a lot of questions about the flow of this
material. Needs a deep look. In particular:
One feels that it takes a rather circuitous route to get to
formal decorated programs -- there might be a way to just go
straight there.
More importantly, it isn't very clear to me that the
version of decorated programs that we wound up with is really
the right one -- why spend all this time writing annotations
that we then say are unnecessary? Seems like we could define
decorated programs with a lighter annotation burden (as in the
exercise at the end) and just add optional annotations when we
want to, to make particular examples clearer.
The rigidity of the Hoare rules as stated in the last chapter
is also annoying here at many points. Building the rules of
consequence into all the other rules might make a lot of things
smoother. Would be a big change (also to Hoare.v), but definitely
worth a try.
And related...
Note to developers (Michael Clarkson @clarksmr, before next release, 2020)
Here are a bunch of improvements I wanted to make but
didn't have time to get to. Maybe next time if no one else gets to
them first.
This chapter feels largely disconnected from the style of the
rest of the series, which is "100% Rocq script". There's a
significant amount of "just comments" here instead. I think
we could make this much better by introducing formal decorated
programs right after we informally define them. Then in the
examples that follow do each informal decorated program (to
get students to find the right assertions, more or less)
followed immediately by a formal version, instead of delaying
formal so far to the end. The parity exercise is a good
example of one that already almost does this already. (BCP 21:
Done!)
There are several places where we are verifying Imp program
schemas, not actual programs. We're mixing Rocq variables with
Imp variables. That's confusing. One example is two_loops;
I've tried to mark others as I come across them. (BCP: I've
added some quizzes and such to try to clarify the relation
between programs / triples and program/triple schemas.
BCP 25: I think this is a non-issue now.)
Weakest preconditions show up in this chapter as completely
optional, then are revisited (and required) in HoareAsLogic.
Consider moving the entire treatment to that chapter. (BCP
23: Yes, we should do that!)
It seems a shame that the SparseAnnotations section is not
visible in the full version, but only in a solution. It's
fantastic! It would be great to show it off. (BCP 25: Yes!
Indeed, perhaps it should even replace the current treatment!)
Note to developers
HIDE: Some useful theorems about Imp are not being redefined for
the modified versions of Imp. We should either re-prove them or add
hints (or exercises!) about this. BCP 20: Which ones???
Quiz
On a piece of paper (or whatever), write down a Hoare-triple
specification for the following program:
X := 2;
Y := X + X
Quiz
Write down a (useful) specification for the following program:
X := X + 1; Y := X + 1
Quiz
Write down a (useful) specification for the following program:
if X ≤ Y then
skip
else
Z := X;
X := Y;
Y := Z
end
Quiz
Write down a (useful) specification for the following program:
X := m;
Y := X + X
Quiz
Write down a (useful) specification for the following program:
X := m;
Z := 0;
while X ≠ 0 do
X := X - 2;
Z := Z + 1
end
The beauty of Hoare Logic is that it is syntax directed: the
structure of proofs exactly follows the structure of programs.
We can record the essential ideas of a Hoare-logic proof —
omitting low-level calculational details — by "decorating" a
program with appropriate assertions on each of its commands.
Such a decorated program carries within itself an argument for
its own correctness.
For example, consider the program:
X := m;
Z := p;
while X ≠ 0 do
Z := Z - 1;
X := X - 1
end
Here is one possible specification for this program, in the
form of a Hoare triple:
{{ True }}
X := m;
Z := p;
while X ≠ 0 do
Z := Z - 1;
X := X - 1
end
{{ Z = p - m }}
Here is a decorated version of this program, embodying a
proof of this specification:
{{ True }} ->>
{{ m = m }}
X := m
{{ X = m }} ->>
{{ X = m ∧ p = p }};
Z := p;
{{ X = m ∧ Z = p }} ->>
{{ Z - X = p - m }}
while X ≠ 0 do
{{ Z - X = p - m ∧ X ≠ 0 }} ->>
{{ (Z - 1) - (X - 1) = p - m }}
Z := Z - 1
{{ Z - (X - 1) = p - m }};
X := X - 1
{{ Z - X = p - m }}
end
{{ Z - X = p - m ∧ ¬ (X ≠ 0) }} ->>
{{ Z = p - m }}
Note to developers
HIDE: MRC'20: It bothers me a little in the proof above (and
similarly throughout the whole file really when it comes to guards)
that when we get to this part:
[[
while X ≠ 0 do {{ Z - X = p - m ∧ X ≠ 0 }} ->>
]]
we are inconsistent about [X ≠ 0] vs. [~(X=0)]. I admit they
evaluate the same (er, sort of---the former is a [bexp] whereas the
latter is an assertion), but they aren't syntactically the same.
Since what we're teaching here (mechanized Hoare logic) is fussy
about syntax, it strikes me as something we ought to be precise
about. But it's an annoying change to propagate through the file,
so I haven't done it. Is it worth a comment, or do others not get
bothered by this? Another way to fix this would be to add the [≠]
operator to Imp, so that we can write the guard in the nicer way.
BCP 20: I think adding in a few more boolean operators at the
outside is the way to go...
BCP 21: ... and I've now done this: ≠ is available in formal
bexps (also >).
Concretely, a decorated program consists of the program's text
interleaved with assertions (sometimes multiple assertions
separated by ->>).
A decorated program can be viewed as a compact representation of a
proof in Hoare Logic: the assertions surrounding each command
specify the Hoare triple to be proved for that part of the program
using one of the Hoare Logic rules, and the structure of the
program itself shows how to assemble all these individual steps
into a proof for the whole program.
Consider the following program, which swaps the values of two
variables using addition and subtraction, instead of by assigning
to a temporary variable.
X := X + Y;
Y := X - Y;
X := X - Y
We can give a proof, in the form of decorations, that this program is
correct — i.e., it really swaps X and Y — as follows.
WORK IN CLASS
Note to developers
HIDE: BCP 21: This side comment seems too technical:
(Note that we are working with natural numbers rather than
fixed-width machine integers, so we don't need to worry about
the possibility of arithmetic overflow anywhere in this
argument. This makes life quite a bit simpler!)
HIDE: A quick / optional exercise using just assignment here
would be good.
Here's a simple program using conditionals, along
with a possible specification:
{{ True }}
if X ≤ Y then
Z := Y - X
else
Z := X - Y
end
{{ Z + X = Y ∨ Z + Y = X }}
Let's turn it into a decorated program...
WORK IN CLASS
Note to developers
NOTATION: LATER: The ~ in that paragraph will typeset wrong if
the space after it is removed. Maybe it's better to give up on
all the unicode hacks in the generated HTML...?
Let's do one more example of simple reasoning about a loop.
The following Imp program calculates the integer quotient and
remainder of parameters m and n.
X := m;
Y := 0;
while n ≤ X do
X := X - n;
Y := Y + 1
end;
If we replace m and n by concrete numbers and execute the program, it
will terminate with the variable X set to the remainder when m
is divided by n and Y set to the quotient.
Here's a possible specification:
{{ True }}
X := m;
Y := 0;
while n ≤ X do
X := X - n;
Y := Y + 1
end
{{ n * Y + X = m ∧ X < n }}
From an informal proof in the form of a decorated program, it is
"easy in principle" to read off a formal proof using the Lean
theorems corresponding to the Hoare Logic rules, but these proofs
can be a bit long and fiddly.
For example...
defreduceToZero:Com:=imp{while(X≠0){X:=X-1}}theoremdeclaration uses `sorry`reduce_to_zero_correct':{{True}}reduceToZero{{X=0}}:=⊢ {{True}}~reduceToZero{{X=0}}-- First put the postcondition into the form expected by-- the while rule.All goals completed! 🐙
A little more (OK, quite a bit more) tactic fanciness for
helping deal with the boring parts of the process of proving
assertions:
This example shows that it is conceptually straightforward to read
off the main elements of a formal proof from a decorated program.
Indeed, the process is so straightforward that it can be
automated, as we will see next.
With a little more work, we can formalize the definition of
well-formed decorated programs and automate the boring mechanical
steps in proving that the decorations are correct.
The first thing we need to do is to formalize a variant of the
syntax of Imp commands that includes embedded assertions, which
we'll call "decorations." We call the new commands decorated
commands, or dcoms.
The choice of exactly where to put assertions in the definition of
dcom is a bit subtle. The simplest thing to do would be to
annotate every dcom with a precondition and postcondition —
something like this...
But this would result in very verbose decorated programs with a
lot of repeated annotations: a simple program like
skip;skip would be decorated like this,
with pre- and post-conditions around each skip, plus identical
pre- and post-conditions on the semicolon!
In other words, we don't want both preconditions and
postconditions on each command, because a sequence of two commands
would contain redundant decorations — the postcondition of the
first likely being the same as the precondition of the second.
Instead, our formal syntax of decorated commands will omit
preconditions whenever possible and embed just postconditions.
The skip command, for example, is decorated only with its
postcondition
skip {{ q }}
on the assumption that the precondition will be provided by
somebody else.
We carry the same assumption through the other syntactic forms:
each decorated command is assumed to carry its own postcondition
within itself but take its precondition from its context in
which it is used.
Sequences d₁ ; d₂ need no additional decorations.
Why?
Because inside d₂ there will be a postcondition, which also
serves as the postcondition of d₁;d₂.
Similarly, inside d₁ there will also be a postcondition, which
additionally serves as the precondition for d₂.
An assignment X := a is decorated only with its postcondition:
X := a {{ q }}
A conditional if b then d₁ else d₂ is decorated with a
postcondition for the entire statement, as well as preconditions
for each branch:
if b then {{ p₁ }} d₁ else {{ p₂ }} d₂ end {{ q }}
Note to developers (Benjamin Pierce @bcpierce00, before next release, 2025)
Maybe we need a note here about why we don't just
calculate p₁ and p₂ later, once we know the precondition of the
whole loop. Indeed, we could (and perhaps should, as discussed
elsewhere), but we are doing something simpler for the moment.
A loop while (b) {d} is decorated with its final
postcondition plus a precondition for the body:
while (b) {{ p }} { d } {{ q }}
The postcondition embedded in d serves as the loop invariant.
Implications ->> can be added as decorations either for a
precondition...
->> {{ p }} d
...or for a postcondition:
d ->> {{ q }}
The former is waiting for another precondition to be supplied by
the context; the latter relies on the postcondition already
embedded in d.
Putting this all together gives us the formal syntax of decorated
commands:
Note to developers (Benjamin Pierce @bcpierce00, before next release, 2025)
It would be nice to use <{ ... }> notations
in this definition and the ones below...
| <{ skip {{p}} }> => p
| <{ _; d₂ }> => post d₂
| <{ _ := _ {{q}} }> => q
The outer Hoare triple of a decorated program is just a Prop;
thus, to show that it is valid, we need to produce a proof of
this proposition.
We will do this by extracting "proof obligations" from the
decorations sprinkled throughout the program.
These obligations are often called verification conditions,
because they are the facts that must be verified to see that the
decorations are locally consistent and thus constitute a proof of
validity of the outer triple.
The function DCom.VerificationConditions takes a decorated command
d together with a precondition p and returns a proposition
that, if it can be proved, implies that the triple
{{p}} d.erase {{d.postcondition}}
is valid.
It does this by walking over d and generating a big conjunction
that includes
local consistency checks for each form of command, plus
uses of ->> to bridge the gap between the assertions found
inside a decorated command and the assertions imposed by the
external precondition; these uses correspond to applications
of the consequence rule.
Local consistency is defined as follows...
The decorated command
skip {{q}}
is locally consistent with respect to a precondition p if
p ->> q.
The sequential composition of d₁ and d₂ is locally
consistent with respect to p if d₁ is locally consistent with
respect to p and d₂ is locally consistent with respect to
the postcondition of d₁.
An assignment
X := a {{q}}
is locally consistent with respect to a precondition p if:
p ->> q [X ↦ a]
A conditional
if b then {{p₁}} d₁ else {{p₂}} d₂ end {{q}}
is locally consistent with respect to precondition p if
(1) p ∧ b ->> p₁
(2) p ∧ b ->> p₂
(3) d₁ is locally consistent with respect to p₁
(4) d₂ is locally consistent with respect to p₂
(5) d₁.postcondition ->> q
(6) d₂.postcondition ->> q
A loop
while (b) {{{q}} d} {{r}}
is locally consistent with respect to precondition p if:
(1) p ->> d.postcondition
(2) d.postcondition ∧ b ->> q
(3) d.postcondition ∧ b ->> r
(4) d is locally consistent with respect to q
A command with an extra assertion at the beginning
->> {{q}} d
is locally consistent with respect to a precondition p if:
(1) p ->> q
(2) d is locally consistent with respect to q
A command with an extra assertion at the end
d ->> {{q}}
is locally consistent with respect to a precondition p if:
(1) d is locally consistent with respect to p
(2) d.postcondition ->> q
With all this in mind, we can write a verification condition
generator that takes a decorated command and reads off a
proposition saying that all its decorations are locally
consistent.
Formally, since a decorated command is "waiting for its
precondition" the main VC generator takes a dcom plus a given
precondition as arguments.
Note to developers
HIDE: There was some discussion in 2016 about whether the VC
generator should should use equivalence or implication in a few
places. I (BCP) believe Phil (Wadler) changed some instances of
the former to the latter.
HIDE: MRC'20: a written explanation of each part of this would be
quite nice. BCP 21: Agreed!! (BCP 23: But it's kind of what's
just above...)
The following key theorem states that DCom.VerificationConditions
does its job correctly. Not surprisingly, each of the Hoare Logic
rules plays a critical role at some point in the proof.
The propositions generated by DCom.VerificationConditions are fairly
big and contain many conjuncts that are essentially trivial.
Note to developers
HIDE: MRC'20: The conditions here used to be just [Eval]ed instead
of being duplicated in a comment. They were actually incorrect
because of changes to notation. Putting them in as an [Example]
will force us to keep them up-to-date. APT20: Yes, but but stating
this an equality completely misses the point about verify_assertion! So I
changed things back.
To automate the overall process of verification, we can use
verification_correct to extract the verification conditions, use
verify_assertion to verify them as much as it can, and finally tidy
up any remaining bits by hand.
Once the outer pre- and postcondition are chosen, the only
creative part in verifying programs using Hoare Logic is finding
the right loop invariants...
The following program subtracts the value of X from the value of
Y by repeatedly decrementing both X and Y. We want to verify its
correctness with respect to the pre- and postconditions shown:
{{ X = m ∧ Y = n }}
while X ≠ 0 do
Y := Y - 1;
X := X - 1
end
{{ Y = n - m }}
To verify this program, we need to find an invariant Inv for the
loop. As a first step we can leave Inv as an unknown and build a
skeleton for the proof by applying the rules for local
consistency, working from the end of the program to the beginning,
as usual, and without doing any thinking at all yet.
This leads to the following skeleton:
(1) {{ X = m ∧ Y = n }} ->> (a)
(2) {{ Inv }}
while X ≠ 0 do
(3) {{ Inv ∧ X ≠ 0 }} ->> (c)
(4) {{ Inv [X ↦ X-1] [Y ↦ Y-1] }}
Y := Y - 1;
(5) {{ Inv [X ↦ X-1] }}
X := X - 1
(6) {{ Inv }}
end
(7) {{ Inv ∧ ¬ (X ≠ 0) }} ->> (b)
(8) {{ Y = n - m }}
Examining this skeleton, we can see that any valid Inv will
have to respect three conditions:
(a) it must be weak enough to be implied by the loop's
precondition, i.e., (1) must imply (2);
(b) it must be strong enough to imply the program's postcondition,
i.e., (7) must imply (8);
(c) it must be preserved by a single iteration of the loop, assuming
that the loop guard also evaluates to true, i.e., (3) must imply (4).
WORK IN CLASS (by filling in the previous template)
HIDE: CH: it might make sense to show all the variants
for future exercise builders, together with some hints on how to
write programs that are easier to verify
Move this later? Might be harder than some of the others.
Note to developers
HIDE: LY: Many are tempted to use division in their propositions here,
with the loop invariant [Y = m!/X!].
Informally, such a decorated program can be correct if we assume
they use real division (in q or r). The issue is that formally in Rocq,
the notation / is also in scope and means integer division, so a pedantic
interpretation would mark those answers wrong, even though that is most
likely *not* what students intended (thus making the grading unfair).
Should we explicitly forbid use of division for this exercise?
(I added a note to that effect in the problem statement).
MRC'20: I strengthened your note so that it explicitly advises
against division (and subtraction).
Note to developers (Michael Clarkson @clarksmr, before next release, 2020)
This is again a program schema rather than a program. Why not...
[[
{{ True }}
X := 0;
Y := 1;
Z := 1;
while X ≠ W do
Z := 2 * Z;
Y := Y + Z;
X := X + 1
end
{{ Y = 2 ^ (W + 1) - 1 }}
]]
...?
BCP 21: Ditto my response above. IMO this is not a problem.
Note to developers (Benjamin Pierce @bcpierce00, before next release, 2021)
This exercise should really be expanded into its
own whole section. Moreover, there is a proposal to make the
decorations in Hoare look more like the decorations earlier in the
present chapter. All three should be aligned.
HIDE: BCP 21: We talked about moving this stuff to the HoareAsLogic
chapter to lighten this chapter, but it fits awkwardly there, so
I'm leaving it here. It's optional anyway. We might consider
assigning one of the exercises as advanced-only though.
A useless (though valid) Hoare triple:
{{ False }} X := Y + 1 {{ X ≤ 5 }}
A better precondition:
{{ Y ≤ 4 ∧ Z = 0 }} X := Y + 1 {{ X ≤ 5 }}
The best precondition:
{{ Y ≤ 4 }} X := Y + 1 {{ X ≤ 5 }}
Assertion Y ≤ 4 is a weakest precondition of command
X := Y + 1 with respect to postcondition X ≤ 5. Think of weakest
here as meaning "easiest to satisfy": a weakest precondition is
one that as many states as possible can satisfy.
p is a weakest precondition of command c for postcondition q
if
p is a precondition, that is, {{p}} c {{q}}; and
p is at least as weak as all other preconditions, that is,
if {{p'}} c {{q}} then p' ->> p.
Note that weakest preconditions need not be unique. For
example, Y ≤ 4 was a weakest precondition above, but so are the
logically equivalent assertions Y < 5, Y ≤ 2 * 2, etc.
It is easy to show that any two weakest preconditions p and p'
of a command c with respect to postcondition q are logically
equivalent; that is, p <<->> p'.
What are weakest preconditions of the following commands
for the following postconditions?
1) {{ ? }} skip {{ X = 5 }}
2) {{ ? }} X := Y + Z {{ X = 5 }}
3) {{ ? }} X := Y {{ X = Y }}
4) {{ ? }}
if X = 0 then Y := Z + 1 else Y := W + 2 end
{{ Y = 5 }}
5) {{ ? }}
X := 5
{{ X = 0 }}
6) {{ ? }}
while (true) {X := 0}
{{ X = 0 }}
Source revision: e85fe77, committed 2026-10-06 21:16 UTC