Jean-Baptiste Tristan & K. Rustan M. Leino |
In Dafny, you can state and prove mathematical propositions
lemma Proposition()
ensures forall m: int, n: int :: m > 0 && n > m ==> m + n > 0
A proposition and its proof are packaged as a lemma
If a lemma comes with a proof script, then the Dafny verifier checks that the proof is valid and establishes the proposition
We will now introduce the languages of proofs
Dafny builds on automated theorem proving
In many cases, Dafny can find a proof on its own
lemma AutoActive()
ensures forall m: int, n: int :: m + n == n + m
{
}
In this example, Dafny finds a complete proof on its own
Therefore, we know that the ensures
clause is true
Dafny is somewhere in between an automated and an interactive theorem prover
A proof plays two roles.
(i) A proof convinces the reader that the statement is correct.
(ii) A proof explains why the statement is correct.
The first point consists of the administrative (‘bookkeeper’) activities of verifying the correctness of the small reasoning steps and see if they constitute a correct proof. One doesn’t have to look at the broad picture, but one just has to verify step by step whether every step is correct. The second point deals with giving the intuition of the theorem: Why is it so natural that this property holds? How did we come to the idea of proving it in this way?
— Herman Geuvers
The principal way by which you write proofs in Dafny is by explaining
You do so by asserting intuitive properties that guide Dafny
Consider the following exercise
ghost predicate Even(n: nat)
ghost predicate Odd(n: nat)
lemma EvenP()
ensures forall n: nat :: Even(n) == exists m: nat :: n == 2 * m
lemma OddP()
ensures forall n: nat :: Odd(n) == !Even(n)
lemma SuccOddIsEven()
ensures forall n: nat :: Odd(n) ==> Even(n + 1)
This is probably obvious to you, but not to Dafny
The reason it is not so obvious is because of the quantifiers
assert
keyword is followed by a proposition
lemma SuccOddIsEven()
ensures forall n: nat :: Odd(n) ==> Even(n + 1)
{
EvenP();
OddP();
assert forall n: nat :: n % 2 == 0 ==> n == 2 * (n / 2);
assert forall n: nat :: n == 2 * (n / 2) ==> Even(n);
assert forall n: nat :: Odd(n) ==> n % 2 == 1;
assert forall n: nat :: n % 2 == 1 ==> (n + 1) % 2 == 0;
}
n
for the proof
lemma SuccOddIsEven(n: nat)
ensures Odd(n) ==> Even(n + 1)
{
EvenP();
OddP();
assert forall n: nat :: n % 2 == 0 ==> n == 2 * (n / 2);
assert forall n: nat :: n == 2 * (n / 2) ==> Even(n);
assert Odd(n) ==> n % 2 == 1;
assert Odd(n) ==> (n + 1) % 2 == 0;
}
We would not write a proof like that on paper
Instead, we would work around all the implications by stating what assumptions we are making in the proof
lemma SuccOddIsEven(n: nat)
requires Odd(n)
ensures Even(n + 1)
{
EvenP();
OddP();
assert forall n: nat :: n % 2 == 0 ==> n == 2 * (n / 2);
assert forall n: nat :: n == 2 * (n / 2) ==> Even(n);
assert n % 2 == 1;
assert (n + 1) % 2 == 0;
}
We would not write a proof like that on paper
Instead, we would organize our proof by first defining a proving a lemma
lemma DivBy2IsEven()
ensures forall n: nat :: n % 2 == 0 ==> Even(n)
{
EvenP(); OddP();
assert forall n: nat :: n % 2 == 0 ==> n == 2 * (n / 2);
}
lemma SuccOddIsEven(n: nat)
requires Odd(n)
ensures Even(n + 1)
{
OddP();
DivBy2IsEven();
assert n % 2 == 1;
assert (n + 1) % 2 == 0;
}
lemma Prop()
ensures forall n: nat :: Even(n) ==> Even(n + 2)
{
assume false; // TODO
}
A forall
statement proves a universal property by fixing an arbitary name
lemma Prop()
ensures forall n: nat :: Even(n) ==> Even(n + 2)
{
forall n: nat
ensures Even(n) ==> Even(n + 2)
{
assume false; // TODO
}
}
An if
statement proves a conditional property by assuming a property
lemma Prop()
ensures forall n: nat :: Even(n) ==> Even(n + 2)
{
forall n: nat
ensures Even(n) ==> Even(n + 2)
{
if Even(n) {
assume false; // TODO
}
}
}
A Skolemization fixes a witness for a known existential property
lemma Prop()
ensures forall n: nat :: Even(n) ==> Even(n + 2)
{
forall n: nat
ensures Even(n) ==> Even(n + 2)
{
if Even(n) {
var a: nat :| n == 2 * a;
assume false; // TODO
}
}
}
A variable definition names an expression and creates a witness
lemma Prop()
ensures forall n: nat :: Even(n) ==> Even(n + 2)
{
forall n: nat
ensures Even(n) ==> Even(n + 2)
{
if Even(n) {
var a: nat :| n == 2 * a;
var b: nat := 2 * (a + 1);
}
}
}
Assume the contrary to your goal and prove false
lemma ProofByContradiction(n: nat)
requires Even(n)
ensures !exists m: nat :: n == 2 * m + 1
{
var m1: nat :| n == 2 * m1;
if exists m: nat :: n == 2 * m + 1 {
var m2: nat :| n == 2 * m2 + 1;
assert false;
}
}
In math, we sometimes enumerate different possibilities
In Dafny, one way to do this uses conditional statements
lemma ProofByCaseAnalysis(n: nat)
ensures Even(n) || Odd(n)
{
if n % 2 == 0 { assert n == 2 * (n / 2); }
if n % 2 == 1 { assert n == (2 * (n / 2)) + 1; }
}
Lemma 1.
$\forall n \cdot \sum_{i=0}^n i = \frac{n(n+1)}{2}$
Proof.
In Dafny, a proof by induction amounts to a recursive call with a smaller argument
lemma ProofByInduction(n: nat)
requires n % 2 == 0
ensures Even(n)
{
if n == 0 {
assert n == 2 * 0;
} else {
assert (n - 2) % 2 == 0;
ProofByInduction(n - 2);
var m: nat :| n - 2 == 2 * m;
assert n == 2 * m + 2;
assert n == 2 * (m + 1);
}
}