Skip to content

Commit 09097f6

Browse files
committed
feat: cases' and induction' tactics (#183)
This adds a lean 3 compatible syntax for cases and induction: `cases' x with a b c d`, `induction' x with a b ih_b c ih_c`
1 parent 2331f95 commit 09097f6

File tree

5 files changed

+284
-36
lines changed

5 files changed

+284
-36
lines changed

Mathlib.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import Mathlib.Mathport.SpecialNames
5252
import Mathlib.Mathport.Syntax
5353
import Mathlib.Tactic.Basic
5454
import Mathlib.Tactic.Cache
55+
import Mathlib.Tactic.Cases
5556
import Mathlib.Tactic.Coe
5657
import Mathlib.Tactic.CommandQuote
5758
import Mathlib.Tactic.Conv

Mathlib/Data/List/Defs.lean

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,24 @@ proofs about these definitions, those are contained in other files in `Mathlib.D
1717
namespace List
1818

1919
/-- Split a list at an index.
20-
splitAt 2 [a, b, c] = ([a, b], [c]) -/
20+
```
21+
splitAt 2 [a, b, c] = ([a, b], [c])
22+
``` -/
2123
def splitAt : ℕ → List α → List α × List α
2224
| n+1, x :: xs => let (l, r) := splitAt n xs; (x :: l, r)
2325
| _, xs => ([], xs)
2426

27+
/-- Split a list at an index. Ensures the left list always has the specified length
28+
by right padding with the provided default element.
29+
```
30+
splitAtD 2 [a, b, c] x = ([a, b], [c])
31+
splitAtD 4 [a, b, c] x = ([a, b, c, x], [])
32+
``` -/
33+
def splitAtD : ℕ → List α → α → List α × List α
34+
| 0, xs, a => ([], xs)
35+
| n+1, [], a => let (l, r) := splitAtD n [] a; (a :: l, r)
36+
| n+1, x :: xs, a => let (l, r) := splitAtD n xs a; (x :: l, r)
37+
2538
/-- An auxiliary function for `splitOnP`. -/
2639
def splitOnPAux {α : Type u} (P : α → Prop) [DecidablePred P] : List α → (List α → List α) → List (List α)
2740
| [], f => [f []]
@@ -32,13 +45,17 @@ def splitOnP {α : Type u} (P : α → Prop) [DecidablePred P] (l : List α) : L
3245
splitOnPAux P l id
3346

3447
/-- Split a list at every occurrence of an element.
35-
[1,1,2,3,2,4,4].split_on 2 = [[1,1],[3],[4,4]] -/
48+
```
49+
[1,1,2,3,2,4,4].split_on 2 = [[1,1],[3],[4,4]]
50+
``` -/
3651
def splitOn {α : Type u} [DecidableEq α] (a : α) (as : List α) : List (List α) :=
3752
as.splitOnP (· = a)
3853

3954
/-- Apply a function to the nth tail of `l`. Returns the input without
4055
using `f` if the index is larger than the length of the List.
41-
modifyNthTail f 2 [a, b, c] = [a, b] ++ f [c] -/
56+
```
57+
modifyNthTail f 2 [a, b, c] = [a, b] ++ f [c]
58+
``` -/
4259
@[simp]
4360
def modifyNthTail (f : List α → List α) : ℕ → List α → List α
4461
| 0, l => f l
@@ -63,7 +80,9 @@ def modifyLast (f : α → α) : List α → List α
6380
| x :: xs => x :: modifyLast f xs
6481

6582
/-- `insertNth n a l` inserts `a` into the list `l` after the first `n` elements of `l`
66-
`insertNth 2 1 [1, 2, 3, 4] = [1, 2, 1, 3, 4]`-/
83+
```
84+
insertNth 2 1 [1, 2, 3, 4] = [1, 2, 1, 3, 4]
85+
``` -/
6786
def insertNth (n : ℕ) (a : α) : List α → List α :=
6887
modifyNthTail (cons a) n
6988

@@ -75,7 +94,9 @@ def takeD : ∀ n : ℕ, List α → α → List α
7594

7695
/-- Fold a function `f` over the list from the left, returning the list
7796
of partial results.
78-
scanl (+) 0 [1, 2, 3] = [0, 1, 3, 6] -/
97+
```
98+
scanl (+) 0 [1, 2, 3] = [0, 1, 3, 6]
99+
``` -/
79100
def scanl (f : α → β → α) : α → List β → List α
80101
| a, [] => [a]
81102
| a, b :: l => a :: scanl f (f a b) l
@@ -88,9 +109,10 @@ def scanrAux (f : α → β → β) (b : β) : List α → β × List β
88109
let (b', l') := scanrAux f b l
89110
(f a b', b' :: l')
90111

91-
/-- Fold a function `f` over the list from the right, returning the list
92-
of partial results.
93-
scanr (+) 0 [1, 2, 3] = [6, 5, 3, 0] -/
112+
/-- Fold a function `f` over the list from the right, returning the list of partial results.
113+
```
114+
scanr (+) 0 [1, 2, 3] = [6, 5, 3, 0]
115+
``` -/
94116
def scanr (f : α → β → β) (b : β) (l : List α) : List β :=
95117
let (b', l') := scanrAux f b l
96118
b' :: l'
@@ -142,10 +164,8 @@ def indexesValues (p : α → Prop) [DecidablePred p] (l : List α) : List (ℕ
142164
foldrIdx (fun i a l => if p a then (i, a) :: l else l) [] l
143165

144166
/-- `indexesOf a l` is the list of all indexes of `a` in `l`. For example:
145-
```
146-
indexesOf a [a, b, a, a] = [0, 2, 3]
147-
```
148-
-/
167+
168+
indexesOf a [a, b, a, a] = [0, 2, 3] -/
149169
def indexesOf [DecidableEq α] (a : α) : List α → List Nat :=
150170
findIdxs (Eq a)
151171

@@ -190,13 +210,17 @@ infixl:50 " <:+ " => isSuffix
190210
infixl:50 " <:+: " => isInfix
191211

192212
/-- `inits l` is the list of initial segments of `l`.
193-
inits [1, 2, 3] = [[], [1], [1, 2], [1, 2, 3]] -/
213+
```
214+
inits [1, 2, 3] = [[], [1], [1, 2], [1, 2, 3]]
215+
``` -/
194216
@[simp] def inits : List α → List (List α)
195217
| [] => [[]]
196218
| a :: l => [] :: map (fun t => a :: t) (inits l)
197219

198220
/-- `tails l` is the list of terminal segments of `l`.
199-
tails [1, 2, 3] = [[1, 2, 3], [2, 3], [3], []] -/
221+
```
222+
tails [1, 2, 3] = [[1, 2, 3], [2, 3], [3], []]
223+
``` -/
200224
@[simp] def tails : List α → List (List α)
201225
| [] => [[]]
202226
| a :: l => (a :: l) :: tails l
@@ -209,7 +233,9 @@ def sublists'Aux : List α → (List α → List β) → List (List β) → List
209233
It differs from `sublists` only in the order of appearance of the sublists;
210234
`sublists'` uses the first element of the list as the MSB,
211235
`sublists` uses the first element of the list as the LSB.
212-
sublists' [1, 2, 3] = [[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]] -/
236+
```
237+
sublists' [1, 2, 3] = [[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]
238+
``` -/
213239
def sublists' (l : List α) : List (List α) :=
214240
sublists'Aux l id []
215241

@@ -219,7 +245,9 @@ def sublistsAux : List α → (List α → List β → List β) → List β
219245

220246
/-- `sublists l` is the list of all (non-contiguous) sublists of `l`; cf. `sublists'`
221247
for a different ordering.
222-
sublists [1, 2, 3] = [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]] -/
248+
```
249+
sublists [1, 2, 3] = [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
250+
``` -/
223251
def sublists (l : List α) : List (List α) :=
224252
[] :: sublistsAux l cons
225253

