Program Construction and Reasoning

Shin-Cheng Mu

2010 Formosan Summer School on , Language, and Computation June 28 – July 9, 2010

Part I • “Ok, I mean to ensure that a computer does what it is supposed to do.” Hoare Logic • “Doesn’t a computer always do what it is in- structed to do?” So, what is this course about?

• I am going to teach you how to write programs. 1.1 The Maximum Segment Sum Problem • But you program much more than I do. What about programming could I possibly teach you? Maximum Segment Sum

• Given a list of numbers, find the maximum sum 1 Introduction: On Programs of a consecutive segment. Correctness – [−1, 3, 3, −4, −1, 4, 2, −1] ⇒ 7 – [−1, 3, 1, −4, −1, 4, 2, −1] ⇒ 6 Programming Language Theory? It has always been, and still is, hard to talk to – [−1, 3, 1, −4, −1, 1, 2, −1] ⇒ 4 people about my research. • Not trivial. However, there is a linear time algo- • “It’s called ‘programming language’.” rithm. −1 3 1 −4 −1 1 2 −1 • “Like, making computers understand natural • 3 4 1 0 2 3 2 0 0 (up + right) ↑ 0 languages?” 4 4 3 3 3 3 2 0 0 up ↑ right • “Well, no... I mean the languages we use to com- municate to computers. We design better pro- A Simple Program Whose Proof is Not gramming language concepts to make program- • { | ≤ ≤ ≤ ming easier.” The specification: max sum (i, j) 0 i j N }, where sum (i, j) = a[i]+a[i+1]+...+a[i]. • “. . . surely it is the easiest to program in natural languages?” – What we want the program to do. • • “Err, no. In fact we are trying to make program- The program: ming more mathematical.” s = 0; m = 0; • “. . . and you call that an improvement?” for (i=0; i<=N; i++) { s = max(0, a[j]+s); Correctness? m = max(m, s); Or I could try to explain that our concern is about } “correctness.” – How to do it. • “And what does that mean?” • They do not look like each other at all! • “That a program meets its specification.” • Moral: programs that appear “simple” might • (totally confused) “A program meets . . . what?” not be that simple after all!

1 Programming, and Programming Languages 1.2 The Binary Search Challenge Can you Implement Binary Search? • Correctness: that the behaviour of a program is Given a sorted array of N numbers and a to allowed by the specification. search for, either locate the position where the key resides in the array, or report that the value does not • Semantics: defining “behaviours” of a program. present in the array, in O(log N) time. • Programming: to code up a correct program! • You would not expect it to be a hard program- ming task. • Thus the job of a programming language is to help the programmer to program, • Jon Bentley [Ben86, pp. 35-36], however, noted:

– either by making it easy to check that “I’ve assigned this problem in whether a program is correct, courses at Bell Labs and IBM. Pro- – or by ensuring that programmers may only fessional programmers had a couple of construct correct programs, that is, disal- hours to convert the above descrip- lowing the very construction of incorrect tion into a program in the language of programs! their choice; . . . 90% of the program- mers found bugs in their programs. . . . Knuth points out that while the Verification v.s. Derivation first binary search was published in 1946, the first published binary search • Verification: given a program, prove that it is without bugs did not appear until correct with respect to some specification. 1962.” • • Derivation: start from the specification, and at- Mike Taylor, owner of a popular blog tempt to construct only correct programs! The Reinvigorated Programmer, re- cently conducted this experiment again: Dijkstra: “to prove the correctness of a http://reprog.wordpress.com/2010/04/19/ given program, was in a sense putting the are-you-one-of-the-10-percent/ cart before the horse. A much more promis- ing approach turned out to be letting cor- Give It a Try? rectness proof and program grow hand in hand: with the choice of the structure of • Bentley: “The only way you’ll believe this is by the correctness proof one designs a program putting down this column right now and writing for which this proof is applicable.”[Dij74] the code yourself.”

“The only effective way to raise the con- • Given: an array a[0,N) of N elements, fidence level of a program significantly is • ∀ ≤ ≤ to give a convincing proof of its correct- that is sorted: ( i, j : 0 i < j < N : a[i] ness. But one should not first make the a[j]). program and then prove its correctness, be- • cause then the requirement of providing the Find i such that a[i] = K, or report that K is proof would only increase the poor program- not in the array. mer’s burden. On the contrary: the pro- grammer should let correctness proof and program grow hand in hand.” [Dij72] 2 Program Verification using Hoare Logic • What happened so far is that theoretical devel- opment of one side benefits the other. The In this course we will talk about program construc- • We focus on verification today, and talk about tion using Dijkstra’s calculus. Most of the materials derivation tomorrow. are from Kaldewaij [Kal90].

