This repository has been archived by the owner on Oct 14, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 216
/
Copy pathcoinductive_predicates.lean
625 lines (516 loc) · 22.6 KB
/
coinductive_predicates.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
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Johannes Hölzl (CMU)
-/
prelude
import init.meta.expr init.meta.tactic init.meta.constructor_tactic init.meta.attribute
import init.meta.interactive
namespace name
def last_string : name → string
| anonymous := "[anonymous]"
| (mk_string s _) := s
| (mk_numeral _ n) := last_string n
end name
namespace expr
open expr
meta def replace_with (e : expr) (s : expr) (s' : expr) : expr :=
e.replace $ λc d, if c = s then some (s'.lift_vars 0 d) else none
meta def local_binder_info : expr → binder_info
| (local_const x n bi t) := bi
| e := binder_info.default
meta def to_implicit_binder : expr → expr
| (local_const n₁ n₂ _ d) := local_const n₁ n₂ binder_info.implicit d
| (lam n _ d b) := lam n binder_info.implicit d b
| (pi n _ d b) := pi n binder_info.implicit d b
| e := e
meta def get_app_fn_args_aux : list expr → expr → expr × list expr
| r (app f a) := get_app_fn_args_aux (a::r) f
| r e := (e, r)
meta def get_app_fn_args : expr → expr × list expr :=
get_app_fn_args_aux []
end expr
namespace tactic
open level expr tactic
meta def mk_local_pisn : expr → nat → tactic (list expr × expr)
| (pi n bi d b) (c + 1) := do
p ← mk_local' n bi d,
(ps, r) ← mk_local_pisn (b.instantiate_var p) c,
return ((p :: ps), r)
| e 0 := return ([], e)
| _ _ := failed
meta def drop_pis : list expr → expr → tactic expr
| (list.cons v vs) (pi n bi d b) := do
t ← infer_type v,
guard (t =ₐ d),
drop_pis vs (b.instantiate_var v)
| [] e := return e
| _ _ := failed
meta def mk_theorem (n : name) (ls : list name) (t : expr) (e : expr) : declaration :=
declaration.thm n ls t (task.pure e)
meta def add_theorem_by (n : name) (ls : list name) (type : expr) (tac : tactic unit) : tactic expr := do
((), body) ← solve_aux type tac,
body ← instantiate_mvars body,
add_decl $ mk_theorem n ls type body,
return $ const n $ ls.map param
meta def is_assigned (m : expr): tactic bool :=
((get_assignment m >> return ff) <|> return tt)
meta def mk_exists_lst (args : list expr) (inner : expr) : tactic expr :=
args.mfoldr (λarg i:expr, do
t ← infer_type arg,
sort l ← infer_type t,
return $ if arg.occurs i ∨ l ≠ level.zero
then (const `Exists [l] : expr) t (i.lambdas [arg])
else (const `and [] : expr) t i)
inner
meta def mk_op_lst (op : expr) (empty : expr) : list expr → expr
| [] := empty
| [e] := e
| (e :: es) := op e $ mk_op_lst es
meta def mk_and_lst : list expr → expr := mk_op_lst `(and) `(true)
meta def mk_or_lst : list expr → expr := mk_op_lst `(or) `(false)
meta def elim_gen_prod : nat → expr → list expr → tactic (list expr × expr)
| 0 e hs := return (hs, e)
| (n + 1) e hs := do
[([h, h'], _)] ← induction e [],
elim_gen_prod n h' (hs ++ [h])
private meta def elim_gen_sum_aux : nat → expr → list expr → tactic (list expr × expr)
| 0 e hs := return (hs, e)
| (n + 1) e hs := do
[([h], _), ([h'], _)] ← induction e [],
swap,
elim_gen_sum_aux n h' (h::hs)
meta def elim_gen_sum (n : nat) (e : expr) : tactic (list expr) := do
(hs, h') ← elim_gen_sum_aux n e [],
gs ← get_goals,
set_goals $ (gs.taken (n+1)).reverse ++ gs.dropn (n+1),
return $ hs.reverse ++ [h']
end tactic
section
universe u
@[user_attribute]
def monotonicity := { user_attribute . name := `monotonicity, descr := "Monotonicity rules for predicates" }
lemma monotonicity.pi {α : Sort u} {p q : α → Prop} (h : ∀a, implies (p a) (q a)) :
implies (Πa, p a) (Πa, q a) :=
take h' a, h a (h' a)
lemma monotonicity.imp {p p' q q' : Prop} (h₁ : implies p' q') (h₂ : implies q p) :
implies (p → p') (q → q') :=
take h, h₁ ∘ h ∘ h₂
@[monotonicity]
lemma monotonicity.const (p : Prop) : implies p p := id
@[monotonicity]
lemma monotonicity.true (p : Prop) : implies p true := take _, trivial
@[monotonicity]
lemma monotonicity.false (p : Prop) : implies false p := false.elim
@[monotonicity]
lemma monotonicity.exists {α : Sort u} {p q : α → Prop} (h : ∀a, implies (p a) (q a)) :
implies (∃a, p a) (∃a, q a) :=
exists_imp_exists h
@[monotonicity]
lemma monotonicity.and {p p' q q' : Prop} (hp : implies p p') (hq : implies q q') :
implies (p ∧ q) (p' ∧ q') :=
and.imp hp hq
@[monotonicity]
lemma monotonicity.or {p p' q q' : Prop} (hp : implies p p') (hq : implies q q') :
implies (p ∨ q) (p' ∨ q') :=
or.imp hp hq
@[monotonicity]
lemma monotonicity.not {p q : Prop} (h : implies p q) :
implies (¬ q) (¬ p) :=
mt h
end
namespace tactic
open expr tactic
/- TODO: use backchaining -/
private meta def mono_aux (ns : list name) (hs : list expr) : tactic unit := do
intros,
(do
`(implies %%p %%q) ← target,
(do is_def_eq p q, applyc `monotone.const) <|>
(do
(expr.pi pn pbi pd pb) ← return p,
(expr.pi qn qbi qd qb) ← return q,
sort u ← infer_type pd,
(do is_def_eq pd qd,
let p' := expr.lam pn pbi pd pb,
let q' := expr.lam qn qbi qd qb,
apply $ (const `monotonicity.pi [u] : expr) pd p' q') <|>
(do guard $ u = level.zero ∧ is_arrow p ∧ is_arrow q,
let p' := pb.lower_vars 0 1,
let q' := qb.lower_vars 0 1,
apply $ (const `monotonicity.imp []: expr) pd p' qd q'))) <|>
first (hs.map $ λh, apply_core h { md := transparency.none } >> skip) <|>
first (ns.map $ λn, do c ← mk_const n, apply_core c { md := transparency.none }, skip),
all_goals mono_aux
meta def mono (e : expr) (hs : list expr) : tactic unit := do
t ← target,
t' ← infer_type e,
ns ← attribute.get_instances `monotonicity,
((), p) ← solve_aux `(implies %%t' %%t) (mono_aux ns hs),
exact (p e)
end tactic
/-
The coinductive predicate `pred`:
coinductive {u} pred (A) : a → Prop
| r : ∀A b, pred A p
where
`u` is a list of universe parameters
`A` is a list of global parameters
`pred` is a list predicates to be defined
`a` are the indices for each `pred`
`r` is a list of introduction rules for each `pred`
`b` is a list of parameters for each rule in `r` and `pred`
`p` is are the instances of `a` using `A` and `b`
`pred` is compiled to the following defintions:
inductive {u} pred.functional (A) ([pred'] : a → Prop) : a → Prop
| r : ∀a [f], b[pred/pred'] → pred.functional a [f] p
lemma {u} pred.functional.mono (A) ([pred₁] [pred₂] : a → Prop) [(h : ∀b, pred₁ b → pred₂ b)] :
∀p, pred.functional A pred₁ p → pred.functional A pred₂ p
def {u} pred_i (A) (a) : Prop :=
∃[pred'], (Λi, ∀a, pred_i a → pred_i.functional A [pred] a) ∧ pred'_i a
lemma {u} pred_i.corec_functional (A) [Λi, C_i : a_i → Prop] [Λi, h : ∀a, C_i a → pred_i.functional A C_i a] :
∀a, C_i a → pred_i A a
lemma {u} pred_i.destruct (A) (a) : pred A a → pred.functional A [pred A] a
lemma {u} pred_i.construct (A) : ∀a, pred_i.functional A [pred A] a → pred_i A a
lemma {u} pred_i.cases_on (A) (C : a → Prop) {a} (h : pred_i a) [Λi, ∀a, b → C p] → C a
lemma {u} pred_i.corec_on (A) [(C : a → Prop)] (a) (h : C_i a)
[Λi, h_i : ∀a, C_i a → [V j ∃b, a = p]] : pred_i A a
lemma {u} pred.r (A) (b) : pred_i A p
-/
namespace tactic
open level expr tactic
namespace add_coinductive_predicate
/- private -/ meta structure coind_rule : Type :=
(orig_nm : name)
(func_nm : name)
(type : expr)
(loc_type : expr)
(args : list expr)
(loc_args : list expr)
(concl : expr)
(insts : list expr)
/- private -/ meta structure coind_pred : Type :=
(u_names : list name)
(params : list expr)
(pd_name : name)
(type : expr)
(intros : list coind_rule)
(locals : list expr)
(f₁ f₂ : expr)
(u_f : level)
namespace coind_pred
meta def u_params (pd : coind_pred) : list level :=
pd.u_names.map param
meta def f₁_l (pd : coind_pred) : expr :=
pd.f₁.app_of_list pd.locals
meta def f₂_l (pd : coind_pred) : expr :=
pd.f₂.app_of_list pd.locals
meta def pred (pd : coind_pred) : expr :=
const pd.pd_name pd.u_params
meta def func (pd : coind_pred) : expr :=
const (pd.pd_name ++ "functional") pd.u_params
meta def func_g (pd : coind_pred) : expr :=
pd.func.app_of_list $ pd.params
meta def pred_g (pd : coind_pred) : expr :=
pd.pred.app_of_list $ pd.params
meta def impl_locals (pd : coind_pred) : list expr :=
pd.locals.map to_implicit_binder
meta def impl_params (pd : coind_pred) : list expr :=
pd.params.map to_implicit_binder
meta def le (pd : coind_pred) (f₁ f₂ : expr) : expr :=
(imp (f₁.app_of_list pd.locals) (f₂.app_of_list pd.locals)).pis pd.impl_locals
meta def corec_functional (pd : coind_pred) : expr :=
const (pd.pd_name ++ "corec_functional") pd.u_params
meta def mono (pd : coind_pred) : expr :=
const (pd.func.const_name ++ "mono") pd.u_params
meta def rec' (pd : coind_pred) : tactic expr :=
do let c := pd.func.const_name ++ "rec",
env ← get_env,
decl ← env.get c,
let num := decl.univ_params.length,
return (const c $ if num = pd.u_params.length then pd.u_params else level.zero :: pd.u_params)
-- ^^ `rec`'s universes are not always `u_params`, e.g. eq, wf, false
meta def construct (pd : coind_pred) : expr :=
const (pd.pd_name ++ "construct") pd.u_params
meta def destruct (pd : coind_pred) : expr :=
const (pd.pd_name ++ "destruct") pd.u_params
meta def add_theorem (pd : coind_pred) (n : name) (type : expr) (tac : tactic unit) : tactic expr :=
add_theorem_by n pd.u_names type tac
end coind_pred
end add_coinductive_predicate
open add_coinductive_predicate
/- compact_relation bs as_ps: Product a relation of the form:
R := λ as, ∃ bs, Λ_i a_i = p_i[bs]
This relation is user visible, so we compact it by removing each `b_j` where a `p_i = b_j`, and
hence `a_i = b_j`. We need to take care when there are `p_i` and `p_j` with `p_i = p_j = b_k`. -/
private meta def compact_relation :
list expr → list (expr × expr) → list expr × list (expr × expr)
| [] ps := ([], ps)
| (list.cons b bs) ps :=
match ps.span (λap:expr × expr, ¬ ap.2 =ₐ b) with
| (_, []) := let (bs, ps) := compact_relation bs ps in (b::bs, ps)
| (ps₁, list.cons (a, _) ps₂) := let i := a.instantiate_local b.local_uniq_name in
compact_relation (bs.map i) ((ps₁ ++ ps₂).map (λ⟨a, p⟩, (a, i p)))
end
meta def add_coinductive_predicate
(u_names : list name) (params : list expr) (preds : list $ expr × list expr) : command := do
let params_names := params.map local_pp_name,
let u_params := u_names.map param,
pre_info ← preds.mmap (λ⟨c, is⟩, do
(ls, t) ← mk_local_pis c.local_type,
(is_def_eq t `(Prop) <|>
fail (format! "Type of {c.local_pp_name} is not Prop. Currently only " ++
"coinductive predicates are supported.")),
let n := if preds.length = 1 then "" else "_" ++ c.local_pp_name.last_string,
f₁ ← mk_local_def (mk_simple_name $ "C" ++ n) c.local_type,
f₂ ← mk_local_def (mk_simple_name $ "C₂" ++ n) c.local_type,
return (ls, (f₁, f₂))),
let fs := pre_info.map prod.snd,
let fs₁ := fs.map prod.fst,
let fs₂ := fs.map prod.snd,
pds ← (preds.zip pre_info).mmap (λ⟨⟨c, is⟩, ls, f₁, f₂⟩, do
sort u_f ← infer_type f₁ >>= infer_type,
let pred_g := λc:expr, (const c.local_uniq_name u_params : expr).app_of_list params,
intros ← is.mmap (λi, do
(args, t') ← mk_local_pis i.local_type,
(name.mk_string sub p) ← return i.local_uniq_name,
let loc_args := args.map $ λe, (fs₁.zip preds).foldl (λ(e:expr) ⟨f, c, _⟩,
e.replace_with (pred_g c) f) e,
let t' := t'.replace_with (pred_g c) f₂,
return { tactic.add_coinductive_predicate.coind_rule .
orig_nm := i.local_uniq_name,
func_nm := (p ++ "functional") ++ sub,
type := i.local_type,
loc_type := t'.pis loc_args,
concl := t',
loc_args := loc_args,
args := args,
insts := t'.get_app_args }),
return { tactic.add_coinductive_predicate.coind_pred .
pd_name := c.local_uniq_name, type := c.local_type, f₁ := f₁, f₂ := f₂, u_f := u_f,
intros := intros, locals := ls, params := params, u_names := u_names }),
/- Introduce all functionals -/
pds.mmap' (λpd:coind_pred, do
let func_f₁ := pd.func_g.app_of_list $ fs₁,
let func_f₂ := pd.func_g.app_of_list $ fs₂,
/- Define functional for `pd` as inductive predicate -/
func_intros ← pd.intros.mmap (λr:coind_rule, do
let t := instantiate_local pd.f₂.local_uniq_name (pd.func_g.app_of_list fs₁) r.loc_type,
return (r.func_nm, r.orig_nm, t.pis $ params ++ fs₁)),
add_inductive pd.func.const_name u_names
(params.length + preds.length) (pd.type.pis $ params ++ fs₁) (func_intros.map $ λ⟨t, _, r⟩, (t, r)),
/- Prove monotonicity rule -/
mono_params ← pds.mmap (λpd, do
h ← mk_local_def `h $ pd.le pd.f₁ pd.f₂,
return [pd.f₁, pd.f₂, h]),
pd.add_theorem (pd.func.const_name ++ "mono")
((pd.le func_f₁ func_f₂).pis $ params ++ mono_params.join)
(do
ps ← intro_lst $ params.map expr.local_pp_name,
fs ← pds.mmap (λpd, do
[f₁, f₂, h] ← intro_lst [pd.f₁.local_pp_name, pd.f₂.local_pp_name, `h],
-- the type of h' reduces to h
let h' := local_const h.local_uniq_name h.local_pp_name h.local_binder_info $
(((const `implies [] : expr)
(f₁.app_of_list pd.locals) (f₂.app_of_list pd.locals)).pis pd.locals).instantiate_locals $
(ps.zip params).map $ λ⟨lv, p⟩, (p.local_uniq_name, lv),
return (f₂, h')),
m ← pd.rec',
apply $ m.app_of_list ps, -- somehow `induction` / `cases` doesn't work?
func_intros.mmap' (λ⟨n, pp_n, t⟩, solve1 $ do
bs ← intros,
ms ← apply_core ((const n u_params).app_of_list $ ps ++ fs.map prod.fst) { all := tt },
params ← (ms.zip bs).enum.mfilter (λ⟨n, m, d⟩, is_assigned m),
params.mmap' (λ⟨n, m, d⟩, mono d (fs.map prod.snd) <|>
fail format! "failed to prove montonoicity of {n+1}. parameter of intro-rule {pp_n}")))),
pds.mmap' (λpd, do
let func_f := λpd:coind_pred, pd.func_g.app_of_list $ pds.map coind_pred.f₁,
/- define final predicate -/
pred_body ← mk_exists_lst (pds.map coind_pred.f₁) $
mk_and_lst $ (pds.map $ λpd, pd.le pd.f₁ (func_f pd)) ++ [pd.f₁.app_of_list pd.locals],
add_decl $ mk_definition pd.pd_name u_names (pd.type.pis $ params) $
pred_body.lambdas $ params ++ pd.locals,
/- prove `corec_functional` rule -/
hs ← pds.mmap $ λpd:coind_pred, mk_local_def `hc $ pd.le pd.f₁ (func_f pd),
pd.add_theorem (pd.pred.const_name ++ "corec_functional")
((pd.le pd.f₁ pd.pred_g).pis $ params ++ fs₁ ++ hs)
(do
intro_lst $ params.map local_pp_name,
fs ← intro_lst $ fs₁.map local_pp_name,
hs ← intro_lst $ hs.map local_pp_name,
ls ← intro_lst $ pd.locals.map local_pp_name,
h ← intro `h,
whnf_target,
fs.mmap' existsi,
hs.mmap' (λf, constructor >> exact f),
exact h)),
let func_f := λpd : coind_pred, pd.func_g.app_of_list $ pds.map coind_pred.pred_g,
/- prove `destruct` rules -/
pds.enum.mmap' (λ⟨n, pd⟩, do
let destruct := pd.le pd.pred_g (func_f pd),
pd.add_theorem (pd.pred.const_name ++ "destruct") (destruct.pis params) (do
ps ← intro_lst $ params.map local_pp_name,
ls ← intro_lst $ pd.locals.map local_pp_name,
h ← intro `h,
(fs, h) ← elim_gen_prod pds.length h [],
(hs, h) ← elim_gen_prod pds.length h [],
apply $ pd.mono.app_of_list ps,
pds.mmap' (λpd:coind_pred, focus1 $ do
apply $ pd.corec_functional,
focus $ hs.map exact),
some h' ← return $ hs.nth n,
apply h',
exact h)),
/- prove `construct` rules -/
pds.mmap' (λpd,
pd.add_theorem (pd.pred.const_name ++ "construct")
((pd.le (func_f pd) pd.pred_g).pis params) (do
ps ← intro_lst $ params.map local_pp_name,
let func_pred_g := λpd:coind_pred,
pd.func.app_of_list $ ps ++ pds.map (λpd:coind_pred, pd.pred.app_of_list ps),
apply $ pd.corec_functional.app_of_list $ ps ++ pds.map func_pred_g,
pds.mmap' (λpd:coind_pred, solve1 $ do
apply $ pd.mono.app_of_list ps,
pds.mmap' (λpd, solve1 $ apply $ pd.destruct.app_of_list ps)))),
/- prove `cases_on` rules -/
pds.mmap' (λpd, do
let C := pd.f₁.to_implicit_binder,
h ← mk_local_def `h $ pd.pred_g.app_of_list pd.locals,
rules ← pd.intros.mmap (λr:coind_rule, do
mk_local_def (mk_simple_name r.orig_nm.last_string) $ (C.app_of_list r.insts).pis r.args),
cases_on ← pd.add_theorem (pd.pred.const_name ++ "cases_on")
((C.app_of_list pd.locals).pis $ params ++ [C] ++ pd.impl_locals ++ [h] ++ rules)
(do
ps ← intro_lst $ params.map local_pp_name,
C ← intro `C,
ls ← intro_lst $ pd.locals.map local_pp_name,
h ← intro `h,
rules ← intro_lst $ rules.map local_pp_name,
func_rec ← pd.rec',
apply $ func_rec.app_of_list $ ps ++ pds.map (λpd, pd.pred.app_of_list ps) ++ [C] ++ rules,
apply $ pd.destruct,
exact h),
set_basic_attribute `elab_as_eliminator cases_on.const_name),
/- prove `corec_on` rules -/
pds.mmap' (λpd, do
rules ← pds.mmap (λpd, do
intros ← pd.intros.mmap (λr, do
let (bs, eqs) := compact_relation r.loc_args $ pd.locals.zip r.insts,
eqs ← eqs.mmap (λ⟨l, i⟩, do
sort u ← infer_type l.local_type,
return $ (const `eq [u] : expr) l.local_type i l),
match bs, eqs with
| [], [] := return ((0, 0), mk_true)
| _, [] := prod.mk (bs.length, 0) <$> mk_exists_lst bs.init bs.ilast.local_type
| _, _ := prod.mk (bs.length, eqs.length) <$> mk_exists_lst bs (mk_and_lst eqs)
end),
let shape := intros.map prod.fst,
let intros := intros.map prod.snd,
prod.mk shape <$>
mk_local_def (mk_simple_name $ "h_" ++ pd.pd_name.last_string)
(((pd.f₁.app_of_list pd.locals).imp (mk_or_lst intros)).pis pd.locals)),
let shape := rules.map prod.fst,
let rules := rules.map prod.snd,
h ← mk_local_def `h $ pd.f₁.app_of_list pd.locals,
pd.add_theorem (pd.pred.const_name ++ "corec_on")
((pd.pred_g.app_of_list $ pd.locals).pis $ params ++ fs₁ ++ pd.impl_locals ++ [h] ++ rules)
(do
ps ← intro_lst $ params.map local_pp_name,
fs ← intro_lst $ fs₁.map local_pp_name,
ls ← intro_lst $ pd.locals.map local_pp_name,
h ← intro `h,
rules ← intro_lst $ rules.map local_pp_name,
apply $ pd.corec_functional.app_of_list $ ps ++ fs,
(pds.zip $ rules.zip shape).mmap (λ⟨pd, hr, s⟩, solve1 $ do
ls ← intro_lst $ pd.locals.map local_pp_name,
h' ← intro `h,
h' ← note `h' none $ hr.app_of_list ls h',
match s.length with
| 0 := induction h' >> skip -- h' : false
| (n+1) := do
hs ← elim_gen_sum n h',
(hs.zip $ pd.intros.zip s).mmap' (λ⟨h, r, n_bs, n_eqs⟩, solve1 $ do
(as, h) ← elim_gen_prod (n_bs - (if n_eqs = 0 then 1 else 0)) h [],
if n_eqs > 0 then do
(eqs, eq') ← elim_gen_prod (n_eqs - 1) h [],
(eqs ++ [eq']).mmap' subst
else skip,
apply ((const r.func_nm u_params).app_of_list $ ps ++ fs),
repeat assumption)
end),
exact h)),
/- prove constructors -/
pds.mmap' (λpd, pd.intros.mmap' (λr,
pd.add_theorem r.orig_nm (r.type.pis params) $ do
ps ← intro_lst $ params.map local_pp_name,
bs ← intros,
apply $ pd.construct,
exact $ (const r.func_nm u_params).app_of_list $ ps ++ pds.map (λpd, pd.pred.app_of_list ps) ++ bs)),
pds.mmap' (λpd:coind_pred, set_basic_attribute `irreducible pd.pd_name),
try triv -- we setup a trivial goal for the tactic framework
open lean.parser
open interactive
@[user_command]
meta def coinductive_predicate (meta_info : decl_meta_info) (_ : parse $ tk "coinductive") : lean.parser unit := do
decl ← inductive_decl.parse meta_info,
add_coinductive_predicate decl.u_names decl.params $ decl.decls.map $ λ d, (d.sig, d.intros),
decl.decls.mfor' $ λ d, do {
get_env >>= λ env, set_env $ env.add_namespace d.name,
meta_info.attrs.apply d.name,
d.attrs.apply d.name,
some doc_string ← pure meta_info.doc_string | skip,
add_doc_string d.name doc_string
}
/-- Prepares coinduction proofs. This tactic constructs the coinduction invariant from
the quantifiers in the current goal.
Current version: do not support mutual inductive rules (i.e. only a since C -/
meta def coinduction (rule : expr) : tactic unit := focus1 $
do
ctxts' ← intros,
-- TODO: why do we need to fix the type here?
ctxts ← ctxts'.mmap (λv,
local_const v.local_uniq_name v.local_pp_name v.local_binder_info <$> infer_type v),
mvars ← apply_core rule { approx := ff, all := tt },
-- analyse relation
g ← list.head <$> get_goals,
(list.cons _ m_is) ← return $ mvars.drop_while (λv, v ≠ g),
tgt ← target,
(is, ty) ← mk_local_pis tgt,
-- construct coinduction predicate
(bs, eqs) ← compact_relation ctxts <$>
((is.zip m_is).mmap (λ⟨i, m⟩, prod.mk i <$> instantiate_mvars m)),
solve1 (do
eqs ← mk_and_lst <$> eqs.mmap (λ⟨i, m⟩, mk_app `eq [m, i] >>= instantiate_mvars),
rel ← mk_exists_lst bs eqs,
exact (rel.lambdas is)),
-- prove predicate
solve1 (do
target >>= instantiate_mvars >>= change, -- TODO: bug in existsi & constructor when mvars in hyptohesis
bs.mmap existsi,
repeat constructor),
-- clean up remaining coinduction steps
all_goals (do
ctxts'.reverse.mmap clear,
target >>= instantiate_mvars >>= change, -- TODO: bug in subst when mvars in hyptohesis
is ← intro_lst $ is.map expr.local_pp_name,
h ← intro1,
(_, h) ← elim_gen_prod (bs.length - (if eqs.length = 0 then 1 else 0)) h [],
(match eqs with
| [] := clear h
| (e::eqs) := do
(hs, h) ← elim_gen_prod eqs.length h [],
(h::(hs.reverse)).mmap' subst
end))
namespace interactive
open interactive interactive.types expr lean.parser
local postfix `?`:9001 := optional
local postfix *:9001 := many
meta def coinduction (corec_name : parse ident)
(revert : parse $ (tk "generalizing" *> ident*)?) : tactic unit := do
rule ← mk_const corec_name,
locals ← mmap tactic.get_local $ revert.get_or_else [],
revert_lst locals,
tactic.coinduction rule,
skip
end interactive
end tactic