@@ -252,7 +280,9 @@ def transposeAux : List α → List (List α) → List (List α)
252280
| a :: i, l :: ls => (a :: l) :: transposeAux i ls
253281

254282
/-- transpose of a list of lists, treated as a matrix.
255-
transpose [[1, 2], [3, 4], [5, 6]] = [[1, 3, 5], [2, 4, 6]] -/
283+
```
284+
transpose [[1, 2], [3, 4], [5, 6]] = [[1, 3, 5], [2, 4, 6]]
285+
``` -/
256286
def transpose : List (List α) → List (List α)
257287
| [] => []
258288
| l :: ls => transposeAux l (transpose ls)
@@ -280,18 +310,23 @@ def extractp (p : α → Prop) [DecidablePred p] : List α → Option α × List
280310

281311
/-- `revzip l` returns a list of pairs of the elements of `l` paired
282312
with the elements of `l` in reverse order.
283-
`revzip [1,2,3,4,5] = [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)]`
284-
-/
313+
```
314+
revzip [1,2,3,4,5] = [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)]
315+
``` -/
285316
def revzip (l : List α) : List (α × α) :=
286317
zip l l.reverse
287318

288319
/-- `product l₁ l₂` is the list of pairs `(a, b)` where `a ∈ l₁` and `b ∈ l₂`.
289-
product [1, 2] [5, 6] = [(1, 5), (1, 6), (2, 5), (2, 6)] -/
320+
```
321+
product [1, 2] [5, 6] = [(1, 5), (1, 6), (2, 5), (2, 6)]
322+
``` -/
290323
def product (l₁ : List α) (l₂ : List β) : List (α × β) :=
291324
l₁.bind $ fun a => l₂.map $ Prod.mk a
292325

