LABS 02: USING THE STAINLESS VERIFICATION SYSTEM (CONT.)
In the second lab, you'll be using Stainless to verify properties about some more complicated programs.
Deliverable
You are given 1 week for this assignment.
Deadline: Thursday, Mar. 9, 23:59.
Send your solutions by mail to nicolas.voirol@epfl.ch.
Exercise 1
We want to verify an implementation of quickSort here. Consider the following code snippet:
import stainless.lang._ import stainless.collection._ object QuickSort { def isSorted(list: List[BigInt]): Boolean = list match { case Cons(x, xs @ Cons(y, _)) => x <= y && isSorted(xs) case _ => true } def quickSort(list: List[BigInt]): List[BigInt] = (list match { case Nil() => Nil[BigInt]() case Cons(x, xs) => par(x, Nil(), Nil(), xs) }) ensuring { res => isSorted(res) } def par(x: BigInt, l: List[BigInt], r: List[BigInt], ls: List[BigInt]): List[BigInt] = ls match { case Nil() => quickSort(l) ++ Cons(x, quickSort(r)) case Cons(x2, xs2) => if (x2 <= x) par(x, Cons(x2, l), r, xs2) else par(x, l, Cons(x2, r), xs2) } }
Your task consists of adding contracts to the quickSort and par functions in order for the postcondition of quickSort to verify.
Hint 1: it may be useful to introduce preconditions! However, you aren't allowed to introduce constraints on the inputs to quickSort.
Hint 2: you may need to introduce extra functions!
Hint 3: in some cases when you want to specify a property on the contents of some list, it is useful to use a quantified formula. For example, to say that some property *p* holds on every element of *list*, you can use
forall(x => list.content.contains(x) ==> p(x))
Exercise 2
Now, to make sure your verification was actually valid, make sure that Stainless can prove termination of the previous quickSort implementation. Termination is shown by inferring a *measure* such that each successive recursive call is linked to some decrease in the measure. The measure is specified thanks to the *decreases* keyword. For example, you can show that append terminates as follows (note that Stainless automatically shows List.size as terminating by structural induction):
import stainless.lang._ import stainless.collection._ object ListTermination { def append[A](l1: List[A], l2: List[A]): List[A] = { decreases(l1.size) l1 match { case Cons(x, xs) => Cons(x, append(xs, l2)) case Nil() => l2 } } }
You can invoke Stainless termination by using the –termination argument.
Annotate the quickSort and par functions with *decreases* constructs to specify the ordering. Note that you can use lexicographic orderings here by giving tuple arguments to *decreases*. Lexicographic ordering on a pair of non-negative integers (x1,y1) < (x2, y2) is defined by the relation
x1 < x2 || (x1 == x1 && y1 < y2)
This ordering extends to n-tuples. You may have encountered it if you examine, in a printed dictionary, the ordering of words of the same length, where the word “pie” comes before the word “pig” because, in the alphabetical ordering,
'p'=='p' && 'i'=='i' && 'e' < 'g'
Solutions
import stainless.proof._ import stainless.lang._ import stainless.collection._ object QuickSort { def isSorted(list: List[BigInt]): Boolean = { list match { case Cons(x, xs @ Cons(y, _)) => x <= y && isSorted(xs) case _ => true } } def appendSorted(l1: List[BigInt], l2: List[BigInt]): List[BigInt] = { require(isSorted(l1) && isSorted(l2) && (l1.isEmpty || l2.isEmpty || l1.last <= l2.head)) l1 match { case Nil() => l2 case Cons(x, xs) => Cons(x, appendSorted(xs, l2)) } } ensuring { res => isSorted(res) && res.content == l1.content ++ l2.content } def quickSort(list: List[BigInt]): List[BigInt] = { decreases(list.size, BigInt(0)) list match { case Nil() => Nil[BigInt]() case Cons(x, xs) => par(x, Nil(), Nil(), xs) } } ensuring { res => isSorted(res) && res.content == list.content } def par(x: BigInt, l: List[BigInt], r: List[BigInt], ls: List[BigInt]): List[BigInt] = { require( forall((a: BigInt) => l.contains(a) ==> a <= x) && forall((a: BigInt) => r.contains(a) ==> x < a) ) decreases(l.size + r.size + ls.size, ls.size + 1) ls match { case Nil() => appendSorted(quickSort(l), Cons(x, quickSort(r))) case Cons(x2, xs2) => if (x2 <= x) par(x, Cons(x2, l), r, xs2) else par(x, l, Cons(x2, r), xs2) } } ensuring { res => isSorted(res) && res.content == l.content ++ r.content ++ ls.content + x } }