summaryrefslogtreecommitdiff
path: root/Test/dafny3/Streams.dfy
blob: 1369e5849bc436bd19b9ba94fb5de0f9047f5b5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// ----- Stream

codatatype Stream<T> = Nil | Cons(head: T, tail: Stream);

function append(M: Stream, N: Stream): Stream
{
  match M
  case Nil => N
  case Cons(t, M') => Cons(t, append(M', N))
}

// ----- f, g, and maps

type X;

function f(x: X): X
function g(x: X): X

function map_f(M: Stream<X>): Stream<X>
{
  match M
  case Nil => Nil
  case Cons(x, N) => Cons(f(x), map_f(N))
}

function map_g(M: Stream<X>): Stream<X>
{
  match M
  case Nil => Nil
  case Cons(x, N) => Cons(g(x), map_g(N))
}

function map_fg(M: Stream<X>): Stream<X>
{
  match M
  case Nil => Nil
  case Cons(x, N) => Cons(f(g(x)), map_fg(N))
}

// ----- Theorems

// map (f * g) M = map f (map g M)
comethod Theorem0(M: Stream<X>)
  ensures map_fg(M) == map_f(map_g(M));
{
  match (M) {
    case Nil =>
    case Cons(x, N) =>
      Theorem0(N);
  }
}
comethod Theorem0_alt(M: Stream<X>)
  ensures map_fg(M) == map_f(map_g(M));
{
  if (M.Cons?) {
    Theorem0(M.tail);
  }
}

