if
, while
,
:=
, class
, ...
function
,
datatype
,
codatatype
, ...
Lemma
,
calc
,
refines
,
inductive
predicate
, ...method between (p: int, r: int) returns (q: int)
requires r-p > 1
ensures p < q < r
{
q := (p+r) / 2;
}
method MultipleReturns(x: int, y: int)
returns (more: int, less: int)
ensures less < x
ensures x < more
{
more := x + y;
less := x - y;
}
Does this program meet the specification?
Try it online: https://rise4fun.com/Dafny
method MultipleReturns(x: int, y: int)
returns (more: int, less: int)
requires 0 < y
ensures less < x
ensures x < more
{
more := x + y;
less := x - y;
}
var
keyword, and can optionally have type
declarations:
var x:int;
var x := 0;
var x:int, y:nat;
var x, y : int;
Exercise: Encode the following example in Dafny and read the error messages.
method m1() {
var a := 0;
a := 1;
var a := 2;
var b:int;
var c;
var d:int, e:bool := 0;
var f, g := 0, false;
var h, i:int;
}
method Triple(x: int) returns (r: int)
ensures r == 3*x
{
var y := Double(x);
r := y + x;
}
method Double(x:int) returns (r:int)
ensures r == 2*x
This follows our discussion on Design by Contract.
Try it online: https://rise4fun.com/Dafny
This related to our discussion on Design by Contract from previous lectures.
Exercise: Write a method Max that takes two integer parameters and returns their maximum. Add appropriate annotations and make sure your code verifies.
method Abs(x: int) returns (y: int)
ensures 0 <= y
{
if x < 0 {
return -x;
} else {
return x;
}
}
method TestAbs() {
var v := Abs(3);
assert 0 <= v;
}
Assertions can be introduced at any point. An assertion says that a particular expression always holds when control reaches that part of the code.
Exercise: Add
assert v == 3
to the end of the method TestAbs
.
Make changes so that the assertion is valid.
method Abs(x: int) returns (y: int)
ensures 0 <= y
{
if x < 0 {
return -x;
} else {
return x;
}
}
method TestAbs() {
var v := Abs(3);
assume v==3;
assert 0 <= v;
assert v==3;
}
Assumptions can be introduced at any point.
function abs(x: int): int
{
if x < 0 then -x else x
}
method m2()
{
assert abs(3) == 3;
}
function abs(x: int): int
{
if x < 0 then -x else x
}
method m2()
{
assert abs(3) == 3;
}
Exercise: Change your specification of
the method Abs
to use this function.
Exercise:
Write a function max
that returns the larger of two given integer
parameters. Write a test method using an assert that tests your function.
Exercise:
Change the specification of the method Max
to use your new function.
We are not going into much detail for now, but it is possible to compile Dafny programs. Example:
method Main()
{
print("Hello World\n");
}
To verify, compile, and run the program:
dafny /compile:3 Main.dfy
function
method
are compiled and can be
called from real code.Exercise: Change your test method for
Max
to capture the value of
max
to a variable, and then do the checks
from the exercise using the variable.
Dafny will reject this program because you are calling
max
from real code. Fix this problem using
a function method
.
Exercise: Define the Fibonacci function and write a method with assertions that test your function.
The Fibonacci function is defined as:
$$ \scriptsize \begin{array}{ll} fib(0) = 0 & \\ fib(1) = 1 & \\ fib(n) = fib(n-1) + fib(n-2) & \,\,\,\textrm{for $n\gt 1$} \end{array} $$invariant
to identify loop invariants:method m4(n: nat)
{
var i := 0;
while i < n
invariant 0 <= i
{
i := i+1;
}
assert i==n;
}
Question: Is the assertion at the end of the loop valid? If not, how can we fix it?
Exercise: Write a method Exp
that takes an integer $m$, a natural
$n$, and returns $m^n$. Write the specification in
terms of a separate, recursive function
exp
.
Exercise: Consider the following function definition:
function fib(n: nat): nat
{
if n == 0 then 0 else
if n == 1 then 1 else fib(n - 1) + fib(n - 2)
}
Turning this function into a function method would be extremely slow, as this version of calculating the Fibonacci numbers has exponential complexity.
Write a verified iterative algorithm that computes the $n^{th}$ Fibonacci number.
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 ...
{
...
}