293326
/-- `sigma l₁ l₂` is the list of dependent pairs `(a, b)` where `a ∈ l₁` and `b ∈ l₂ a`.
294-
sigma [1, 2] (λ_, [(5 : ℕ), 6]) = [(1, 5), (1, 6), (2, 5), (2, 6)] -/
327+
```
328+
sigma [1, 2] (λ_, [(5 : ℕ), 6]) = [(1, 5), (1, 6), (2, 5), (2, 6)]
329+
``` -/
295330
protected def sigma {σ : α → Type _} (l₁ : List α) (l₂ : ∀ a, List (σ a)) : List (Σ a, σ a) :=
296331
l₁.bind $ fun a => (l₂ a).map $ Sigma.mk a
297332

@@ -303,7 +338,9 @@ def ofFnAux {n} (f : Fin n → α) : ∀ m, m ≤ n → List α → List α
303338
| m+1, h, l => ofFnAux f m (Nat.le_of_lt h) (f ⟨m, h⟩ :: l)
304339

305340
/-- `ofFn f` with `f : fin n → α` returns the list whose ith element is `f i`
306-
`ofFn f = [f 0, f 1, ... , f(n - 1)]` -/
341+
```
342+
ofFn f = [f 0, f 1, ... , f(n - 1)]
343+
``` -/
307344
def ofFn {n} (f : Fin n → α) : List α :=
308345
ofFnAux f n (Nat.le_refl _) []
309346

@@ -347,13 +384,17 @@ section Chain
347384
variable (R : α → α → Prop)
348385

349386
/-- `Chain R a l` means that `R` holds between adjacent elements of `a::l`.
350-
Chain R a [b, c, d] ↔ R a b ∧ R b c ∧ R c d -/
387+
```
388+
Chain R a [b, c, d] ↔ R a b ∧ R b c ∧ R c d
389+
``` -/
351390
inductive Chain : α → List α → Prop
352391
| nil {a : α} : Chain a []
353392
| cons : ∀ {a b : α} {l : List α}, R a b → Chain b l → Chain a (b :: l)
354393

355394
/-- `Chain' R l` means that `R` holds between adjacent elements of `l`.
356-
Chain' R [a, b, c, d] ↔ R a b ∧ R b c ∧ R c d -/
395+
```
396+
Chain' R [a, b, c, d] ↔ R a b ∧ R b c ∧ R c d
397+
``` -/
357398
def Chain' : List α → Prop
358399
| [] => True
359400
| a :: l => Chain R a l
@@ -367,7 +408,8 @@ def Nodup : List α → Prop :=
367408

368409
/-- `eraseDup l` removes duplicates from `l` (taking only the first occurrence).
369410
Defined as `pwFilter (≠)`.
370-
eraseDup [1, 0, 2, 2, 1] = [0, 2, 1] -/
411+
412+
eraseDup [1, 0, 2, 2, 1] = [0, 2, 1] -/
371413
def eraseDup [DecidableEq α] : List α → List α :=
372414
pwFilter (· ≠ ·)
373415

@@ -398,7 +440,9 @@ def last' {α} : List α → Option α
398440
| b :: l => last' l
399441