// map f (append M N) = append (map f M) (map f N)
comethod Theorem1(M: Stream<X>, N: Stream<X>)
  ensures map_f(append(M, N)) == append(map_f(M), map_f(N));
{
  match (M) {
    case Nil =>
    case Cons(x, M') =>
      Theorem1(M', N);
  }
}
comethod Theorem1_alt(M: Stream<X>, N: Stream<X>)
  ensures map_f(append(M, N)) == append(map_f(M), map_f(N));
{
  if (M.Cons?) {
    Theorem1(M.tail, N);
  }
}

// append NIL M = M
ghost method Theorem2(M: Stream<X>)
  ensures append(Nil, M) == M;
{
  // trivial
}

// append M NIL = M
comethod Theorem3(M: Stream<X>)
  ensures append(M, Nil) == M;
{
  match (M) {
    case Nil =>
    case Cons(x, N) =>
      Theorem3(N);
  }
}
comethod Theorem3_alt(M: Stream<X>)
  ensures append(M, Nil) == M;
{
  if (M.Cons?) {
    Theorem3(M.tail);
  }
}

// append M (append N P) = append (append M N) P
comethod Theorem4(M: Stream<X>, N: Stream<X>, P: Stream<X>)
  ensures append(M, append(N, P)) == append(append(M, N), P);
{
  match (M) {
    case Nil =>
    case Cons(x, M') =>
      Theorem4(M', N, P);
  }
}
comethod Theorem4_alt(M: Stream<X>, N: Stream<X>, P: Stream<X>)
  ensures append(M, append(N, P)) == append(append(M, N), P);
{
  if (M.Cons?) {
    Theorem4(M.tail, N, P);
  }
}

// ----- Flatten

// Flatten can't be written as just:
//
//     function SimpleFlatten<T>(M: Stream<Stream>): Stream
//     {
//       match M
//       case Nil => Nil
//       case Cons(s, N) => append(s, SimpleFlatten(N))
//     }
//
// because this function fails to be productive given an infinite stream of Nil's.
// Instead, here are two variations SimpleFlatten.  The first variation (FlattenStartMarker)
// prepends a "startMarker" to each of the streams in "M".  The other (FlattenNonEmpties)
// insists that "M" contains no empty streams.  One can prove a theorem that relates these
// two versions.

// This first variation of Flatten returns a stream of the streams in M, each preceded with
// "startMarker".

function FlattenStartMarker<T>(M: Stream<Stream>, startMarker: T): Stream
{
  FlattenStartMarkerAcc(Nil, M, startMarker)
}

function FlattenStartMarkerAcc<T>(accumulator: Stream, M: Stream<Stream>, startMarker: T): Stream
{
  match accumulator
  case Cons(hd, tl) =>
    Cons(hd, FlattenStartMarkerAcc(tl, M, startMarker))
  case Nil =>
    match M
    case Nil => Nil
    case Cons(s, N) => Cons(startMarker, FlattenStartMarkerAcc(s, N, startMarker))
}

// The next variation of Flatten requires M to contain no empty streams.

copredicate StreamOfNonEmpties(M: Stream<Stream>)
{
  match M
  case Nil => true
  case Cons(s, N) => s.Cons? && StreamOfNonEmpties(N)
}

function FlattenNonEmpties(M: Stream<Stream>): Stream
  requires StreamOfNonEmpties(M);
{
  FlattenNonEmptiesAcc(Nil, M)
}

function FlattenNonEmptiesAcc(accumulator: Stream, M: Stream<Stream>): Stream
  requires StreamOfNonEmpties(M);
{
  match accumulator
  case Cons(hd, tl) =>
    Cons(hd, FlattenNonEmptiesAcc(tl, M))
  case Nil =>
    match M
    case Nil => Nil
    case Cons(s, N) => Cons(s.head, FlattenNonEmptiesAcc(s.tail, N))
}

// We can prove a theorem that links the previous two variations of flatten.  To
// do that, we first define a function that prepends an element to each stream
// of a given stream of streams.
// The "assume" statements below are temporary workaround.  They make the proofs
// unsound, but, as a temporary measure, they express the intent of the proofs.

function Prepend<T>(x: T, M: Stream<Stream>): Stream<Stream>
  ensures StreamOfNonEmpties(Prepend(x, M));
{
  match M
  case Nil => Nil
  case Cons(s, N) => Cons(Cons(x, s), Prepend(x, N))
}

ghost method Theorem_Flatten<T>(M: Stream<Stream>, startMarker: T)
  ensures FlattenStartMarker(M, startMarker) == FlattenNonEmpties(Prepend(startMarker, M));
{
  Lemma_Flatten(Nil, M, startMarker);
}

comethod Lemma_Flatten<T>(accumulator: Stream, M: Stream<Stream>, startMarker: T)
  ensures FlattenStartMarkerAcc(accumulator, M, startMarker) == FlattenNonEmptiesAcc(accumulator, Prepend(startMarker, M));
{
  calc {
    FlattenStartMarkerAcc(accumulator, M, startMarker);
    { Lemma_FlattenAppend0(accumulator, M, startMarker); }
    append(accumulator, FlattenStartMarkerAcc(Nil, M, startMarker));
    { Lemma_Flatten(Nil, M, startMarker);
      assume FlattenStartMarkerAcc(Nil, M, startMarker) == FlattenNonEmptiesAcc(Nil, Prepend(startMarker, M));  // postcondition of the co-recursive call
    }
    append(accumulator, FlattenNonEmptiesAcc(Nil, Prepend(startMarker, M)));
    { Lemma_FlattenAppend1(accumulator, Prepend(startMarker, M)); }
    FlattenNonEmptiesAcc(accumulator, Prepend(startMarker, M));
  }
}

comethod Lemma_FlattenAppend0<T>(s: Stream, M: Stream<Stream>, startMarker: T)
  ensures FlattenStartMarkerAcc(s, M, startMarker) == append(s, FlattenStartMarkerAcc(Nil, M, startMarker));
{
  match (s) {
    case Nil =>
    case Cons(hd, tl) =>
      calc {
        FlattenStartMarkerAcc(Cons(hd, tl), M, startMarker);
        Cons(hd, FlattenStartMarkerAcc(tl, M, startMarker));
        { Lemma_FlattenAppend0(tl, M, startMarker);
          assume FlattenStartMarkerAcc(tl, M, startMarker) == append(tl, FlattenStartMarkerAcc(Nil, M, startMarker));  // this is the postcondition of the co-recursive call
        }
        Cons(hd, append(tl, FlattenStartMarkerAcc(Nil, M, startMarker)));
        // def. append
        append(Cons(hd, tl), FlattenStartMarkerAcc(Nil, M, startMarker));
      }
  }
}

comethod Lemma_FlattenAppend1<T>(s: Stream, M: Stream<Stream>)
  requires StreamOfNonEmpties(M);
  ensures FlattenNonEmptiesAcc(s, M) == append(s, FlattenNonEmptiesAcc(Nil, M));
{
  match (s) {
    case Nil =>
    case Cons(hd, tl) =>
      calc {
        FlattenNonEmptiesAcc(Cons(hd, tl), M);
        // def. FlattenNonEmptiesAcc
        Cons(hd, FlattenNonEmptiesAcc(tl, M));
        { Lemma_FlattenAppend1(tl, M);
          assume FlattenNonEmptiesAcc(tl, M) == append(tl, FlattenNonEmptiesAcc(Nil, M));  // postcondition of the co-recursive call
        }
        Cons(hd, append(tl, FlattenNonEmptiesAcc(Nil, M)));
        // def. append
        append(Cons(hd, tl), FlattenNonEmptiesAcc(Nil, M));
      }
  }
}