2 • A program computing the greatest common di- 2.1 Assignments visor: Substitution |[ con A, B : int {0 < A ∧ 0 < B} • P [E/x]: substituting free occurrences of x in P ; var x, y : int; for E. x, y := A, B; • We do so in mathematics all the time. A for- do y < x → x := x − y mal definition of substitution, however, is rather [] x < y → y := y − x tedious. od • {x = y = gcd(A, B)} For this lecture we will only appeal to “common ]|. sense”: – E.g. (x ≤ 3)[x − 1/x] ⇔ x−1 ≤ 3 ⇔ x ≤ 4. • Assignments denoted by :=; do denotes loops – ((∃y : y ∈ N : x < y) ∧ y < x)[y + 1/y] with guarded bodies. ⇔ (∃y : y ∈ N : x < y) ∧ y + 1 < x. • Assertions delimited in curly brackets. – (∃y : y ∈ N : x < y)[y/x] ⇔ (∃z : z ∈ N : y < z). The Hoare Triple • The notation [E/x] hints at “divide by x and multiply by E.” In the refinement calculus, sub- • The state space of a program is the states of all stitution is closely related to assignments, thus its variables. some also write [x := E]. – E.g. state space for the GCD program is (int × int). Substitution and Assignments • Which is correct: • The Hoare triple {P } S {Q}, operationally, de- notes that the statement S, when executed in a 1. {P } x := E {P [E/x]}, or state satisfying P , terminates in a state satisfy- 2. {P [E/x]} x := E {P }? ing Q. • Answer: 2! For example: • Perhaps the simplest statement: {P } skip {Q} { ≤ } { ≤ } iff. P ⇒ Q. (x 3)[x + 1/x] x := x + 1 x 3 ⇔ {x + 1 ≤ 3} x := x + 1 {x ≤ 3} { ∧ } { ≥ } – X > 0 Y > 0 skip X 0 . ⇔ {x ≤ 2} x := x + 1 {x ≤ 3}. – Note that the annotations need not be “ex- act.” 2.2 Sequencing Catenation The Hoare Triple • {P } S; T {Q} equivals that there exists R such • {P } S {true} expresses that S terminates. that {P } S {R} and {R} T {Q}. • Verify: • {P } S {Q} and P0 ⇒ P implies {P0} S {Q}. |[ var x, y : int; • {P } S {Q} and Q ⇒ Q0 implies {P } S {Q0}. {x = A ∧ y = B} • {P } S {Q} and {P } S {R} equivales {P } S {Q ∧ x := x − y; R}. {y = B ∧ x + y = A} • {P } S {Q} and {R} S {Q} equivales {P ∨ y := x + y; { − ∧ } R} S {Q}. y x = B y = A x := y − x; • More on these “healthiness” conditions of Hoare {x = B ∧ y = A} triples in the next lecture. ]|.

3 2.3 Selection • One takes effort exponential to n; the other is linear. If-Conditionals • Dijkstra: “. . . if we ever want to be able to com- • → → Selection takes the form if B0 S0 [] ... [] Bn pose really large programs reliably, we need a Sn fi. programming discipline such that the intellec- tual effort needed to understand a program does • Each B is called a guard; B → S is a guarded i i i not grow more rapidly than in proportion to the command. program length.” [Dijnd]

• If none of the guards B0 ...Bn evaluate to true, the program aborts. Otherwise, one of the 2.4 Loop and loop invariants command with a true guard is chosen non- deterministically and executed. Loops

• To annotate an if statement: • Repetition takes the form do B0 → S0 [] ... [] Bn → Sn od. {P } • if B0 → {P ∧ B0} S0 {Q} If none of the guards B0 ...Bn evaluate to true, [] B1 → {P ∧ B1} S1 {Q} the loop terminates. Otherwise one of the com- fi mands is chosen non-deterministically, before the {Q, Pf }, next iteration. • To annotate a loop (for partial correctness): where Pf : P ⇒ B0 ∨ B1. {P } Binary Maximum do B0 → {P ∧ B0} S0 {P } [] B → {P ∧ B } S {P } • Goal: to assign x ↑ y to z. By definition, z = 1 1 1 od x ↑ y ↔ (z = x ∨ z = y) ∧ x ≤ z ∧ y ≤ z. {Q, Pf }, • Try z := x. We reason: where Pf : P ∧ ¬B0 ∧ ¬B1 ⇒ Q. ∨ ∧ ≤ ∧ ≤ ((z = x z = y) x z y z)[x/z] • P is called the loop . Every loop should ⇔ (x = x ∨ x = y) ∧ x ≤ x ∧ y ≤ x be constructed with an invariant in mind! ⇔ y ≤ x, Linear-Time Exponentiation which hinted at using a guarded command: y ≤ x → z := x. |[ con N {0 ≤ N}; var x, n : int; • Indeed: x, n := 1, 0 {true} {x = 2n ∧ n ≤ N} if y ≤ x → {y ≤ x} z := x {z = x ↑ y} ; do n ≠ N → [] x ≤ y → {x ≤ y} z := y {z = x ↑ y} {x = 2n ∧ n ≤ N ∧ n ≠ N} fi x, n := x + x, n + 1 {z = x ↑ y}. {x = 2n ∧ n ≤ N, Pf1 } od On Understanding Programs {x = 2N , Pf2 } ]| • There are two ways to understand the program below: Pf1: → → if B00 S00 [] B01 S01 fi; (x = 2n ∧ n ≤ N)[x + x, n + 1/x, n] if B → S [] B → S fi; 10 10 11 11 n+1 : ⇔ x + x = 2 ∧ n + 1 ≤ N n if Bn0 → Sn0 [] Bn1 → Sn1 fi. ⇔ x = 2 ∧ n < N

4 Pf2: 1. B ∧ ¬B0 ∧ ¬B1 ⇒ Q, n ∧ ≤ ∧ ¬ ̸ x = 2 n N (n = N) 2. for all i, {P ∧ Bi} Si {P }, ⇒ x = 2N 3. P ∧ (B1 ∨ B2) ⇒ t ≥ 0,

4. for all i, {P ∧ Bi ∧ t = C} Si {t < C}. Greatest Common Divisor E.g. Linear-Time Exponentiation • Known: gcd(x, x) = x; gcd(x, y) = gcd(y, x − y) if x > y. • What is the bound function? • |[ con A, B : int {0 < A ∧ 0 < B}; |[ con N {0 ≤ N}; var x, n : int; var x, y : int; x, n := 1, 0 x, y := A, B {x = 2n ∧ n ≤ N, bnd : N − n} {0 < x ∧ 0 < y ∧ gcd(x, y) = gcd(A, B)} ; do n ≠ N → ; do y < x → x := x − y x, n := x + x, n + 1 [] x < y → y := y − x od od {x = 2N } {x = gcd(A, B) ∧ y = gcd(A, B)} ]| ]| • n ∧ ∧ ̸ ⇒ − ≥ • (0 < x ∧ 0 < y ∧ gcd(x, y) = gcd(A, B))[x − y/x] x = 2 n n = N N n 0, ↔ − ∧ ∧ − 0 < x y 0 < y gcd(x y, y) = gcd(A, B) • {...∧N −n = t} x, n := x+x, n−1 {N −n < t}. ⇐ 0 < x ∧ 0 < y ∧ gcd(x, y) = gcd(A, B) ∧ y < x

