-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathCustomSimp.lean
More file actions
674 lines (589 loc) · 23.4 KB
/
CustomSimp.lean
File metadata and controls
674 lines (589 loc) · 23.4 KB
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
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
import Batteries
import Lean
import Qq
import TutorialAux.Init -- for Sections (5, 7)
/-!
# Tutorial: Writing `simp` from scratch
Content
(1) What `simp` does on proof term level?
(2) Filling implicit arguments
(3) Custom SimpResult datastructure
(4) Basic `simp` implementation
(5) Debugging with traces
(6) Implementing `simp` inside binders
(7) Collecting tagged lemmas
-/
open Lean Meta Elab Tactic Qq
/-
## (1) What `simp` does on proof term level
Simp doesn't do all its equal rewrites in one go like `rw`.
Instead, it recursively dives into the term, and when
it combines two branches in which both terms got
updated, it uses
-/
#check congr
theorem simp_example (a b c : Nat) (f : Nat → Nat) (p : Nat → Prop)
(h_eq : ∀ x : Nat, f x = x) (h_finish : p (a + c + b + c + a + c)) :
p (f a + c + f b + c + f a + c) := by
simp only [h_eq]
exact h_finish
#print simp_example
/-
This is much more messy than rw's but after prettification,
you could get the following proof term.
-/
example (a b c : Nat) (f : Nat → Nat) (p : Nat → Prop)
(h_eq : ∀ x : Nat, f x = x) (h_finish : p (a + c + b + c + a + c)) :
p (f a + c + f b + c + f a + c) :=
Eq.mpr
(congrArg (fun X => p (X + c))
(congr
(congrArg (fun X => (X + c + ·))
(congr
(congrArg
(fun X => (X + c + ·)) (h_eq a : f a = a)
: (f a + c + ·) = (a + c + ·)
)
(h_eq b : f b = b)
: f a + c + f b = a + c + b)
: (f a + c + f b + c + ·) = (a + c + b + c + ·)
)
(h_eq a : f a = a)
: f a + c + f b + c + f a = a + c + b + c + a)
: p (f a + c + f b + c + f a + c) = p (a + c + b + c + a + c)
)
h_finish
/-
You can notice that
* The expression is build gradually, not in one go.
* We actually run `h_eq a` twice in the proof term, because
we are rewriting with it on two places, contrary to `rw`.
You might think that the `simp` approach is the more
flexible / general but there are cases where `simp` doesn't work,
and `rw` succeeds. This is due to type dependency issues such as in
the following example.
-/
example (a b : Nat) (h_eq : a = b) (p : ∀ n : Nat, Fin n → Prop)
(h : ∀ y : Fin b, p b y) : ∀ x : Fin a, p a x := by
try simp only [h_eq] -- simp cannot do the rewrite
rw [h_eq] -- rw can, because it rewrites at several places at once
exact h
/-
## (2) Filling implicit arguments
When we were applying `congrArg` and `Eq.mpr` in `rw`, we were explicitly filling
the universe levels, and implicit argument. Already there, it was a bit annoying,
and with `simp`, we will need to build much more terms. Fortunatelly, there are
ways to avoid this work.
-/
example (a b c : Nat) (pf1 : a = b) (pf2 : b = c) : True := by
-- we would like to emulate calling
have pf3 : a = c := Eq.trans pf1 pf2
-- but the full expression we want to build is
have pf3' := @Eq.trans.{1} Nat a b c pf1 pf2
-- on tactic level, we have several ways to build it
run_tacq
-- (a) low-level constructing the term, we have to provide all the arguments
let lowlev := mkApp6 ((mkConst ``Eq.trans [1])) (mkConst ``Nat) a b c pf1 pf2
logInfo m!"lowlev = {lowlev}"
-- (b) using `Qq`
let pfQ : Q($a = $c) := q(Eq.trans $pf1 $pf2)
logInfo m!"pfq = {pfQ}"
-- (c) using `mkAppM`
let pfAppM ← mkAppM ``Eq.trans #[pf1, pf2]
logInfo m!"pfAppM = {pfAppM}"
-- (d) using `mkEqTrans` -- common functions already have their meta-versions
let pfEqT ← mkEqTrans pf1 pf2
logInfo m!"pfEqT = {pfEqT}"
trivial
/-
The crucial difference between `Qq` and `mkAppM` is that `Qq` does the type inference
in compile-time whereas `mkAppM` does it in runtime. Let us implement both as functions.
-/
def buildTransQ {u : Level} {α : Q(Sort u)} {a b c : Q($α)}
(pf1 : Q($a = $b)) (pf2 : Q($b = $c)) : Q($a = $c) :=
q(Eq.trans $pf1 $pf2)
def buildTransM (pf1 pf2 : Expr) : MetaM Expr :=
mkAppM ``Eq.trans #[pf1, pf2]
/-
Notice that `buildTransM` needs to run in MetaM -- only that way it will have enough
data to correctly infer the types of the given expressions, and hence the correct
implicit arguments.
On the other hand, `buildTransQ` doesn't need MetaM. It needs to get all the data
that makes `pf1` and `pf2` correctly annotated: `u α a b c`.
Even if these arguments are passed implicitly (so the meta-programmer doesn't
have to write them), they are indeed passed, and play a crucial role in runtime
to build the resulting term.
We will use `mkAppM` to finish the implementation of `simp`.
Using `Qq` requires taking care of the annotations which can become
a bit finicky (doable but perhaps not as well suited for a tutorial).
On the other hand, we encourage you to try building terms with `Qq` too,
and see what suits your needs better.
-/
/-
### Exercise
Define a function `myCalculation` which takes two numbers
`a b : Nat / Int / Rat`, and builds `a + b * a`
automatically infering their type, and the appropriate typeclass
instance. Try:
* specific functions, you can find them ;-)
* `mkAppM`
* `Qq`
Tip: when editing the middle of the file, it might help to prevent
Lean to from recompiling the rest of the file by typing `#exit`
at the end of the section.
Just don't forget to delete it when you move on ;-).
Hint for Qq: Qq can infer the instance too but you cannot (!!)
pass it implicitly as `[Q(HAdd $α $α $α)]` (as you might have discovered).
So first, try to pass `Q(HAdd $α $α $α)` as an explicit argument,
and insert `q(inferInstance)` to the call (analogously multiplication).
Later, you can do a trick with a default argument filled with
an `exact` tactic -- you need to fill it with a tactic to postpone
the type inference.
-/
def myCalculation (a b : Expr) : MetaM Expr := do
return a
-- preliminary, you will have to change the type signature to some `Q(...)`
def myCalcQ2 (a : Expr) (b : Expr) : Expr := a
example (a b : Nat) (c d : Int) (e f : Rat) : True := by
run_tacq
let ab ← myCalculation a b
-- let ab := myCalcQ a b
let cd ← myCalculation c d
-- let cd := myCalcQ c d
let ef ← myCalculation e f
-- let ef := myCalcQ e f
logInfo m!"ab := {ab}, cd := {cd}, ef := {ef}"
unless ← isDefEq ab q($a + $b*$a) do throwError "ab := {ab} != a+b*a"
unless ← isDefEq cd q($c + $d*$c) do throwError "cd := {cd} != c+d*c"
unless ← isDefEq ef q($e + $f*$e) do throwError "ef := {ef} != e+f*e"
trivial
/-
## (3) Custom SimpResult datastructure
First, we define a structure capturing the result.
The output of a simplification run on `a` is a new expression `expr`
of the same type, and a proof `pf : a = expr`. Sometimes, `simp` doesn't
perform any simplification, in that case, we allow `pf` to be `none`
(although we could also close it using `rfl`)
-/
structure SimpResult where
expr : Expr
pf? : Option Expr
-- Note that the library Simp also has very similar Result structure, both
-- in basic library, and with Qq hints.
#check Simp.Result
#check Simp.ResultQ
/-
Let's prepare some ways to combine results together. There are not too much
iteresting ideas, going on so you can skip to the next section.
-/
def SimpResult.empty (e : Expr) : SimpResult := {expr := e, pf? := none}
#check Eq.refl
/-- (tutorial function)
Gets the proof, possibly building `rfl` if it was none. -/
def SimpResult.getProof (r : SimpResult) : MetaM Expr :=
match r.pf? with
| some pf => pure pf
| none => mkAppM ``Eq.refl #[r.expr]
-- see also `mkEqRefl`
#check Eq.trans
/-- (tutorial function)
Combines two `SimpResults` using `Eq.trans` -/
def SimpResult.trans (r1 r2 : SimpResult) : MetaM SimpResult := do
match r1.pf? with
| none => return r2
| some pf1 => match r2.pf? with
| none => return {expr := r2.expr, pf? := some pf1}
| some pf2 =>
let pf ← mkAppM ``Eq.trans #[pf1, pf2]
return {expr := r2.expr, pf? := some pf}
#check congr
#check congrArg
#check congrFun
/-- (tutorial function)
Combines `f = g`, and `a = b` into `f a = g b` using `congr` -/
def SimpResult.app (rf rArg : SimpResult) : MetaM SimpResult := do
let expr := mkApp rf.expr rArg.expr
match rf.pf? with
| none => match rArg.pf? with
| none => return .empty expr
| some pfArg => return {expr := expr, pf? := ← mkAppM ``congrArg #[rf.expr, pfArg]}
| some pff => match rArg.pf? with
| none => return {expr := expr, pf? := ← mkAppM ``congrFun #[pff, rArg.expr]}
| some pfArg => return {expr := expr, pf? := ← mkAppM ``congr #[pff, pfArg]}
-- see also `mkCongr`, `mkCongrArg`, `mkCongrFun`
/-
## (4) Basic `simp` implementation
We split the simplification algorithm into two functions.
* Function `simProcBasic (...) a` only tries to make a single rewrite step
of the root of `a` to `b` and build a proof of `a = b`.
* Recursive function `simpRec0` gets a specific root-rewriting function as
an argument (currently `simProcBasic`), tries to apply it anywhere inside
the term, and returns the proof of equality in the same format.
-/
/-- (tutorial function)
Root-rewriting function analogous to `simp only`.
It gets a list of equalities `rules`, and tries to find a matching
rule `rule : a = b` that matches the given expression `a`.
The `rule` also can be quantified.
On the other hand, we do not look to subexpressions of `a`,
and only try to perform the step once. (that is the job of `simpRec`)
-/
def simProcBasic (rules : List Expr) (a : Expr) :
MetaM SimpResult :=
withNewMCtxDepth do
for rule in rules do
let eq ← whnf (← inferType rule)
let (mvars, _, eq) ← forallMetaTelescope eq -- turn quantifiers into mvars
-- let pf := mkAppN eq mvars -- build the proof term
let some (_, ar, br) := eq.app3? ``Eq | throwError "Not an equality: {rule} : {eq}"
if ← withTransparency .reducible (isDefEq a ar) then
let br ← instantiateMVars br
let pf := mkAppN rule (← mvars.mapM instantiateMVars)
return {expr := br, pf? := some pf}
return .empty a
-- Test!
example (a b : Nat) (f : Nat → Nat) (h : ∀ x, f x = x) : True := by
run_tacq
let e := q($f ($a + $b))
let res ← simProcBasic [h] e
logInfo m!"Simplify ({e}) to: {res.expr}"
logInfo m!"Proof term: {res.pf?}"
trivial
/-- (tutorial function)
Recursive rewrite inside a term.
-/
partial -- simplification could repeat indefinitely, `partial` skips termination check
def simpRec0 (simProc : Expr → MetaM SimpResult)
(a : Expr) : MetaM SimpResult := do
let an ← whnfR a
let res ← match an with -- try to simplify the inside of the expression
| .app f arg =>
let rf ← simpRec0 simProc f
let rArg ← simpRec0 simProc arg
rf.app rArg
| _ => pure <| .empty an
let resProc ← simProc res.expr -- This is the step actually doing the rewrite!
if resProc.pf?.isNone then
return res
-- if rewrite was successful, we repeat in case there is more to do
let res ← res.trans resProc
let resRepeat ← simpRec0 simProc res.expr
res.trans resRepeat
-- Test rewriting inside the term.
example (a b : Nat) (f : Nat → Nat) (h : ∀ x, f x = x)
(h_test : 2 * f a = f b * 3): True := by
run_tacq
let res ← simpRec0 (simProcBasic [h]) h_test.ty
logInfo m!"Simplify ({h_test.ty}) to: {res.expr}"
logInfo m!"Proof term: {res.pf?}"
trivial
/-
### Using `simp` infrastructure
The library `simp` is similarly modular as ours, with a few extra features. Often,
we don't have to implement the entire `simpRec` from scratch. Let us show how
to perform the same simplification using our own `simProcBasic`
but library's `simp` instead of our own `simpRec`. Notice that
`simp` can simplify inside a binder.
-/
#print Simp.Simproc
#check Simp.main
#check applySimpResultToTarget
example (a b c : Nat) (p : Nat → Nat → Prop)
(h₁ : a = b) (h₂ : b = c) (finish : ∀ x, p x c → p x c) :
(∀ x, p x a → p x a) := by
-- simp only [h₁, h₂]
run_tacq goal =>
let ctx : Simp.Context ← Simp.mkContext -- optionally lemmas & extra congruence lemmas
let method : Simp.Simproc := fun e : Expr => do
let res ← simProcBasic [h₁, h₂] e
-- Very straightforward translation from our `SimpResult` to the library
-- `Simp.Step`. In general, `Simp.Step` can guard the repetition inside
-- `simp` by deciding on `done` / `visit` / `continue`
if res.pf?.isNone then return Simp.Step.continue
else return Simp.Step.visit { expr := res.expr, proof? := res.pf? }
let methods : Simp.Methods := { pre := method }
let (res, _stats) ← Simp.main goal.ty ctx (methods := methods)
logInfo m!"Simplify ({goal.ty}) to: {res.expr}"
logInfo m!"Proof term: {res.proof?}"
-- we could match on `res.proof?` but we can also use library function
let mvarIdNew ← applySimpResultToTarget goal.mvarId! goal.ty res
if mvarIdNew == goal.mvarId! then throwError "simp made no progress"
replaceMainGoal [mvarIdNew]
exact finish
/-
## (5) Debugging with traces
For basic debug prints, we can use `logInfo`, however:
* we have to delete it when we want to hide the debug,
* it can get messy when we print too many messages.
Lean offers a system of traces for debugging purposes. We can
display traces for many standard Lean functions. For example,
`whnf` sometimes calls `trace[trace.Meta.whnf]`, so let us look
at the debug prints.
-/
run_meta
let e1 : Q(Nat) := q(let x := 3; x^2)
-- setting the option manually, otherwise we would get much more
-- traces
let e2 ← withOptions (fun opt => opt.setBool `trace.Meta.whnf true) do
whnf e1
logInfo m!"logInfo e2: {e2}"
-- We can also do this ourself
run_meta
withOptions (fun opt => opt.setBool `trace.WhateverName true) do
trace[WhateverName] m!"Hello trace"
/-
But usually, we want to turn the trace on using `set_option`.
Such option must be defined in another imported file. Here,
we registered `MyTrace` in `TutorialAux/Init.Lean`. You get
get to the definition by ctrl-clicking on `trace.MyTrace`
in `set_option`.
-/
set_option trace.MyTrace true in -- try to comment out this line
run_meta
trace[MyTrace] m!"Hello trace"
-- as with any other option, we can also set the option globally with
set_option trace.MyTrace true
-- or unset
set_option trace.MyTrace false
/-
### Tree Structure
The traces can be packed into a tree with
-/
#check withTraceNode
#check withTraceNode'
#check withTraceNodeBefore
-- for example
set_option trace.MyTrace true in
run_meta
trace[MyTrace] "Start"
let res? : Option Nat ← withTraceNodeBefore `MyTrace (pure "Pack 1") do
trace[MyTrace] "Start inside"
let a : Nat ← withTraceNode' `MyTrace do
trace[MyTrace] "Double inside"
pure (40, m!"obtaining 40")
trace[MyTrace] "Subresult {a}"
pure (some a) -- also try one of the following lines instead
-- return none
-- throwError "Crashed"
trace[MyTrace] "Result is {res?}"
/-
Notice that `withTraceNodeBefore` calculates the packed message
at the beginning but the emoticon at the end. This emoticon depends
on the calculated value, which is why we need `Option` or `Bool`
as a return type. Alternatively, we can define `ExceptToEmoji` on
a custom data type.
-/
instance simpResultToEmoji : ExceptToEmoji Exception SimpResult where
toEmoji x := exceptOptionEmoji (x.map SimpResult.pf?)
set_option trace.MyTrace true in
run_meta
let _res : SimpResult ← withTraceNodeBefore `MyTrace (pure "Pack") do
let expr := q(2)
let pf : Q(1 + 1 = 2) := q(rfl)
trace[MyTrace] "expr := {expr}"
trace[MyTrace] "pf := {pf}"
pure ⟨expr, some pf⟩
-- pure ⟨expr, none⟩
-- throwError "oops"
/-- (tutorial function) Trace SimpResult if nonempty -/
def SimpResult.trace (res : SimpResult) : MetaM Unit := do
match res.pf? with
| some pf =>
trace[MyTrace] "=> {res.expr}"
withTraceNode' `MyTrace do
trace[MyTrace] pf
pure ((), "(proof term)")
| _ => pure ()
/-
## (6) Implementing `simp` inside binders
Here, we look how to implement `simp` inside binders on our own without using
library's `Simp.main`. Let's look again how library's simp does it.
-/
theorem simp_example2 (a b c : Nat) (p : Nat → Nat → Prop)
(h₁ : a = b) (h₂ : b = c) (finish : ∀ x, p x c → p x c) :
(∀ x, p x a → p x a) := by
simp only [h₁, h₂]
exact finish
#print simp_example2
-- The proof term uses special theorems digging into forall & implication:
#check implies_congr
#check forall_congr
-- Let's start with using these lemmas to build `SimpResult`
#check implies_congr
/-- (tutorial function)
from `a = b`, `c = d` proves `a → c = b → d` using `implies_congr` on `SimpResult`s -/
def SimpResult.impl (r1 r2 : SimpResult) : MetaM SimpResult := do
let expr := mkForall Name.anonymous BinderInfo.default r1.expr r2.expr
if r1.pf?.isNone && r2.pf?.isNone then return .empty expr
return {expr := expr, pf? := some <|
← mkAppM ``implies_congr #[← r1.getProof, ← r2.getProof]
}
-- see also `mkImpCongr`
#check forall_congr
/-- (tutorial function)
Gets a proof of `p fv = q fv` where `fv` is a free variable, and `p fv` is a `Prop`.
and builds a proof of `(∀ x, p x) = (∀ x, q x)` using forall_congr.
-/
def SimpResult.forall (fv : Expr) (r : SimpResult) :
MetaM SimpResult := do
let expr ← mkForallFVars #[fv] r.expr -- bind `fv` into forall `expr := ∀ x, q x`
match r.pf? with
| none => return .empty expr
| some pf =>
let pf ← mkLambdaFVars #[fv] pf -- bind `fv` into lambda, `pf : ∀ x, p x = q x`
let pf ← mkAppM ``forall_congr #[pf] -- `pf : (∀ x, p x) = (∀ x, q x)`
return {expr := expr, pf? := some pf}
-- see also `mkForallCongr`
-- Now, we need to update `simpRec0` to use them.
/-- (tutorial function)
Recursive simplification inside a term with implications.
-/
partial
def simpRec (simProc : Expr → MetaM SimpResult)
(a : Expr) : MetaM SimpResult := do
trace[MyTrace] "Simplifying {a}"
let an ← whnfR a
let res ← -- try to simplify the inside of the expression
withTraceNodeBefore `MyTrace (pure "inside") do
match an with
| .app f arg =>
let rf ← simpRec simProc f
let rArg ← simpRec simProc arg
rf.app rArg
| .forallE _name t body _bi =>
if !body.hasLooseBVars then -- not a dependent implication -> impl_congr
let rt ← simpRec simProc t
let rBody ← simpRec simProc body
rt.impl rBody
else -- dependent implication -> forall_congr
if !(← isProp an) then -- forall_congr only works on a Prop
pure <| .empty an
else
-- In general, `forallTelescope` unpacks forall a bit like `intros` creating
-- new free variables and putting them into the local context within
-- the inner do scope. Here we want just a single step, hence
-- `forallBoundedTelescope` with `maxFVars? := some 1`
forallBoundedTelescope an (some 1) (fun fvars body => do
-- this `body` has a fvar, contrary to the bare `body`
-- we got by unpacking the `Expr` which uses a `bvar`
let res ← simpRec simProc body
res.forall fvars[0]!
)
| _ => pure <| .empty an
res.trace
let resProc ←
withTraceNodeBefore `MyTrace (pure "root") do
simProc res.expr -- This is the step actually doing the rewrite!
resProc.trace
if resProc.pf?.isNone then
return res
-- if rewrite was successful, we repeat in case there is more to do
let res ← res.trans resProc
let resRepeat ← simpRec simProc res.expr
res.trans resRepeat
/--
(tutorial function) Simplifies the goal with a `simProc`
-/
def mySimpGoal (simProc : Expr → MetaM SimpResult) : TacticM Unit := do
let goal ← getMainGoal
goal.withContext do
let target ← goal.getType
let res ←
withTraceNodeBefore `MyTrace (pure "Build simp equation") do
simpRec simProc target -- run simplification
match res.pf? with
| none => throwError "mySimpGoal made no progress"
| some pf =>
trace[MyTrace] target
res.trace
-- use Eq.mpr as with `rw`, this time using `mkAppM`
let m ← mkFreshExprSyntheticOpaqueMVar res.expr
goal.assign <| ← mkAppM ``Eq.mpr #[pf, m]
replaceMainGoal [m.mvarId!]
-- Test!
set_option trace.MyTrace true in
example (a b c : Nat) (p : Nat → Nat → Prop)
(h₁ : a = b) (h₂ : b = c) (finish : ∀ x, p x c → p x c) :
(∀ x, p x a → p x a) := by
-- simp only [h₁, h₂]
run_tacq mySimpGoal (simProcBasic [h₁, h₂])
exact finish
/-
Exercise: The implementation above works with ∀ but not with ∃.
Update the function `simpRec` so that the following proof passes.
-/
set_option trace.MyTrace true in
example (a b : Nat) (p : Nat → Nat → Prop)
(h : a = b) (finish : ∃ x, p x b) :
(∃ x, p x a) := by
-- simp only [h]
run_tacq mySimpGoal (simProcBasic [h])
exact finish
/-
## (7) Collecting tagged lemmas
The standard `simp` doesn't need to be given the lemmas each usage, it uses
all the lemmas tagged with `@[simp]`. Let us show an example to introduce
a custom attribute `my_tag`.
This requires two `initialize` steps.
* Creating an environment extension `myExt` using
`registerSimpleScopedEnvExtension`. This will store the array of tagged theorems.
* Registering the attribute `my_tag` itself using `registerBuiltinAttribute`.
Little annoyingly, every `initialize` must be done in another file than
where it is tested (i.e. where we tag theorems with the attribute).
Look at the file `TutorialAux/Tag.lean` where these two steps are done.
-/
#check myExt
-- Since we imported `TutorialAux/Tag.lean`, we can tag some theorems with `my_tag`
@[my_tag] theorem add_assoc_rev (a b c : Nat) :
a + (b + c) = (a + b) + c := (Nat.add_assoc a b c).symm
@[my_tag] theorem two_mul_rev (a : Nat) : a + a = 2 * a := a.two_mul.symm
-- even some theorems are defined without `@[my_tag]`
theorem two_mul_rev' (a b : Nat) : (b + a) + a = b + 2 * a := by omega
theorem two_two_four (a : Nat) : 2 * (2 * a) = 4 * a := by omega
-- we can add the attribute later
attribute [my_tag] two_mul_rev'
attribute [my_tag] two_two_four
-- look at the theorems stored at `customExt`
run_meta
let state := myExt.getState (← getEnv)
for (e,t) in state do
logInfo m!"{e} : {t}"
/-
Now, we have all the ingredients to an alternative to `simProcBasic`.
This will use tagged theorems (tagged when the is run), and allow
quantified theorems.
-/
#check simProcBasic
def simProcTag (expr : Expr) : MetaM SimpResult :=
withNewMCtxDepth do
let state := myExt.getState (← getEnv)
for (e,t) in state do
let (mvars, _, eq) ← forallMetaTelescope t -- turn quantifiers into mvars
let pf := mkAppN e mvars -- build the proof term
let some (_, ar, br) := eq.app3? ``Eq | throwError "Not an equality: {pf} : {eq}"
if ← withTransparency .reducible (isDefEq expr ar) then
let br ← instantiateMVars br
let pf ← instantiateMVars pf
return {expr := br, pf? := some pf}
return .empty expr
set_option trace.MyTrace true in
example (a : Nat) (p : Nat → Prop) (h : p (4*a + 2*a + a)) :
p ( (a+a+a)+a+(a+a+a) ) := by
run_tac mySimpGoal simProcTag
exact h
/-
Exercise: Update the code for `Tag.lean` & `simProcTag` so that
it can accept theorems with universe levels.
-/
#check eq_self
attribute [my_tag] eq_self
example (a : Nat) : a + a = 2 * a := by
run_tac mySimpGoal simProcTag
exact True.intro
-- Hints:
#check getConstInfo
#check mkFreshLevelMVarsFor
-- Feel free to change the type (Expr × Expr) stored in `myTag`
-- the pair `(e,t)` might not be ideal. Also remember that the metavariables
-- must be introduced when trying to apply a theorem, not at initialization
-- because we want different mvar instantiations at different places.