forked from blanchette/logical_verification_2021
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlove04_functional_programming_demo.lean
527 lines (388 loc) · 12.4 KB
/
love04_functional_programming_demo.lean
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
import .lovelib
/-! # LoVe Demo 4: Functional Programming
We take a closer look at the basics of typed functional programming: inductive
types, proofs by induction, recursive functions, pattern matching, structures
(records), and type classes. -/
set_option pp.beta true
set_option pp.generalized_field_notation false
namespace LoVe
/-! ## Inductive Types
Recall the definition of type `nat` (= `ℕ`): -/
#print nat
/-! Mottos:
* **No junk**: The type contains no values beyond those expressible using the
constructors.
* **No confusion**: Values built in a different ways are different.
For `nat` (= `ℕ`):
* "No junk" means that there are no special values, say, `–1` or `ε`, that
cannot be expressed using a finite combination of `zero` and `succ`.
* "No confusion" is what ensures that `zero` ≠ `succ x`.
In addition, inductive types are always finite. `succ (succ (succ …))` is not a
value.
## Structural Induction
__Structural induction__ is a generalization of mathematical induction to
inductive types. To prove a property `P[n]` for all natural numbers `n`, it
suffices to prove the base case
`P[0]`
and the induction step
`∀k, P[k] → P[k + 1]`
For lists, the base case is
`P[[]]`
and the induction step is
`∀y ys, P[ys] → P[y :: ys]`
In general, there is one subgoal per constructor, and induction hypotheses are
available for all constructor arguments of the type we are doing the induction
on. -/
lemma nat.succ_neq_self (n : ℕ) :
nat.succ n ≠ n :=
begin
induction' n,
{ simp },
{ simp [ih] }
end
/-! The `case` tactic can be used to supply custom names, and potentially
reorder the cases. -/
lemma nat.succ_neq_self₂ (n : ℕ) :
nat.succ n ≠ n :=
begin
induction' n,
case succ : m IH {
simp [IH] },
case zero {
simp }
end
/-! ## Structural Recursion
__Structural recursion__ is a form of recursion that allows us to peel off
one or more constructors from the value on which we recurse. Such functions are
guaranteed to call themselves only finitely many times before the recursion
stops. This is a prerequisite for establishing that the function terminates. -/
def fact : ℕ → ℕ
| 0 := 1
| (n + 1) := (n + 1) * fact n
def fact₂ : ℕ → ℕ
| 0 := 1
| 1 := 1
| (n + 1) := (n + 1) * fact₂ n
/-! For structurally recursive functions, Lean can automatically prove
termination. For more general recursive schemes, the termination check may fail.
Sometimes it does so for a good reason, as in the following example: -/
-- fails
def illegal : ℕ → ℕ
| n := illegal n + 1
constant immoral : ℕ → ℕ
axiom immoral_eq (n : ℕ) :
immoral n = immoral n + 1
lemma proof_of_false :
false :=
have immoral 0 = immoral 0 + 1 :=
immoral_eq 0,
have immoral 0 - immoral 0 = immoral 0 + 1 - immoral 0 :=
by cc,
have 0 = 1 :=
by simp [*] at *,
show false, from
by cc
/-! ## Pattern Matching Expressions
`match` _term₁_, …, _termM_ `with`
| _pattern₁₁_, …, _pattern₁M_ := _result₁_
⋮
| _patternN₁_, …, _patternNM_ := _resultN_
`end`
`match` allows nonrecursive pattern matching within terms.
In contrast to pattern matching after `lemma` or `def`, the patterns are
separated by commas, so parentheses are optional. -/
def bcount {α : Type} (p : α → bool) : list α → ℕ
| [] := 0
| (x :: xs) :=
match p x with
| tt := bcount xs + 1
| ff := bcount xs
end
def min (a b : ℕ) : ℕ :=
if a ≤ b then a else b
/-! ## Structures
Lean provides a convenient syntax for defining records, or structures. These are
essentially nonrecursive, single-constructor inductive types. -/
structure rgb :=
(red green blue : ℕ)
#check rgb.mk
#check rgb.red
#check rgb.green
#check rgb.blue
namespace rgb_as_inductive
inductive rgb : Type
| mk : ℕ → ℕ → ℕ → rgb
def rgb.red : rgb → ℕ
| (rgb.mk r _ _) := r
def rgb.green : rgb → ℕ
| (rgb.mk _ g _) := g
def rgb.blue : rgb → ℕ
| (rgb.mk _ _ b) := b
end rgb_as_inductive
structure rgba extends rgb :=
(alpha : ℕ)
#print rgba
def pure_red : rgb :=
{ red := 0xff,
green := 0x00,
blue := 0x00 }
def semitransparent_red : rgba :=
{ alpha := 0x7f,
..pure_red }
#print pure_red
#print semitransparent_red
def shuffle (c : rgb) : rgb :=
{ red := rgb.green c,
green := rgb.blue c,
blue := rgb.red c }
/-! `cases'` performs a case distinction on the specified term. This gives rise
to as many subgoals as there are constructors in the definition of the term's
type. The tactic behaves the same as `induction'` except that it does not
produce induction hypotheses. -/
lemma shuffle_shuffle_shuffle (c : rgb) :
shuffle (shuffle (shuffle c)) = c :=
begin
cases' c,
refl
end
lemma shuffle_shuffle_shuffle₂ (c : rgb) :
shuffle (shuffle (shuffle c)) = c :=
match c with
| rgb.mk _ _ _ := eq.refl _
end
/-! ## Type Classes
A __type class__ is a structure type combining abstract constants and their
properties. A type can be declared an instance of a type class by providing
concrete definitions for the constants and proving that the properties hold.
Based on the type, Lean retrieves the relevant instance. -/
#print inhabited
@[instance] def nat.inhabited : inhabited ℕ :=
{ default := 0 }
@[instance] def list.inhabited {α : Type} :
inhabited (list α) :=
{ default := [] }
#eval inhabited.default ℕ -- result: 0
#eval inhabited.default (list ℤ) -- result: []
def head {α : Type} [inhabited α] : list α → α
| [] := inhabited.default α
| (x :: _) := x
lemma head_head {α : Type} [inhabited α] (xs : list α) :
head [head xs] = head xs :=
begin
cases' xs,
{ refl },
{ refl }
end
#eval head ([] : list ℕ) -- result: 0
#check list.head
@[instance] def fun.inhabited {α β : Type} [inhabited β] :
inhabited (α → β) :=
{ default := λa : α, inhabited.default β }
inductive empty : Type
@[instance] def fun_empty.inhabited {β : Type} :
inhabited (empty → β) :=
{ default := λa : empty, match a with end }
@[instance] def prod.inhabited {α β : Type}
[inhabited α] [inhabited β] :
inhabited (α × β) :=
{ default := (inhabited.default α, inhabited.default β) }
/-! Here are other type classes without properties: -/
#check has_zero
#check has_neg
#check has_add
#check has_one
#check has_inv
#check has_mul
#check (1 : ℕ)
#check (1 : ℤ)
#check (1 : ℝ)
/-! We encountered these type classes in lecture 2: -/
#print is_commutative
#print is_associative
/-! ## Lists
`list` is an inductive polymorphic type constructed from `nil` and `cons`: -/
#print list
/-! `cases'` can also be used on a hypothesis of the form `l = r`. It matches `r`
against `l` and replaces all occurrences of the variables occurring in `r` with
the corresponding terms in `l` everywhere in the goal. -/
lemma injection_example {α : Type} (x y : α) (xs ys : list α)
(h : list.cons x xs = list.cons y ys) :
x = y ∧ xs = ys :=
begin
cases' h,
cc
end
/-! If `r` fails to match `l`, no subgoals emerge; the proof is complete. -/
lemma distinctness_example {α : Type} (y : α) (ys : list α)
(h : [] = y :: ys) :
false :=
by cases' h
def map {α β : Type} (f : α → β) : list α → list β
| [] := []
| (x :: xs) := f x :: map xs
def map₂ {α β : Type} : (α → β) → list α → list β
| _ [] := []
| f (x :: xs) := f x :: map₂ f xs
#check list.map
lemma map_ident {α : Type} (xs : list α) :
map (λx, x) xs = xs :=
begin
induction' xs,
case nil {
refl },
case cons : y ys {
simp [map, ih] }
end
lemma map_comp {α β γ : Type} (f : α → β) (g : β → γ)
(xs : list α) :
map g (map f xs) = map (λx, g (f x)) xs :=
begin
induction' xs,
case nil {
refl },
case cons : y ys {
simp [map, ih] }
end
lemma map_append {α β : Type} (f : α → β) (xs ys : list α) :
map f (xs ++ ys) = map f xs ++ map f ys :=
begin
induction' xs,
case nil {
refl },
case cons : y ys {
simp [map, ih] }
end
def tail {α : Type} : list α → list α
| [] := []
| (_ :: xs) := xs
#check list.tail
def head_opt {α : Type} : list α → option α
| [] := option.none
| (x :: _) := option.some x
def head_pre {α : Type} : ∀xs : list α, xs ≠ [] → α
| [] hxs := by cc
| (x :: _) _ := x
#eval head_opt [3, 1, 4]
#eval head_pre [3, 1, 4] (by simp)
-- fails
#eval head_pre ([] : list ℕ) sorry
def zip {α β : Type} : list α → list β → list (α × β)
| (x :: xs) (y :: ys) := (x, y) :: zip xs ys
| [] _ := []
| (_ :: _) [] := []
#check list.zip
def length {α : Type} : list α → ℕ
| [] := 0
| (x :: xs) := length xs + 1
#check list.length
/-! `cases'` can also be used to perform a case distinction on a proposition, in
conjunction with `classical.em`. Two cases emerge: one in which the proposition
is true and one in which it is false. -/
#check classical.em
lemma min_add_add (l m n : ℕ) :
min (m + l) (n + l) = min m n + l :=
begin
cases' classical.em (m ≤ n),
case inl {
simp [min, h] },
case inr {
simp [min, h] }
end
lemma min_add_add₂ (l m n : ℕ) :
min (m + l) (n + l) = min m n + l :=
match classical.em (m ≤ n) with
| or.inl h := by simp [min, h]
| or.inr h := by simp [min, h]
end
lemma min_add_add₃ (l m n : ℕ) :
min (m + l) (n + l) = min m n + l :=
if h : m ≤ n then
by simp [min, h]
else
by simp [min, h]
lemma length_zip {α β : Type} (xs : list α) (ys : list β) :
length (zip xs ys) = min (length xs) (length ys) :=
begin
induction' xs,
case nil {
refl },
case cons : x xs' {
cases' ys,
case nil {
refl },
case cons : y ys' {
simp [zip, length, ih ys', min_add_add] } }
end
lemma map_zip {α α' β β' : Type} (f : α → α') (g : β → β') :
∀xs ys,
map (λab : α × β, (f (prod.fst ab), g (prod.snd ab)))
(zip xs ys) =
zip (map f xs) (map g ys)
| (x :: xs) (y :: ys) := by simp [zip, map, map_zip xs ys]
| [] _ := by refl
| (_ :: _) [] := by refl
/-! ## Binary Trees
Inductive types with constructors taking several recursive arguments define
tree-like objects. __Binary trees__ have nodes with at most two children. -/
inductive btree (α : Type) : Type
| empty {} : btree
| node : α → btree → btree → btree
/-! The type `aexp` of arithmetic expressions was also an example of a tree data
structure.
The nodes of a tree, whether inner nodes or leaf nodes, often carry labels or
other annotations.
Inductive trees contain no infinite branches, not even cycles. This is less
expressive than pointer- or reference-based data structures (in imperative
languages) but easier to reason about.
Recursive definitions (and proofs by induction) work roughly as for lists, but
we may need to recurse (or invoke the induction hypothesis) on several child
nodes. -/
def mirror {α : Type} : btree α → btree α
| btree.empty := btree.empty
| (btree.node a l r) := btree.node a (mirror r) (mirror l)
lemma mirror_mirror {α : Type} (t : btree α) :
mirror (mirror t) = t :=
begin
induction' t,
case empty {
refl },
case node : a l r ih_l ih_r {
simp [mirror, ih_l, ih_r] }
end
lemma mirror_mirror₂ {α : Type} :
∀t : btree α, mirror (mirror t) = t
| btree.empty := by refl
| (btree.node a l r) :=
calc mirror (mirror (btree.node a l r))
= mirror (btree.node a (mirror r) (mirror l)) :
by refl
... = btree.node a (mirror (mirror l)) (mirror (mirror r)) :
by refl
... = btree.node a l (mirror (mirror r)) :
by rw mirror_mirror₂ l
... = btree.node a l r :
by rw mirror_mirror₂ r
lemma mirror_eq_empty_iff {α : Type} :
∀t : btree α, mirror t = btree.empty ↔ t = btree.empty
| btree.empty := by refl
| (btree.node _ _ _) := by simp [mirror]
/-! ## Dependent Inductive Types (**optional**) -/
#check vector
inductive vec (α : Type) : ℕ → Type
| nil {} : vec 0
| cons (a : α) {n : ℕ} (v : vec n) : vec (n + 1)
#check vec.nil
#check vec.cons
def list_of_vec {α : Type} : ∀{n : ℕ}, vec α n → list α
| _ vec.nil := []
| _ (vec.cons a v) := a :: list_of_vec v
def vec_of_list {α : Type} :
∀xs : list α, vec α (list.length xs)
| [] := vec.nil
| (x :: xs) := vec.cons x (vec_of_list xs)
lemma length_list_of_vec {α : Type} :
∀{n : ℕ} (v : vec α n), list.length (list_of_vec v) = n
| _ vec.nil := by refl
| _ (vec.cons a v) :=
by simp [list_of_vec, length_list_of_vec v]
end LoVe