// Style 1: extrinsec verification // lemma and code separate datatype List = Empty | Cons(head : int, tail : List) function len(l : List) : int { match l case Empty => 0 case Cons(head, tail) => 1 + len(tail) } function app(l1 : List, l2 : List) : List { match l1 case Empty => l2 case Cons(head, tail) => Cons(head, app(tail, l2)) } lemma {:induction false} app_len(l1 : List, l2 : List) ensures len(app(l1, l2)) == len(l1) + len(l2) { match l1 case Empty => case Cons(head, tail) => app_len(tail, l2); } // Style 2: intrinsec verification // postconditions part of function definition/declaration // for each function call: precondition proved in order for the code to compile // postcondition brought into the current proof context function append(l1 : List, l2 : List) : List ensures len(append(l1, l2)) == len(l1) + len(l2) { match l1 case Empty => l2 case Cons(head, tail) => Cons(head, append(tail, l2)) } lemma try1(l1 : List, l2 : List) requires len(l1) == 10 requires len(l2) == 10 { var l := app(l1, l2); app_len(l1, l2); // need to call lemma for the next assertion to be proved assert len(l) == 20; } lemma try2(l1 : List, l2 : List) requires len(l1) == 10 requires len(l2) == 10 { assert len(append(l1, l2)) == 20; // no need to call lemma here } // If a postcondition cannot be proved automatically, you can help // the system by adding proof structure. // Each expression is actually of the form: // < helper proof structure > ; // expression // where the < helper proof structure > can be omitted. // You can also declare and assign variables before the expression. function mywitness(n : nat) : bool // used for triggering an existential quantifier { true } function length(l : List) : int ensures exists n : nat {:trigger mywitness(n)} :: length(l) == n { match l case Empty => assert mywitness(0); // helper assertion 0 // return value for this branch case Cons(head, tail) => var length_tail : int := length(tail); // helper variable var ntail : nat :| length_tail == ntail; // helper variable assert mywitness(ntail + 1); // helper assertion 1 + length_tail // return value for this branch } /* Advantages of intrinsec proofs: - more automation; - less code. Advantages of extrinsec proofs: - better control (no polution of proof context with possibly irrelevant facts); - some properties impossible to state intrinsecly (e.g., associativity). */ /* When to use intrinsec style: - for very small postconditions that are likely universally useful. When to use extrinsec style: - when you need a property only a few times. */ // Example of property which should be intrinsec: function mylen(l : List) : int ensures mylen(l) >= 0 // likely useful several times { match l case Empty => 0 case Cons(head, tail) => 1 + mylen(tail) } // Example of property which should be extrinsec: lemma {:induction false} app_assoc(l1 : List, l2 : List, l3 : List) ensures app(l1, app(l2, l3)) == app(app(l1, l2), l3) { match l1 case Empty => case Cons(head, tail) => app_assoc(tail, l2, l3); } // Refinement type = type + constraint /* function mylen(l : List) : int ensures mylen(l) >= 0 // likely useful several times { match l case Empty => 0 case Cons(head, tail) => 1 + mylen(tail) } */ type Natural = x : int | 0 <= x witness 0 // Natural is the set of all integers x w/ the property that 0 <= x // witness tells Dafny that Natural is inhabited (i.e. not empty, has at least one value) function betterlen(l : List) : Natural { match l case Empty => 0 case Cons(_, tail) => 1 + betterlen(tail) } // the predefined nat type has the same definition method example_refinement(n : Natural, x : int) requires x >= -3 { assert n is int; // for every variable of a refinement type, Dafny knows its base type assert (n as int) >= 0; // and also that is satisfies the constraint // in reverse, for every assignment to a variable of a refinement type, // Dafny needs to establish the constraint. // this fails: // var m : Natural := x; // because Dafny cannot proof that m is >= 0 // this works, because the postcondition states x >= -3: var m' : Natural := x + 3; } // Here are some examples of useful refinement types: type byte = x : int | 0 <= x < 256 // an 8-bit value type color_channels = x : int | 3 <= x <= 4 witness 3 // number of color channels in an image (rgb or rgba) datatype cell = empty | O | X type board = s : seq | |s| == 9 witness seq(9, _ => empty) // tic-tac-toe board type dow = x : int | 0 <= x < 7 // day of week datatype Formula = Var(s : string) | Not(f : Formula) | And(f1 : Formula, f2 : Formula) | Or(f1 : Formula, f2 : Formula) predicate negationFree(f : Formula) { match f case Var(_) => true case Not(f) => false case _ => negationFree(f.f1) && negationFree(f.f2) } type NFFormula = f : Formula | negationFree(f) witness Var("p")