400442
/-- `rotate l n` rotates the elements of `l` to the left by `n`
401-
rotate [0, 1, 2, 3, 4, 5] 2 = [2, 3, 4, 5, 0, 1] -/
443+
```
444+
rotate [0, 1, 2, 3, 4, 5] 2 = [2, 3, 4, 5, 0, 1]
445+
``` -/
402446
def rotate (l : List α) (n : ℕ) : List α :=
403447
let (l₁, l₂) := List.splitAt (n % l.length) l
404448
l₂ ++ l₁
@@ -431,9 +475,10 @@ def mmapFilter {m : Type → Type v} [Monad m] {α β} (f : α → m (Option β)
431475
`mmapUpperTriangle f l` calls `f` on all elements in the upper triangular part of `l × l`.
432476
That is, for each `e ∈ l`, it will run `f e e` and then `f e e'`
433477
for each `e'` that appears after `e` in `l`.
434-
Example: suppose `l = [1, 2, 3]`. `mmapUpperTriangle f l` will produce the list
435-
`[f 1 1, f 1 2, f 1 3, f 2 2, f 2 3, f 3 3]`.
436-
-/
478+
```
479+
mmapUpperTriangle f [1, 2, 3] =
480+
return [← f 1 1, ← f 1 2, ← f 1 3, ← f 2 2, ← f 2 3, ← f 3 3]
481+
``` -/
437482
def mmapUpperTriangle {m} [Monad m] {α β : Type u} (f : α → α → m β) : List α → m (List β)
438483
| [] => pure []
439484
| h :: t => return (← f h h) :: (← t.mmap (f h)) ++ (← t.mmapUpperTriangle f)
@@ -442,9 +487,9 @@ def mmapUpperTriangle {m} [Monad m] {α β : Type u} (f : α → α → m β) :
442487
`mmap'Diag f l` calls `f` on all elements in the upper triangular part of `l × l`.
443488
That is, for each `e ∈ l`, it will run `f e e` and then `f e e'`
444489
for each `e'` that appears after `e` in `l`.
445-
Example: suppose `l = [1, 2, 3]`. `mmap'Diag f l` will evaluate, in this order,
446-
`f 1 1`, `f 1 2`, `f 1 3`, `f 2 2`, `f 2 3`, `f 3 3`.
447-
-/
490+
```
491+
mmap'Diag f [1, 2, 3] = do f 1 1; f 1 2; f 1 3; f 2 2; f 2 3; f 3 3
492+
``` -/
448493
def mmap'Diag {m} [Monad m] {α} (f : α → α → m Unit) : List α → m Unit
449494
| [] => return ()
450495
| h :: t => do f h h; t.mmap' (f h); t.mmap'Diag f

Mathlib/Mathport/Syntax.lean

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Authors: Mario Carneiro
55
-/
66
import Lean.Elab.Command
77
import Lean.Elab.Quotation
8+
import Mathlib.Tactic.Cases
89
import Mathlib.Tactic.Core
910
import Mathlib.Tactic.CommandQuote
1011
import Mathlib.Tactic.Ext
@@ -116,13 +117,9 @@ syntax (name := applyWith) "apply " term " with " term : tactic
116117
syntax (name := mapply) "mapply " term : tactic
117118
syntax (name := toExpr') "to_expr' " term : tactic
118119
syntax (name := withCases) "with_cases " tacticSeq : tactic
119-
syntax (name := induction') "induction' " casesTarget,+ (" using " ident)?
120-
(" with " (colGt binderIdent)+)? (" generalizing " (colGt ident)+)? : tactic
121120
syntax caseArg := binderIdent,+ (" :" (ppSpace (ident <|> "_"))+)?
122121
syntax (name := case') "case' " (("[" caseArg,* "]") <|> caseArg) " => " tacticSeq : tactic
123122
syntax "destruct " term : tactic
124-
syntax (name := cases') "cases' " casesTarget,+ (" using " ident)?
125-
(" with " (colGt binderIdent)+)? : tactic
126123
syntax (name := casesM) "casesm" "*"? ppSpace term,* : tactic
127124
syntax (name := casesType) "cases_type" "*"? ppSpace ident* : tactic
128125
syntax (name := casesType!) "cases_type!" "*"? ppSpace ident* : tactic

Mathlib/Tactic/Cases.lean

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/-
2+
Copyright (c) 2022 Mario Carneiro. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Mario Carneiro
5+
-/
6+
import Lean
7+
import Mathlib.Tactic.OpenPrivate
8+
import Mathlib.Data.List.Defs
9+
10+
/-!
11+
12+
# Backward compatible implementation of lean 3 `cases` tactic
13+
14+
This tactic is similar to the `cases` tactic in lean 4 core, but the syntax for giving
15+
names is different:
16+
17+
```
18+
example (h : p ∨ q) : q ∨ p := by
19+
cases h with
20+
| inl hp => exact Or.inr hp
21+
| inr hq => exact Or.inl hq
22+
23+
example (h : p ∨ q) : q ∨ p := by
24+
cases' h with hp hq
25+
· exact Or.inr hp
26+
· exact Or.inl hq
27+
28+
example (h : p ∨ q) : q ∨ p := by
29+
rcases h with hp | hq
30+
· exact Or.inr hp
31+
· exact Or.inl hq
32+
```
33+
34+
Prefer `cases` or `rcases` when possible, because these tactics promote structured proofs.
35+
-/
36+
37+
namespace Lean.Parser.Tactic
38+
open Meta Elab Elab.Tactic
39+
40+
open private getAltNumFields in evalCases ElimApp.evalAlts.go in
41+
def ElimApp.evalNames (elimInfo : ElimInfo) (alts : Array (Name × MVarId)) (withArg : Syntax)
42+
(numEqs := 0) (numGeneralized := 0) (toClear : Array FVarId := #[]) :
43+
TermElabM (Array MVarId) := do
44+
let mut names := if withArg.isNone then [] else
45+
withArg[1].getArgs.map (getNameOfIdent' ·[0]) |>.toList
46+
let mut subgoals := #[]
47+
for (altName, g) in alts do
48+
let numFields ← getAltNumFields elimInfo altName
49+
let (altVarNames, names') := names.splitAtD numFields `_
50+
names := names'
51+
let (_, g) ← introN g numFields altVarNames
52+
let some (g, _) ← Cases.unifyEqs numEqs g {} | pure ()
53+
let (_, g) ← introNP g numGeneralized
54+
let g ← liftM $ toClear.foldlM tryClear g
55+
subgoals := subgoals.push g
56+
pure subgoals
57+
58+
open private getElimNameInfo generalizeTargets generalizeVars in evalInduction in
59+
elab (name := induction') tk:"induction' " tgts:(casesTarget,+)
60+
usingArg:(" using " ident)?
61+
withArg:(" with " (colGt binderIdent)+)?
62+
genArg:(" generalizing " (colGt ident)+)? : tactic => do
63+
let targets ← elabCasesTargets tgts.getSepArgs
64+
let (elimName, elimInfo) ← getElimNameInfo usingArg targets (induction := true)
65+
let g ← getMainGoal
66+
withMVarContext g do
67+
let targets ← addImplicitTargets elimInfo targets
68+
evalInduction.checkTargets targets
69+
let targetFVarIds := targets.map (·.fvarId!)
70+
withMVarContext g do
71+
let genArgs ← if genArg.isNone then pure #[] else getFVarIds genArg[1].getArgs
72+
let forbidden ← mkGeneralizationForbiddenSet targets
73+
let mut s ← getFVarSetToGeneralize targets forbidden
74+
for v in genArgs do
75+
if forbidden.contains v then
76+
throwError "variable cannot be generalized because target depends on it{indentExpr (mkFVar v)}"
77+
if s.contains v then
78+
throwError "unnecessary 'generalizing' argument, variable '{mkFVar v}' is generalized automatically"
79+
s := s.insert v
80+
let (fvarIds, g) ← Meta.revert g (← sortFVarIds s.toArray)
81+
let result ← withRef tgts <| ElimApp.mkElimApp elimName elimInfo targets (← getMVarTag g)
82+
let elimArgs := result.elimApp.getAppArgs
83+
ElimApp.setMotiveArg g elimArgs[elimInfo.motivePos].mvarId! targetFVarIds
84+
assignExprMVar g result.elimApp
85+
let subgoals ← ElimApp.evalNames elimInfo result.alts withArg
86+
(numGeneralized := fvarIds.size) (toClear := targetFVarIds)
87+
setGoals (subgoals ++ result.others).toList
88+
89+
open private getElimNameInfo in evalCases in
90+
elab (name := cases') "cases' " tgts:(casesTarget,+) usingArg:(" using " ident)?
91+
withArg:(" with " (colGt binderIdent)+)? : tactic => do
92+
let targets ← elabCasesTargets tgts.getSepArgs
93+
let (elimName, elimInfo) ← getElimNameInfo usingArg targets (induction := false)
94+
let g ← getMainGoal
95+
withMVarContext g do
96+
let targets ← addImplicitTargets elimInfo targets
97+
let result ← withRef tgts <| ElimApp.mkElimApp elimName elimInfo targets (← getMVarTag g)
98+
let elimArgs := result.elimApp.getAppArgs
99+
let targets ← elimInfo.targetsPos.mapM (instantiateMVars elimArgs[·])
100+
let motive := elimArgs[elimInfo.motivePos]
101+
let g ← generalizeTargetsEq g (← inferType motive) targets
102+
let (targetsNew, g) ← introN g targets.size
103+
withMVarContext g do
104+
ElimApp.setMotiveArg g motive.mvarId! targetsNew
105+
assignExprMVar g result.elimApp
106+
let subgoals ← ElimApp.evalNames elimInfo result.alts withArg
107+
(numEqs := targets.size) (toClear := targetsNew)
108+
setGoals subgoals.toList

0 commit comments

Comments
 (0)