A Weird Equilibrium E.g. Greatest Common Divisor

• Consider the following program: • What is the bound function? | [ var x, y, z : int |[ con A, B : int {0 < A ∧ 0 < B}; var x, y : int; {true, bnd : 3 × (x ↑ y ↑ z) − (x + y + z)} → ; do x < y x := x + 1 x, y := A, B → [] y < z y := y + 1 {0 < x ∧ 0 < y ∧ gcd(x, y) = gcd(A, B), → [] z < x z := z + 1 bnd : |x − y|} od ; do y < x → x := x − y { } x = y = z [] x < y → y := y − x | ] . od {x = gcd(A, B) ∧ y = gcd(A, B)} • If it terminates at all, we do have x = y = z. ]| But why does it terminate?

1. bnd ≥ 0, and bnd = 0 implies none of the • ... ⇒ |x − y| ≥ 0, guards are true. • { ∧ ∧| − | } − {| − | 2. {x < y ∧ bnd = t} x := x + 1 {bnd < t}. ... 0 < y y < x x y = t x := x y x y < t}. Repetition To annotate a loop for total correctness: 2.5 Summary {P, bnd : t} We have the following rules for constructs of the do B0 → {P ∧ B0} S0 {P } guarded command language. [] B1 → {P ∧ B1} S1 {P } od • {P } skip {Q} ↔ [P ⇒ Q]. {Q}, • {P } x := E {Q} ↔ [P ⇒ Q[E/x]] and P im- we have got a list of things to prove: plies that E is defined.

5 • {P } S; T {Q} ↔ (∃R :: {P } S {R} ∧ 3.1 The van Gasteren-Feijen Ap- {R} T {Q}). proach

• {P } if B0 → S0 [] B1 → S1 fi {R} equivals The Program

1. [P ⇒ B0 ∨ B1] and {M < N ∧ Φ(M,N)} { ∧ } { } { ∧ } { } 2. P B0 S0 Q and P B1 S1 Q . l, r := M,N {M ≤ l < r ≤ N ∧ Φ(l, r), bnd : r − l} • {P } do B → S [] B → S od {Q} follows 0 0 1 1 ; do l + 1 ≠ r → from {... ∧ l + 2 ≤ r} m := (l + r)/2 1. [P ∧ ¬B0 ∧ ¬B1 ⇒ Q], {... ∧ l < m < r} { ∧ } { } { ∧ } { } 2. P B0 S0 P and P B1 S1 P , and ; if Φ(m, r) → l := m 3. there exists an integer function bnd on the [] Φ(l, m) → r := m state space such that fi od ∧ ∨ ⇒ ≥ (a) [P (B0 B1) bnd 0], {M ≤ l < N ∧ Φ(l, l + 1)} (b) {P ∧ B0 ∧ bnd = C} S0 {t < C}, and

(c) {P ∧ B1 ∧ bnd = C} S1 {t < C}. Proof of Correctness Statements of the guarded command language satisfy Let’s start with verifying the easier bits. the following rules: • When the loop exits: • {P } S {false} ↔ [P ↔ false], M ≤ l < r ≤ N ∧ Φ(l, r) ∧ ¬(l + 1 ≠ r) • {P } S {Q} ∧ [P0 ⇒ P ] ⇒ {P0} S {Q}, ⇒ M ≤ l < l + 1 ≤ N ∧ Φ(l, l + 1)

• {P } S {Q} ∧ [Q ⇒ Q0] ⇒ {P } S {Q0}, ⇔ M ≤ l < N ∧ Φ(l, l + 1). • { } { } ∧ { } { } ⇒ { } { ∧ } P S Q P S R P S Q R , • Termination: exercise. • {P } S {Q} ∧ {R} S {Q} ⇒ {P ∨ R} S {Q}. • To verify {. . . l + 2 ≤ r} m := (l + r)/2 {. . . l < m < r}:

3 Binary Search Revisited (l < m < r)[((l + r)/2)/m] ⇔ l < (l + r)/2 ⇐ l + 2 ≤ r. The van Gasteren-Feijen Approach

• Van Gasteren and Feijen [vGF95] pointed a sur- Proof of Correctness prising fact: binary search does not apply only • To verify that the loop body maintains the in- to sorted lists! variant, check the first branch in if: • In fact, they believe that comparing binary (M ≤ l < r ≤ N ∧ Φ(l, r))[m/l] search to searching for a word in a dictionary ⇔ ≤ ≤ ∧ is a major educational blunder. M m < r N Φ(m, r) ⇐ M ≤ l < r ≤ N ∧ Φ(l, r) ∧ • Their binary search: let Φ be a predicate on l < m < r ∧ Φ(m, r). (int × int), with some additional constraints to be given later: • Similarly with the other branch. | { ∧ } [ con M,N : int M < N Φ(M,N) ... ; • However, we still need to be sure that at least var l, r : int; one of the guards in if holds! Thus we need this bsearch property from Φ: {M ≤ l < N ∧ Φ(l, l + 1)} ]| Φ(l, r) ∧ l < m < r ⇒ Φ(l, m) ∨ Φ(m, r). (1)

6 Instantiations Discussions Some Φ that satisfies (1): • “Adding” elements to a? • Φ(i, j) = a[i] ≠ a[j] for some array a. Van – The invariant implies that −1 < m < N, Gasteren and Feijen suggested using this as the thus a[−1] and a[N] are never accessed. example when introducing binary search. – No actual alteration necessary. • Φ(i, j) = a[i] < a[j], – It also enables us to handle possibly empty arrays • Φ(i, j) = a[i] × a[j] ≤ 0, • Is the program different from the usual binary • Φ(i, j) = a[i] ∨ a[j], etc. search you’ve seen?

