Note: We can use integer expressions, as long as we show that they are non-negative before entering the loop body.
while 0 < i
invariant 0 <= i
decreases i
{
i := i - 1;
}
Exercise: Prove termination of the following loop:
method m1(m: int, n: int)
requires 0 < m && 0 < n
{
var x, y := m, n;
while x != y
invariant 0 < x && 0 < y
{
if x < y {
y := y - x;
} else {
x := x - y;
}
}
}
Exercise: Termination annotations might be necessary when we have recursive methods or functions. Prove that the following function terminates:
function f(n: nat, m: nat): nat
{
if n==m then n
else if n<m then f(n, m-n) else f(n-m, m)
}
array<T>
, where
T
is another type.
a.Length
Exercise: Specify the first requirement of
Find
: if the algorithm returns an index (i.e.
non-negative integer), then the key should be
present at that index.
Use the following signature for your method:
method Find(a: array<int>, key: int) returns (index: int)
Exercise: Implement Find
in a way that satisfies your specification.
method Find(a: array<int>, key: int) returns (index: int)
ensures 0 <= index ==> index < a.Length && a[index] == key
{
// ...
}
forall k :: k < k + 1
forall k :: 0 <= k < a.length ==> 0 <= a[k]
exists k :: 0 <= k < a.length ==> 0 <= a[k]
Exercise: Use quantifiers to specify that the method
Find
returns a negative number whenever the searched key is not
present in the array.
method Find(a: array<int>, key: int) returns (index: int)
ensures 0 <= index ==> index < a.Length && a[index] == key
{
// ...
}
Exercise: Write a method that takes an integer array, which it requires to have at least one element, and returns an index to the maximum of the array's elements. Annotate the method with pre- and postconditions that state the intent of the method, and annotate its body with a loop invariant to verify it.
method FindMax(a: array<int>) returns (max: int)
predicate sorted(a: array<int>)
requires a != null
{
forall j, k :: 0 <= j < k < a.Length ==> a[j] <= a[k]
}
Write this predicate in Dafny. Try to explain the error message that appears.
predicate sorted(a: array<int>)
requires a != null
reads a
{
forall j, k ::
0 <= j < k < a.Length ==>
a[j] <= a[k]
}
modifies
annotation. Example:
method m3(a: array<int>)
requires 1 <= a.Length
modifies a
{
a[0] := 42;
}
Exercise: Write a verified implementation of binary search. Use the following signature:
method BinarySearch(a: array<int>, key: int) returns (index: int)
requires a != null && sorted(a)
ensures ...
{
...
}
datatype Colour = Red | White | Blue
datatype Nat = Zero | Succ(n: Nat)
Zero
, Succ(Zero)
,
Succ(Succ(Zero))
, etc.Zero < Succ(Zero) < Succ(Succ(Zero))
function add(n1: Nat, n2: Nat): Nat
{
match n1
case Zero => n2
case Succ(n) => Succ(add(n, n2))
}
lemma add_unit(n: Nat)
ensures add(n, Zero) == n
ensures add(Zero, n) == n
{}
lemma add_succ(n1: Nat, n2: Nat)
ensures Succ(add(n1,n2)) == add(n1, Succ(n2))
{}
{}
,
then it's an axiom (i.e., we assume it to
be true).
lemma add_commutative(n1: Nat, n2: Nat)
ensures add(n1, n2) == add(n2, n1)
{}
lemma {:induction n1} add_commutative(n1: Nat, n2: Nat)
ensures add(n1, n2) == add(n2, n1)
{
match n1
case Zero =>
calc {
add(Zero, n2);
== // def. add
n2;
== { add_unit(n2); }
add(n2, Zero);
}
case Succ(n) =>
// Inductive hypothesis: add(n, n2) == add(n2, n)
}
case Succ(n) =>
// Inductive hypothesis: add(n, n2) == add(n2, n)
calc {
add(n1, n2);
== // n1 == Succ(n)
add(Succ(n), n2);
== // def. add
Succ(add(n,n2));
== // IH: add(n, n2) == add(n2, n)
Succ(add(n2,n));
== { add_succ(n2, n); }
add(n2, Succ(n));
== // Succ(n) == n1
add(n2, n1);
}
Activity: Let's define together a functional library to manipulate Lists.
datatype List = ...