-
Notifications
You must be signed in to change notification settings - Fork 273
/
Trans.lean
186 lines (167 loc) · 6.94 KB
/
Trans.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
/-
Copyright (c) 2022 Siddhartha Gadgil. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Siddhartha Gadgil, Mario Carneiro
-/
import Mathlib.Lean.Meta
import Mathlib.Lean.Elab.Tactic.Basic
/-!
# `trans` tactic
This implements the `trans` tactic, which can apply transitivity theorems with an optional middle
variable argument.
-/
set_option autoImplicit true
namespace Mathlib.Tactic
open Lean Meta Elab
initialize registerTraceClass `Tactic.trans
/-- Discrimation tree settings for the `trans` extension. -/
def transExt.config : WhnfCoreConfig := {}
/-- Environment extension storing transitivity lemmas -/
initialize transExt :
SimpleScopedEnvExtension (Name × Array DiscrTree.Key) (DiscrTree Name) ←
registerSimpleScopedEnvExtension {
addEntry := fun dt (n, ks) ↦ dt.insertCore ks n
initial := {}
}
initialize registerBuiltinAttribute {
name := `trans
descr := "transitive relation"
add := fun decl _ kind ↦ MetaM.run' do
let declTy := (← getConstInfo decl).type
let (xs, _, targetTy) ← withReducible <| forallMetaTelescopeReducing declTy
let fail := throwError
"@[trans] attribute only applies to lemmas proving
x ∼ y → y ∼ z → x ∼ z, got {indentExpr declTy} with target {indentExpr targetTy}"
let .app (.app rel _) _ := targetTy | fail
let some yzHyp := xs.back? | fail
let some xyHyp := xs.pop.back? | fail
let .app (.app _ _) _ ← inferType yzHyp | fail
let .app (.app _ _) _ ← inferType xyHyp | fail
let key ← withReducible <| DiscrTree.mkPath rel transExt.config
transExt.add (decl, key) kind
}
/-- Composition using the `Trans` class in the homogeneous case. -/
def _root_.Trans.simple {a b c : α} [Trans r r r] : r a b → r b c → r a c := trans
/-- Composition using the `Trans` class in the general case. -/
def _root_.Trans.het {a : α} {b : β} {c : γ}
{r : α → β → Sort u} {s : β → γ → Sort v} {t : outParam (α → γ → Sort w)}
[Trans r s t] : r a b → s b c → t a c := trans
open Lean.Elab.Tactic
/-- solving `e ← mkAppM' f #[x]` -/
def getExplicitFuncArg? (e : Expr) : MetaM (Option <| Expr × Expr) := do
match e with
| Expr.app f a => do
if ← isDefEq (← mkAppM' f #[a]) e then
return some (f, a)
else
getExplicitFuncArg? f
| _ => return none
/-- solving `tgt ← mkAppM' rel #[x, z]` given `tgt = f z` -/
def getExplicitRelArg? (tgt f z : Expr) : MetaM (Option <| Expr × Expr) := do
match f with
| Expr.app rel x => do
let check: Bool ← do
try
let folded ← mkAppM' rel #[x, z]
isDefEq folded tgt
catch _ =>
pure false
if check then
return some (rel, x)
else
getExplicitRelArg? tgt rel z
| _ => return none
/-- refining `tgt ← mkAppM' rel #[x, z]` dropping more arguments if possible -/
def getExplicitRelArgCore (tgt rel x z : Expr) : MetaM (Expr × Expr) := do
match rel with
| Expr.app rel' _ => do
let check: Bool ← do
try
let folded ← mkAppM' rel' #[x, z]
isDefEq folded tgt
catch _ =>
pure false
if !check then
return (rel, x)
else
getExplicitRelArgCore tgt rel' x z
| _ => return (rel ,x)
/--
`trans` applies to a goal whose target has the form `t ~ u` where `~` is a transitive relation,
that is, a relation which has a transitivity lemma tagged with the attribute [trans].
* `trans s` replaces the goal with the two subgoals `t ~ s` and `s ~ u`.
* If `s` is omitted, then a metavariable is used instead.
-/
elab "trans" t?:(ppSpace colGt term)? : tactic => withMainContext do
let tgt ← getMainTarget''
let (rel, x, z) ←
match tgt with
| Expr.app f z =>
match (← getExplicitRelArg? tgt f z) with
| some (rel, x) =>
let (rel, x) ← getExplicitRelArgCore tgt rel x z
pure (rel, x, z)
| none => throwError "transitivity lemmas only apply to
binary relations, not {indentExpr tgt}"
| _ => throwError "transitivity lemmas only apply to binary relations, not {indentExpr tgt}"
trace[Tactic.trans]"goal decomposed"
trace[Tactic.trans]"rel: {indentExpr rel}"
trace[Tactic.trans]"x: {indentExpr x}"
trace[Tactic.trans]"z: {indentExpr z}"
-- first trying the homogeneous case
try
let ty ← inferType x
let t'? ← t?.mapM (elabTermWithHoles · ty (← getMainTag))
let s ← saveState
trace[Tactic.trans]"trying homogeneous case"
let lemmas :=
(← (transExt.getState (← getEnv)).getUnify rel transExt.config).push ``Trans.simple
for lem in lemmas do
trace[Tactic.trans]"trying lemma {lem}"
try
liftMetaTactic fun g ↦ do
let lemTy ← inferType (← mkConstWithLevelParams lem)
let arity ← withReducible <| forallTelescopeReducing lemTy fun es _ ↦ pure es.size
let y ← (t'?.map (pure ·.1)).getD (mkFreshExprMVar ty)
let g₁ ← mkFreshExprMVar (some <| ← mkAppM' rel #[x, y]) .synthetic
let g₂ ← mkFreshExprMVar (some <| ← mkAppM' rel #[y, z]) .synthetic
g.assign (← mkAppOptM lem (mkArray (arity - 2) none ++ #[some g₁, some g₂]))
pure <| [g₁.mvarId!, g₂.mvarId!] ++ if let some (_, gs') := t'? then gs' else [y.mvarId!]
return
catch _ => s.restore
pure ()
catch _ =>
trace[Tactic.trans]"trying heterogeneous case"
let t'? ← t?.mapM (elabTermWithHoles · none (← getMainTag))
let s ← saveState
for lem in (← (transExt.getState (← getEnv)).getUnify rel transExt.config).push
``HEq.trans |>.push ``HEq.trans do
try
liftMetaTactic fun g ↦ do
trace[Tactic.trans]"trying lemma {lem}"
let lemTy ← inferType (← mkConstWithLevelParams lem)
let arity ← withReducible <| forallTelescopeReducing lemTy fun es _ ↦ pure es.size
trace[Tactic.trans]"arity: {arity}"
trace[Tactic.trans]"lemma-type: {lemTy}"
let y ← (t'?.map (pure ·.1)).getD (mkFreshExprMVar none)
trace[Tactic.trans]"obtained y: {y}"
trace[Tactic.trans]"rel: {indentExpr rel}"
trace[Tactic.trans]"x:{indentExpr x}"
trace[Tactic.trans]"z: {indentExpr z}"
let g₂ ← mkFreshExprMVar (some <| ← mkAppM' rel #[y, z]) .synthetic
trace[Tactic.trans]"obtained g₂: {g₂}"
let g₁ ← mkFreshExprMVar (some <| ← mkAppM' rel #[x, y]) .synthetic
trace[Tactic.trans]"obtained g₁: {g₁}"
g.assign (← mkAppOptM lem (mkArray (arity - 2) none ++ #[some g₁, some g₂]))
pure <| [g₁.mvarId!, g₂.mvarId!] ++ if let some (_, gs') := t'? then gs' else [y.mvarId!]
return
catch e =>
trace[Tactic.trans]"failed: {e.toMessageData}"
s.restore
throwError m!"no applicable transitivity lemma found for {indentExpr tgt}
"
syntax "transitivity" (ppSpace colGt term)? : tactic
set_option hygiene false in
macro_rules
| `(tactic| transitivity) => `(tactic| trans)
| `(tactic| transitivity $e) => `(tactic| trans $e)