3.3 Searching with Premature Return 3.2 Searching in a Sorted List A More Common Program Searching for a Key Bentley’s program can be rephrased below:

• To search for a key K in an ascending-sorted l, r := 0,N − 1; found := false; array a, it seems that we could just pick: do l ≤ r → m := (l + r)/2; Φ(i, j) = a[i] ≤ K < a[j], if a[m] < K → l := m + 1 [] a[m] = K → found := true; break and check whether a[i] = K after the loop. [] K < a[m] → r := m − 1 fi • However, we are not sure we can establish the od. precondition a[l] ≤ K < a[r]! I’d like to derive it, but • For a possibly empty array a[0..N), imagine two • it is harder to formally deal with break elements a[−1] and a[N] such that a[−1] ≤ x and x < a[N] for all x. – but Bentley also employed a semi-formal reasoning using a to argue • Equivalently, pick: for the correctness of the program; • Φ(i, j) = (i = −1 ∨ a[i] ≤ K) ∧ (K < a[j] ∨ j = N). to relate the test a[m] < K to l := m+1 we have to bring in the fact that a is sorted earlier.

The Program Comparison

• The two programs do not solve exactly the same {0 ≥ N ∧ Φ(−1,N)} problem (e.g. when there are multiple Ks in a). l, r := −1,N {−1 ≤ l < r ≤ N ∧ Φ(l, r), bnd : r − l} • Is the second program quicker because it assigns ; do l + 1 ≠ r → l and r to m + 1 and m − 1 rather than m? {... ∧ l + 2 ≤ r} – l := m + 1 because a[m] is covered in an- m := (l + r)/2 other case; ; if a[m] ≤ K → l := m [] K < a[m] → r := m – r := m − 1 because a range is represented fi differently. od • Is it quicker to perform an extra test to return {−1 ≤ l < N ∧ Φ(l, l + 1)} early? ; if l > −1 → found := a[l] = k [] l = −1 → found := false – When K is not in a, the test is wasted. fi – Rolfe [Rol97] claimed that single compari- son is quicker in average.

7 – Knuth [Knu97, Exercise 23, Section 6.2.1]: Proof by reference to inaccessible literature single comparison needs 17.5 lg N + 17 a simple corollary of a theorem to be found in instructions, double comparison needs a privately circulated memoir of the Slovenian 18 lg N − 16 instructions. Philological Society, 1883.

Proof by personal communication ‘Eight- Exercise: Unimodel Search dimensional colored cycle stripping is NP- complete [Karp, personal communication] (in • Let array a[0,N), with 0 < N, be the concate- the elevator).’ nation of a strictly increasing and a strictly de- creasing array. Formally: Proof by appeal to intuition Cloud-shaped drawings. (∃k : 0 ≤ k < N : ∀ ≤ − ∧ ( i : 0 < i k : a[i 1] < a[i]) A semantic proof (∀j : k ≤ j < N : a[j − 1] > a[j])). A map of London is place on the ground of Trafal- gar Square. There is a point on the map that is di- Use binary search to find the maximum element. rectly above the point on the ground that it repre- sents. [Bac03, Figure 3.2] • What invariant to use? Proof. The map is directly above a part of London. Thus the entire map is directly above the part of the area which Part II it represents. Now, the smaller area of the map repre- senting Central London is also above the part of the area Program Derivation which it represents. Within the area representing Central London, Trafalgar Square is marked, and this yet smaller Program Derivation part of the map is directly above the part it represents. Continuing this way, we can find smaller and smaller ar- • Wikipedia: program derivation is the derivation eas of the map each of which is directly above the part of of a program from its specification, by mathe- the area which it represents. In the limit we reduce the matical means. area on the map to a single point. • To write a formal specification (which could be non-executable), and then apply mathematically Proof of Pythagoras’s Theorem I B J correct rules in order to obtain an executable program.

• The program thus obtained is correct by con- a c E struction.

C A 4 What is a Proof, Anyway? b

But What is a Proof, Anyway? Xavier Leroy, “How to prove it” http://cristal. L D K Let ABC be a trian- \ o inria.fr/~xleroy/stuff/how-to-prove-it. gle with BAC = 90 . Let the lengths of BC, AC, html: AB be, respectively, a, b, and c. We wish to prove that a2 = b2 + c2. Construct a square IJKL, of Proof by example Prove the case n = 2 and sug- side b + c, and a square BCDE, of side a. Clearly, gests that it contains most of the ideas of the area(IJKL) = (b + c)2. But general proof. area(IJKL) = area(BCDE)+ Proof by intimidation ‘Trivial’. 4 × area(ABC) Proof by cumbersome notation Best done with = a2 + abc. access to at least four alphabets and special sym- bols. That is, (b + c)2 = a2 + 2bc, whence b2 + c2 = a2.

