Java - Recursion (Full Notes)
Java - Recursion (Full Notes)
1.1 Recursion
Recursion Recursion
Slide 1
Recursion
1
Recursion Recursion
We can deal with recursion using the techniques we have already seen for reasoning about
method calls.
The “trick” is that when proving that a method’s body satisfies its specification, we can
assume that the method specifications hold for any methods that are called within the
body, including any recursive calls.
This allows us to tackle our proofs in a modular way.
As a running example we shall use a recursive method that returns the sum of all elements
in an array.
2
Recursion Recursion
Recursion - Example
1 int sum(int[] a)
2 // PRE: a 6= null P (P1 )
3 // POST: a[..) ≈ a[..)pre ∧ r = a[..) (Q1 )
4 {
Slide 3
Note that we can derive the mid-condition M1 without any knowledge of the specification
of sumAux by working backwards from the post-condition Q1 of sum. The only way that
thePreturn statement can satisfy the post-condition is if res has been set up to be equal
to a[..).
3
Recursion Recursion
Recursion Recursion
4
Recursion Recursion
P1 −→ P2 [i 7→ 0]
We discuss the development of each of these explicit proof obligations in more detail
below, paying particular attention to the substitutions and implicit assumptions we need
to make in each case to reflect the effects of the code.
5
Recursion Recursion
a 6= null
−→
a 6= null ∧ 0 ≤ 0 ≤ a.length
There is no reference to the array contents in P1 or P2 , nor has any code run before line
5, so all we need to substitute here are the call values for sumAux on line 5.
6
Recursion Recursion
P
a 6= null ∧ a[..) ≈ a[..)pre ∧ r = a[0..) ∧ res = r
−→ P
a[..) ≈ a[..)pre ∧ res = a[..)
In general, when we make a method call that could potentially make updates to an array,
we need to carefully distinguish the contents of the array passed into the method (a[..)old )
from the initial array contents (a[..)pre ) and the current array contents (a[..)).
However, in this case no code has actually run before line 5, so we know that the array
contents are not modified before the call to sumAux. Thus we know that a[..)pre passed
into sumAux will be the same as a[..)pre passed into sum.
7
Recursion Recursion
P
a[..) ≈ a[..)pre ∧ res = a[..) ∧ r = res
−→ P
a[..) ≈ a[..)pre ∧ r = a[..)
The final proof obligation for sum is for a simple return statement. We have already seen
how to deal with such a case.
Recursion Recursion
Recursion - Example
5 if (i == a.length) {
6 // MID: a[..) ≈ a[..)pre ∧ i = a.length (M2 )
7 return 0;
8 } else {
9 // MID: a[..) ≈ a[..)pre ∧ a 6= null ∧ 0 ≤ i < a.length
(M3 )
10 int val = a[i] + sumAux(a, i+1); P
11 // MID: a[..) ≈ a[..)pre ∧ val = a[i..) (M4 )
12 return val;
13 }
14 }
8
Recursion Recursion
Recursion Recursion
9
Recursion Recursion
(M2 ∧ r = 0) −→ Q2
line 9: The pre-condition of sumAux must establish the mid-condition M3 .
(P2 ∧ a[..) ≈ a[..)pre ∧ i 6= a.length) −→ M3
line 10: The mid-condition M3 must establish the pre-condition of sumAux.
M3 −→ P2 [i 7→ i+1]
line 11: The post-condition of sumAux must establish the mid-condition M4 .
(M3 [a[..) 7→ a[..)old ] ∧ Q2 [a[..)pre 7→ a[..)old , i 7→ i+1] ∧ val = a[i]old + r) −→ M4
line 12: The mid-condition M4 must establish the post-condition of sumAux.
(M4 ∧ r = val) −→ Q2
We discuss the development of each of these explicit proof obligations in more detail
below, paying particular attention to the cases involved in the recursive call to sumAux.
10
Recursion Recursion
As before, there is no reference to the array contents in P2 , nor has any code modified
the array or its contents before line 6.
11
Recursion Recursion
There are a two interesting points to note about the proof obligations above.
Firstly, since i = a.length we can infer from this that a 6= null. This is because
a.length returns a value which is comparable to i, so there cannot have been a null
pointer dereference.
Secondly,
P proving that r has the desired return value relies on recalling from the definition
of that:
X
∀k.[ a[k..k) = 0 ]
That is, the sum of any empty range is always equal to 0 (the identity element of addition).
12
Recursion Recursion
Recursion Recursion
13
Clearly we could also explicitly denote the substitution on P2 of a 7→ a.
However, recall that we choose to omit unnecessary identity substitutions to ease the
notational burden of our proof obligations.
Recursion Recursion
It is important that the premise of our proof obligation includes the fact that
0 ≤ i < a.length
so that we know the array dereference of a[i] in the code is valid (does not result in an
ArrayOutOfBounds error).
Also note that if line 10 of our program were modified to:
int val = sumAux(a, i+1) + a[i]
then the proof obligation above would have to be slightly modified to track the new order
of execution. In particular, the effect of the code would now be described as:
val = r + a[i]
In this case, when we add the ith element of the array to the sum, we do so from the
array that was modified by the call to sumAux. Of course, our specification for sumAux
actually guarantees that the array is unmodified, so in this case the ordering makes no
difference.
14
Recursion Recursion
P
a[..) ≈ a[..)pre ∧ val = a[i..) ∧ r = val
−→ P
a[..) ≈ a[..)pre ∧ r = a[i..)
A lot of what we have covered in this section may feel quite mechanical and it is correct
to have this feeling. Everything we have done so far could be automated. In fact, there
are a number of proof assistant tools that can do just this.
However, when we reason about iterative programs in the next part of the course, we will
see that this requires more intuition, and so this is much harder to automate.
15
Recursion Recursion
2 // PRE: a 6= null P (P )
3 // POST: a[..) ≈ a[..)pre ∧ r = a[..) (Q)
4 {
5 return sillySum(a);
6 }
...so what about Termination?
Note that the sillySum method would actually satisfy its post-condition if it were to
terminate, so the method is partially correct.
However, termination of sillySum is clearly not guaranteed (or in this case even possible),
so the method is definitely not totally correct.
16
Recursion Recursion
The length of the array that remains to be processed (a.length − i) decreases with each
recursive call to sumAux. We can use this “Measure of Progress” to reason about the
termination of the sumAux method.
17
Recursion Recursion
a.length − i
We prove termination of the sumAux method via mathematical induction on the length
of the remaining array to be processed, i.e. a.length − i.
Base case:
To show:
∀a ∈ int[], i ∈ Z.
[ 0 ≤ i ≤ a.length −→ sumAux(a,i) terminates when a.length − i = 0 ]
Proof:
Take arbitrary a ∈ int[] and i ∈ Z where 0 ≤ i ≤ a.length.
a.length − i = 0 −→ i = a.length
When i = a.length, SumAux enters the first branch of the conditional and termi-
nates on line 7.
18
Inductive Step:
Take arbitrary k ∈ N.
Inductive Hypothesis:
(IH): ∀a ∈ int[], i0 ∈ Z.
[ 0 ≤ i0 ≤ a.length −→ sumAux(a,i0 ) terminates when a.length − i0 = k ]
To show:
∀a ∈ int[], i ∈ Z.
[ 0 ≤ i ≤ a.length −→ sumAux(a,i) terminates when a.length − i = k + 1 ]
Proof:
Take arbitrary a ∈ int[], i ∈ Z where 0 ≤ i ≤ a.length.
a.length − i = k + 1 −→ i = a.length − k − 1
i 6= a.length (since k ∈ N) so sumAux enters the second branch of the conditional.
a.length − i = k + 1 −→ i + 1 = a.length − k
and i + 1 > 0, by choice of i, so applying (IH) (with i0 as i + 1) we know that
sumAux(a,i+1) will terminate.
So, SumAux will then terminate on line 12.
Recursion Recursion
19
Recursion Recursion
Recursion Recursion
20