|
1 |
| -/* More interesting data to reason about: sequences. */ |
2 |
| - |
3 |
| -/* Outline: |
4 |
| -- sequences |
5 |
| -- algebraic data types (user-defined data) |
6 |
| -*/ |
7 |
| - |
8 |
| -/* In order to write proofs about interesting systems and protocols, we first |
9 |
| -need some way of modeling the state of those systems and protocols. We'll |
10 |
| -explore Dafy's built-in sequence type as well as algebraic data types, its core |
11 |
| -way of creating user-defined types. */ |
12 |
| - |
13 |
| -/*** Sequences ***/ |
14 |
| - |
15 |
| -/* These data types are very similar to data structures, but will be purely |
16 |
| - * mathematical. We'll start with the sequence type `seq<T>`. The `T` in angle |
17 |
| - * brackets is a _type parameter_, needed because sequences can hold values of |
18 |
| - * any type. A sequence is related to data structures you're familiar with like |
19 |
| - * linked lists or arrays/vectors (the terminology depends on the programming |
20 |
| - * language). A sequence is the mathematical essence of the state of a |
21 |
| - * list/vector/array without the implementation details. |
22 |
| - */ |
23 |
| - |
24 |
| -lemma SequenceOperations() |
25 |
| -{ |
26 |
| - // var is a (mutable) local variable, defined within a lemma |
27 |
| - var s: seq<int> := [3, 1, 2]; |
28 |
| - // sequences can be indexed, starting at 1 |
29 |
| - assert s[1] == 1; |
30 |
| - // we can extract a subsequence |
31 |
| - assert s[1..2] == [1]; |
32 |
| - // we can re-assign the sequence and update one element using the `s[i := v]` syntax. |
33 |
| - s := s[2 := 3]; |
34 |
| - assert s[0] == s[2] == 3; |
35 |
| - // `|s|` is the sequence's length |
36 |
| - assert |s| == 3; |
37 |
| - // we can append two sequences |
38 |
| - s := s + [7, 12]; |
39 |
| - assert |s| == 5; |
40 |
| - assert s == [3, 1, 3, 7, 12]; |
41 |
| -} |
42 |
| - |
43 |
| -/* Below we give some lemmas illustrating sequence operations. */ |
44 |
| - |
45 |
| -function SeqGet(s: seq<int>, i: nat): int |
46 |
| - // New feature (function preconditions): functions can have _requires_ |
47 |
| - // clauses, also called _preconditions_. Dafny will statically check that the |
48 |
| - // preconditions hold (in the same way as assertions) wherever a call to the |
49 |
| - // function appears. |
50 |
| - // |
51 |
| - // This particular precondition requires that the index is in-bounds. The fact |
52 |
| - // that `i` is of type `nat` already assures that it is non-negative. |
53 |
| - requires i < |s| |
54 |
| -{ |
55 |
| - s[i] |
56 |
| -} |
57 |
| - |
58 |
| -function SeqUpdate(s: seq<int>, i: nat, n: int): seq<int> |
59 |
| - requires i < |s| |
60 |
| -{ |
61 |
| - s[i := n] |
62 |
| -} |
63 |
| - |
64 |
| -lemma CheckedPreconditions(s: seq<int>, i: nat) |
65 |
| - // Dafny does not allow even stating this because it tries to index a // |
66 |
| - // sequence without a proof that the index is in-bounds, one of the preconditions |
67 |
| - // for sequence indexing. |
68 |
| - // |
69 |
| - // We'd get the same error in an ensures clause or function definition. |
70 |
| - requires s[i] > 0 // error: index out of range |
71 |
| -{} |
72 |
| - |
73 |
| -lemma CheckedFunctionPreconditions(s: seq<int>, i: nat) |
74 |
| - // almost same as above, but notice that the error specifically points to the |
75 |
| - // `SeqGet` precondition |
76 |
| - requires SeqGet(s, i) > 0 // error: function precondition might not hold |
77 |
| -{} |
78 |
| - |
79 |
| -lemma SequenceAppendFact(s1: seq<int>, s2: seq<int>) |
80 |
| - // New syntax (forall where clause): notice the new pipe symbol after the `i`. |
81 |
| - // The general syntax is of the form `forall x | p(x) :: q(x)`; it means the |
82 |
| - // same thing as `forall x :: p(x) ==> q(x)` but can be easier to read. You |
83 |
| - // might read this as "for all x where p(x), q(x)". |
84 |
| - // |
85 |
| - // We're also leaving off the type of `i` because Dafny can infer that it's an `int`. |
86 |
| - ensures forall i | |s1| <= i < |s1| + |s2| :: (s1 + s2)[i] == s2[i - |s1|] |
87 |
| -{ |
88 |
| - // goes through without any extra proof |
89 |
| -} |
90 |
| - |
| 1 | +/* More interesting data to reason about: algebraic data types. */ |
91 | 2 |
|
92 | 3 | /*** Algebraic data types ***/
|
93 | 4 |
|
94 | 5 | /* Sequences are powerful and all, but eventually you'll want to define new data
|
95 |
| -types. For that Dafny has algebraic data types which capture "products" |
96 |
| -(struct-like types) and "sums" (tagged unions). While we're explaining algebraic |
97 |
| -data types we'll take a short break from lemmas/proofs and just explain this bit |
98 |
| -of functional programming. */ |
| 6 | + * types. For that Dafny has algebraic data types which capture "products" |
| 7 | + * (struct-like types) and "sums" (tagged unions). These are often abbreviated to |
| 8 | + * "ADTs" (not to be confused with "abstract data types" which are a totally |
| 9 | + * different concept related to object-oriented programming; we won't generally |
| 10 | + * use that term). Algebraic data types are also used for programming (as opposed |
| 11 | + * to in the course where we'll only see them used for mathematical models), so |
| 12 | + * we'll take a short break and first explain them in the context of functions |
| 13 | + * before seeing them used in lemmas and assertions. */ |
99 | 14 |
|
100 | 15 | /* Let's start with a struct that has two fields, x and y: */
|
101 | 16 | datatype Point = PointCtor(x: int, y: int)
|
@@ -174,19 +89,25 @@ datatype CoffeeRecipe =
|
174 | 89 | // a very incomplete list :)
|
175 | 90 |
|
176 | 91 | // How much milk will be in my coffee drink?
|
| 92 | +// |
| 93 | +// (If you don't know much about coffee, that's okay! Take this as a |
| 94 | +// specification for what these drinks are, at least as far as milk is |
| 95 | +// concerned.) |
177 | 96 | function MilkOz(coffee: CoffeeRecipe): nat {
|
178 | 97 | match coffee {
|
179 |
| - // New feature: we can put an underscore for fields that aren't needed in |
180 |
| - // some "match arm". We can also name a field and refer to it on the right side |
181 |
| - // (notice that this name also doesn't have to match the name in the datatype). |
| 98 | + // New feature (ignore patterns): we can put an underscore for fields that |
| 99 | + // aren't needed in some "match arm". We can also name a field and refer to |
| 100 | + // it on the right side (notice that this name also doesn't have to match |
| 101 | + // the name in the datatype). |
182 | 102 | case Drip(_, milk) => if milk then 1 else 0
|
183 | 103 | case Espresso(_) => 0
|
184 | 104 | // standard-sized latte
|
185 | 105 | case Latte(_) => 8
|
186 | 106 | }
|
187 | 107 | }
|
188 | 108 |
|
189 |
| -/* Below we illustrate some reasoning about data types. */ |
| 109 | +/* Now that we've seen algebraic datatypes in functions, let's start using them |
| 110 | + * in lemmas and assertions. */ |
190 | 111 |
|
191 | 112 | lemma VariantsDiffer() {
|
192 | 113 | assert Cytosine != Guanine;
|
|
0 commit comments