8 Informal v.s. Formal Proofs Another Formal Proof The calculational logic proofs we have seen were • To read an informal proof, we are expected to formal: have a good understanding of the problem do- main, the meaning of the natural language state- ¬(P ↔ Q) ments, and the language of mathematics. ⇔ { unfolding ¬ } • A formal proof shifts some of the burdens to the (P ↔ Q) ↔ ⊥ “form”: the symbols, the syntax, and rules ma- ⇔ { ↔ associative } nipulating them. “Let the symbols do the work!” P ↔ (Q ↔ ⊥) • Our proof of the swapping program is formal: ⇔ { folding ¬ } {x = A ∧ y = B} P ↔ ¬Q. x := x − y; y := x + y; x := y − x Rather than relying on intuition on truth tables, we {x = B ∧ y = A}. try to develop intuition on calculational rules. Tsuru-Kame Zan The Tsuru-Kame Problem 4.1 Quantifier manipulation Some cranes (tsuru) and tortoises (kame) are mixed Quantifications in a cage. Known is that there are 5 heads and 14 legs. Find out the numbers of cranes and tortoises. • Let ⊕ be a commutative, associative operator with identity e, that is, • The kindergarten approach: plain simple enu- meration! – x ⊕ y = y ⊕ x, – Crane 0, Tortoise 5 . . . No. – x ⊕ (y ⊕ z) = (x ⊕ y) ⊕ z, and – Crane 1, Tortoise 4 . . . No. – e ⊕ x = x = x ⊕ e, – Crane 2, Tortoise 3 . . . No. and let f be a function defined on int. – Crane 3, Tortoise 2 . . . Yes! • We denote f m ⊕ f (m + 1) ⊕ ... ⊕ f (n − 1) by – Crane 4, Tortoise 1 . . . No. (⊕i : m ≤ i < n : f i). – Crane 5, Tortoise 0 . . . No. • (⊕i : n≤i

9 Examples 5.1 Taking Conjuncts as Invariants

• E.g. Conjunctive Postconditions • ∧ – (+i : 3≤i<5 : i2) = 32 + 42 = 25. When the post condition has the form P Q, one may take one of the conjuncts as the invariant ≤ ≤ × × × – (+i, j : 3 i j<5 : i j) = 3 3 + 3 4 + and the other as the guard: 4 × 4. { } ¬ → { ∧ } – (∧i : 2≤i<9 : odd i ⇒ prime i) = true. – P do Q S od P Q . – (↑ i : 1≤i<7 : −i2 + 5i) = 6 (when i = 2 or • E.g. consider the specficication: 3). |[ con A, B : int; {0 ≤ A ∧ 0 ≤ B} • As a convention, (+i : R : F ) is written (Σi : var q, r : int; R : F ), (∧i : R : F ) is written (∀i : R : F ), and divmod (∨i : R : F ) is written (∃i : R : F ). {q = A div B ∧ r = A mod B} ]|. • A special rule for ↑ (or ↓) and +: • The post condition expands to R :: A = q × B + ↑ ↑ x + ( i : R : F i) = ( i : R : x + F i). r ∧ 0 ≤ r ∧ r < B.

The Number Of . . . But Which Conjunct to Choose?

• Define # : Bool → {0, 1}: • q = A div B ∧ r = A mod B expands to R : A = q × B + r ∧ 0 ≤ r ∧ r < B, which # false = 0 leads to a number of possibilities: # true = 1. • {0 ≤ r ∧ r < B} do A ≠ q ×B +r → S od {R}, • “The number of” quantifier is defined by: • {A = q ×B +r ∧ r < B} do 0 > r → S od {R}, (#i : R i : F i) = (Σi : R i : #(F i)), or • {A = q ×B +r ∧ 0 ≤ r} do r ≥ B → S od {R}, from which we may derive: etc. – (#i : false : F i) = 0, Computing the Quotient and the Remainder – (#i : 0 ≤ i < n + 1 : F i) = (#i : 0 ≤ i < Try A = q × B + r ∧ 0 ≤ r as the invariant and n : F i) + #(F n). ¬(r < B) as the guard:

q, r := 0,A; 5 Loop construction {P : A = q × B + r ∧ 0 ≤ r} ≤ → Deriving Programs from Specifications do B r {P ∧ B ≤ r} • From such a specification: q, r := q + 1, r − B {P } |[ con declarations; od {preconditions} {P ∧ r < B} prog {postcondition} • ]| P is established by q, r := 0,A. • Choose r as the bound. we hope to derive prog. • Since B > 0, try r := r − B: • We usually work backwards from the post con- dition. P [r − B/r] ⇔ × − ∧ ≤ − • The techniques we are about to learn is mostly A = q B + r B 0 r B about constructing loops and loop invariants. ⇔ A = (q − 1) × B + r ∧ B ≤ r.

10 Hmm... we almost have P ∧ B ≤ r, apart from Constructing Loop Body in Steps that q is replaced by q − 1. We will see this pattern often: • ⇔ • P [q + 1, r − B/q, r] we have discovered that (r = e)[x + 1/x] r = e ⊕ e′. ⇔ A = (q + 1) × B + r − B ∧ 0 ≤ r − B ⇔ A = q × B + r ∧ B ≤ r. • We want to establish: {r = e ∧ ... } 5.2 Replacing Constants by Variables r := r ⊕ e′ {r = e ⊕ e′} Exponentiation ; x := x + 1 {r = e}. • Consider the problem: • |[ con A, B : int {A ≥ 0 ∧ B ≥ 0}; It works because: var r : int; (r = e ⊕ e′)[r ⊕ e′/r] exponentiation ⇔ ⊕ ′ ⊕ ′ {r = AB} r e = e e ]|. ⇐ r = e.

• There is not much we can do with a state space Summing Up an Array consisting of only one variable. • Another simple exercise. • Replacing constants by variables may yield some • We talk about it because we need range splitting. possible invariants.

• Again we have several choices: r = xB, r = Ax, r = xy, etc. |[ con N : int {0 ≤ N}; f : array [0..N) of int; var x : int Exponentiation sum {x = (Σi : 0≤i

• (r = Ax)[x + 1/x] ⇔ r = Ax+1. However, when |[ con N : int {0 ≤ N}; f : array [0..N) of int; r = Ax holds, Ax+1 = A × Ax = A × r! n, x := 0, 0 • Indeed, {P : x = (Σi : 0≤i

11 • Strengthening by Using More Variables New plan: (x = (Σi : 0≤i

|[ con N : int{N ≥ 0}; a : array [0..N) of int; P0 : r = (#i, j : 0 ≤ i < j < n : a[i] ≤ 0 ∧ a[j] ≥ 0),

var r : int; P1 : 0 ≤ n ≤ N, S Q : s = (#i : 0 ≤ i < n : a[i] ≤ 0). {r = (#i, j : 0 ≤ i < j < N : a[i] ≤ 0 ∧ a[j] ≥ 0)} ]|.

Update the New Variable • Replace N by n:

≤ P0 : r = (#i, j : 0 i < j < n : (#i : 0 ≤ i < n : a[i] ≤ 0)[n + 1/n] ≤ ∧ ≥ a[i] 0 a[j] 0), = (#i : 0 ≤ i < n + 1 : a[i] ≤ 0) ≤ ≤ P1 : 0 n N. = { split off i = n (assuming 0 ≤ n) } ≤ ≤ ≤ • Initialisation: n, r := 0, 0. (#i : 0 i < n : a[i] 0) + #(a[i] 0) = { Q } |[ con N : int {N ≥ 0}; a : array [0..N) of int; s + #(a[i] ≤ 0) var r : int; { s if a[i] > 0, = n, r := 0, 0 s + 1 if a[i] ≤ 0. {P0 ∧ P1, bnd : N − n} ; do n ≠ N → . . . n := n + 1 od {r = (#i, j : 0 ≤ i < j < N : a[i] ≤ 0 ∧ a[j] ≥ 0)} Resulting Program ]|. |[ ... {N ≥ 0} n, r, s := 0, 0, 0 No. of Pairs in an Array {P0 ∧ P1 ∧ Q, bnd : N − n} To reason about P0[n + 1/n], we calculate (assum- ; do n ≠ N → {P0 ∧ P1 ∧ Q ∧ n ≠ N} ing P0, P1 : 0 ≤ n ≤ N and n ≠ N): if a[n] < 0 → skip [] a[n] ≥ 0 → r := r + s (#i, j : 0 ≤ i < j < n + 1 : a[i] ≤ 0 ∧ a[j] ≥ 0) fi = { split off j = n } {P0[n + 1/n] ∧ P1 ∧ Q ∧ n ≠ N} → (#i, j : 0 ≤ i < j < n : a[i] ≤ 0 ∧ a[j] ≥ 0)+ ; if a[n] > 0 skip [] a[n] ≤ 0 → s := s + 1 (#i : 0 ≤ i < n : a[i] ≤ 0 ∧ a[n] ≥ 0) fi = { P } 0 {(P0 ∧ P1 ∧ Q)[n + 1/n]} r + (#i : 0 ≤ i < n : a[i] ≤ 0 ∧ a[n] ≥ 0) ; n := n + 1 { od r, if a[n] < 0; = {r = (#i, j : 0 ≤ i < j < N : a[i] ≤ 0 ∧ a[j] ≥ 0)} r + (#i : 0 ≤ i < n : a[i] ≤ 0), if a[n] ≥ 0. ]|.

We could compute (#i : 0 ≤ i < n : a[i] ≤ 0) in a Since P0 ∧P1 ∧Q∧n ≠ N is a common precondition loop. . . or can we store it in another variable? for the if’s (the second if does not use P0), they can

12 be combined: Fibonacci Recall: fib 0 = 0, fib 1 = 1, and fib (n + 2) = |[ ... {N ≥ 0} fib n + fib (n + 1). n, r, s := 0, 0, 0 {P0 ∧ P1 ∧ Q, bnd : N − n} |[ con N : int {0 ≤ N}; var x, y : int; ; do n ≠ N → {P0 ∧ P1 ∧ Q ∧ n ≠ N} if a[n] < 0 → s := s + 1 n, x, y := 0, 0, 1 [] a[n] = 0 → r, s := r + s, s + 1 {P : x = fib n ∧ 0 ≤ n ≤ N ∧ y = fib (n + 1)} [] a[n] > 0 → r := r + s ; do n ≠ N → {P ∧ n ≠ N} fi x, y := y, x + y; n := n + 1 {P } {(P0 ∧ P1 ∧ Q)[n + 1/n]} od ; n := n + 1 {x = fib N} od ]|. {r = (#i, j : 0 ≤ i < j < N : a[i] ≤ 0 ∧ a[j] ≥ 0)} ]|. • Inv. is established by n, x := 0, 0. • (x = fib n ∧ 0≤n≤N ∧ y = fib (n+1))[n+1/n] It’s Easier to Do More? ⇔ x = fib (n+1) ∧ 0≤n

• The resulting loop computes values for two vari- • (x = fib (n+1) ∧ ... ∧ y = fib (n+2)) ables rather than one. It appears that it does [y, x + y/x, y] more work. ⇔ y = fib (n+1) ∧ ... ∧ x + y = fib (n+2) • However, we often find that a loop that does ⇐ x = fib n ∧ ... ∧ y = fib (n+1). more is eaiser to construct, because more has been established in the previous iteration of the 5.4 Tail Invariants loop. Tail Recursion • The invariant is “stronger” because it promises • more. A function f is tail recursive if it looks like: • It is a common phenomena: a generalised theo- f x = h x, if b x; ¬ rem is easier to prove. f x = f (g x), if (b x).

• We will see another way to generalise the invari- • The goal is to derive a program that computes ant in the next section. f X for given X. Plan:

|[ con X; var r, x; Isn’t It Getting A Bit Too Complicated?

• Quantifier and indexes manipulation tend to get x := X very long and tedious. {f x = f X} ; do ¬(b x) → x := g x od – Expect to see even longer expressions later! ; r := h x {r = f X} • With long and complex expressions, one tend to ]|, make mistakes. provided that the loop terminates. • To certain extent, it is a restriction of the data structure we are using. With arrays we have to manipulate the indexes. Using Associativity • Is it possible to use higher-level data structures? • Consider function k such that: Lists? Trees? k x = a, if b x; ⊕ ¬ – Like map, filter, foldr. . . in functional pro- k x = h x k (g x), if (b x). gramming? where ⊕ is associative with identity e. Note that – More on this issue later. k is not tail recursive.

13 • Goal: establish r = k X for given X. Fast Exponentiation

• Trick: use an invariant r ⊕ k x = k X. • To achieve r = AB, choose invariant r × xy = AB: – ‘computed’ ⊕ ‘to be computed’ = k X. – Strategy: keep shifting stuffs from right • To construct the loop body, we reason for the hand side of ⊕ to the left, until the right case even y: is e. r × xy Constructing the Loop Body = { assumption: even y } If b x holds: r × (x × x)ydiv2 × y × r ⊕ k x = k X = (r x )[x x, y div 2/x, y]. ⇔ { b x } and for odd y: r ⊕ a = k X. r × xy Otherwise: = { assumption: odd y } − r ⊕ k x = k X r × (x × xy 1) ⇔ { ¬(b x) } = { × associative } r ⊕ (h x ⊕ k (g x)) = k X (r × x) × xy−1 ⇔ { ⊕ associative } = (r × xy)[r × x, y − 1/r, y]. (r ⊕ h x) ⊕ k (g x) = k X ⇔ (r ⊕ k x = k X)[r ⊕ h x, g x/r, x]. Fast Exponentiation The resulting program:

r, x, y := 1, A, B; {r × xy = AB ∧ 0 ≤ y, bnd = y} The Program do y ≠ 0 ∧ even y → x, y := x × x, y div 2 [] y ≠ 0 ∧ odd y → r, y := r × x, y − 1 |[ con X; var r, x; od {r × xy = AB ∧ y = 0}. r, x := e, X {r ⊕ k x = k X} ; do ¬(b x) → r, x := r ⊕ h x, g x od {r ⊕ a = k X} 6 Max. Segment Sum Solved ; r := r ⊕ a {r = k X} Specification ]|, | { ≤ } if the loop terminates. [ con N : int 0 N ; f : array [0..N) of int; var r, n : int; Exponentation Again n, r := 0, 0 { ↑ ≤ ≤ ≤ ∧ ≤ ≤ } • Consider again computing AB. Notice that: r = ( p, q : 0 p q n : sum p q) 0 n N ; do n ≠ N → x0 = 1 ... ; n := n + 1 xy = 1 × (x × x)y div 2 if even y, od = x × xy−1 if odd y. {r = (↑ p, q : 0≤p≤q≤N : sum p q)} ]| • How does it fit the pattern above? (Hint: k now has type (int × int) → int.) • sum p q = (Σi : p≤i

14 Strengthening the Invariant Derived Program

• Let P0 : r = (↑ p, q : 0 ≤ p ≤ q ≤ n : sum p q). |[ con N : int {0 ≤ N}; f : array [0..N) of int; n, r, s := 0, 0, 0; var r, s, n : int; {P0 ∧ 0 ≤ n ≤ N ∧ s = (↑ p : 0 ≤ p ≤ n : sum p n)} do n ≠ N− > n, r, s := 0, 0, 0 ... ; n := n + 1 {P0 ∧ P1 ∧ 0 ≤ n ≤ N, bnd : N − n} od ; do n ≠ N → {r = (↑ p, q : 0 ≤ p ≤ q ≤ N : sum p q)} s := (s + f[n]) ↑ 0; r := r ↑ s; • With assumption that 0 ≤ n + 1 ≤ N: n := n + 1 (↑ p, q : 0 ≤ p ≤ q ≤ n : sum p q)[n + 1/n] od { ↑ ≤ ≤ ≤ } = (↑ p, q : 0 ≤ p ≤ q ≤ n + 1 : sum p q) r = ( 0 p q N : sum p q :) | = (↑ p, q : 0 ≤ p ≤ q ≤ n : sum p q) ↑ ] ↑ ≤ ≤ ( p, q : 0 p n+1 : sum p (n+1)). • P0 : r = (↑ p, q : 0 ≤ p ≤ q ≤ n : sum p q). • P : s = (↑ p, q : 0 ≤ p ≤ n : sum p n). • Let’s introduce P1 : s = (↑ p : 0 ≤ p ≤ n : 1 sum p n). 7 Where to Go from Here? Constructing the Loop Body What Have We Learnt? • Known: P : r = (↑ p, q : 0 ≤ p ≤ q ≤ n : 0 How to program! sum p q), • Imperative program derivation by backwards • ↑ ≤ ≤ P1 : s = ( p : 0 p n : sum p n), reasoning. • ↑ ≤ ≤ ≤ P0[n + 1/n]: r = ( p, q : 0 p q n : • Key to imperative program derivation: every ↑ ↑ ≤ ≤ sum p q) ( p : 0 p n+1 : sum p (n+1)). loop shall be built with an invariant and a bound • Therefore, a possible strategy would be: in mind. • Some techniques to construct loop invariants: {P0 ∧ P1 ∧ 0 ≤ n ≤ N ∧ n ≠ N} s := ?; – taking conjuncts as invariants; {P ∧ P [n + 1/n] ∧ 0 ≤ n ≤ N ∧ n ≠ N} 0 1 – replacing constants by variables; r := r ↑ s; – strengthening the invariant; {(P0 ∧ P1 ∧ 0 ≤ n ≤ N)[n + 1/n]} n := n + 1 – tail invariants. {P0 ∧ P1 ∧ 0 ≤ n ≤ N} What Have We Learnt? Updating the Prefix Sum And some more philosophical issues. Recall P ≡ s = (↑ p : 0 ≤ p ≤ n : sum p n). 1 • What being formal means, and how it helps us. (↑ p : 0 ≤ p ≤ n : sum p n)[n + 1/n] • To program is to construct code that meets the = (↑ p : 0 ≤ p ≤ n+1 : sum p (n+1)) specification; = { splitting p = n + 1 } • and to do so, the program must be constructed (↑ p : 0 ≤ p ≤ n : sum p (n+1)) ↑ together with its proof. sum (n+1) (n+1) Connection with other courses? = { [n + 1, n + 1) is an empty range } • What we have learnt is . (↑ p : 0 ≤ p ≤ n : sum p (n+1)) ↑ 0 • = (↑ p : 0 ≤ p ≤ n : sum p n + f[n]) ↑ 0 We have not talked about Dijkstra’s weakest precondition semantics, in which a program is ↑ ≤ ≤ ↑ = (( p : 0 p n : sum p n) + f[n]) 0. seen as a predicate transformer – a function

Thus, {P1} s :=? {P1[n + 1/n]} is satisfied by s := from predicates to predicates. See Dijkstra and (s + f[n]) ↑ 0. Scholten [DS90].

15 What’s Missing? Separation Logic

• One reason making this calculus rather tedious: • Another way out is separation logic: a logic complex manipulation of quantifiers and array about heap and stores. indexes. • Advocated by John C. Reynolds [Rey02]. • To certain extent it is the limitation of data structure we are using. To manipulate arrays, • Facilitates reasoning about pointers and sharing. we tend to perform plenty of operations using indexes. • Separation between concurrent modules. • Could we use “higher-level” data structures to avoid these messy details? Where to Go from Here?

Purity and Aliasing • Early issues of Science of Computer Program- ming have regular columns for program deriva- • Side-effects strictly forbidden in expressions. tion.

– {P [E/x]} x := E {P } fails if E has side ef- • Books and papers by Dijkstra, Gries, Back, fects, Backhouse, etc. – which is why some programming languages • have a clear separation of expressions and You might not actually derive programs, but statements. knowledge learnt here can be applied to program verification. • That means aliasing could cause disasters, – Plenty of tools around for program verifica- • which in turn makes call-by-reference dangerous. tion basing on pre/post-conditions. Some of them will be taught in this summer – Extra care must be taken when we intro- school. duce subroutines, – which is why procedure calls were such a • You might never derive any more programs for big issue. the rest of your life. But the next time you need a loop, you will know better how to construct it • If your interests are in program derivation, you and why it works. could dismiss these problematic features. If you work on verification, however, you have to cope Other Historical References with them. We may see that in Frama-C. References to some of the historical materials that are mentioned in the supplementary part of this lec- Functional Program Derivation ture: In contrast, much of functional program derivation is essentially built on a theory of data structure. • Correspondence between Maarten van Em- den and Dijkstra is recorded at http: max ◦ map sum ◦ segments //vanemden.wordpress.com/2008/05/06/ = max ◦ map sum ◦ concat ◦ map inits ◦ tails i-remember-edsger-dijkstra-1930-2002/. = { map f ◦ concat = concat ◦ map (map f) } • Details of the first ALGOL compiler is docu- max ◦ concat ◦ map (map sum) ◦ map inits ◦ tails mented by Kruseman Aretz [KA03]. = { since max ◦ concat = max ◦ map max } : • History and analysis of ALGOL by de Beer = max ◦ scanr zmax 0. [dB06].

For an introduction, check out lectures in FLOLAC • Controversy around Go To [Dij68, Knu74, + ’07 and ’08! Rub87, MML 87, Dij87].

16 References [MML+87] Donald Moore, Chuck Musciano, Michael J. Liebhaber, Steven F. Lott, [Bac03] Roland C. Backhouse. Program Con- and Lee Starr. “‘GOTO considered struction: Calculating Implementations harmful’ considered harmful” considered from Specifications. John Wiley & Sons, harmful? Communications of the ACM, Ltd., 2003. 30(5):351–355, May 1987. [Ben86] Jon Bentley. Programming Pearls. [Rey02] John C. Reynolds. Separation logic: a Addison-Wesley, 1986. logic for shared mutable data structures. In Gordon Plotkin, editor, Annual IEEE [dB06] Heer de Beer. The history of the AL- Symposium on Logic in Computer Sci- GOL effort. Master’s thesis, Eindhoven ence, pages 55–74. IEEE Computer So- University of Technology, August 2006. ciety Press, 2002. [Dij68] Edsger W. Dijkstra. Go To statement [Rol97] Timothy J. Rolfe. Analytic derivation of considered harmful. Communications of comparisons in binary search. SIGNUM the ACM, 11(3):147–148, March 1968. Newsletter, 32(4):15–19, October 1997. [Dij72] Edsger W. Dijkstra. The humble pro- [Rub87] Frank Rubin. ‘GOTO considered harm- grammer. Communications of the ACM, ful’ considered harmful. Communications 15(10):859–866, 1972. EWD 340, Turing of the ACM, 30(3):195–196, March 1987. Award lecture. [vGF95] Netty van Gasteren and Wim H. J. [Dij74] Edsger W. Dijkstra. Programming as a Feijen. The binary search revisited. discipline of mathematical nature. Amer- AvG127/WF214, November 1995. ican Mathematical Monthly, 81(6):608– 612, May 1974. EWD 361. [Dij87] Edsger W. Dijkstra. On a somewhat dis- appointing correspondence. EWD 1009, circulated privately, May 1987. [Dijnd] Edsger W. Dijkstra. On understand- ing programs. EWD 264, circulated pri- vately, n.d. [DS90] Edsger W. Dijkstra and Carel S. Scholten. Predicate Calculus and Pro- gram Semantics. Springer-Verlag, 1990. [KA03] Frans E. J. Kruseman Aretz. The Dijkstra-Zonneveld ALGOL 60 compiler for the Electrologica X1. Technical Report SEN N 0301, Centrum voor Wiskunde en Informatica, 2003. [Kal90] Anne Kaldewaij. Programming: the Derivation of Algorithms. Prentice Hall, 1990. [Knu74] Donald E. Knuth. Structured program- ming with go to statements. ACM Com- puting Surveys, 6(4):261–301, 1974. [Knu97] Donald E. Knuth. The Art of Com- puter Programming Volume 3: Sorting and Searching, 3rd Edition. Addison Wesley, 1997.

17