Built with
doc-gen4 , running Lean4.
Bubbles (
) indicate interactive fragments: hover for details, tap to reveal contents.
Use
Ctrl+↑ Ctrl+↓ to navigate,
Ctrl+🖱️ to focus.
On Mac, use
Cmd instead of
Ctrl .
/-
Copyright (c) 2019 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn
Ported by: Yury Kudryashov, Frédéric Dupuis
-/
import Mathlib.Tactic.Cases
import Mathlib.Tactic.PermuteGoals
import Mathlib.Init.Data.Int.Order
/-!
# lift tactic
This file defines the `lift` tactic, allowing the user to lift elements from one type to another
under a specified condition.
## Tags
lift, tactic
-/
/-- A class specifying that you can lift elements from `α` to `β` assuming `cond` is true.
Used by the tactic `lift`. -/
class CanLift ( α β : Sort _ ) ( coe : outParam <| β → α ) ( cond : outParam <| α → Prop ) where
/-- An element of `α` that satisfies `cond` belongs to the range of `coe`. -/
prf : ∀ x : α , cond x → ∃ y : β , coe y = x
#align can_lift CanLift
instance : CanLift ℤ ℕ (fun n => ↑n ) fun x => 0 ≤ x instance : CanLift ℤ ℕ ( fun n : ℕ ↦ n ) (0 ≤ ·) :=
⟨ fun n hn ↦ ⟨ n . natAbs , Int.natAbs_of_nonneg hn ⟩⟩
/-- Enable automatic handling of pi types in `CanLift`. -/
instance Pi.canLift : (ι : Sort u_1) →
(α : ι → Sort u_2 ) →
(β : ι → Sort u_3 ) →
(coe : (i : ι ) → β i → α i ) →
(P : (i : ι ) → α i → Prop ) →
[inst : (i : ι ) → CanLift (α i ) (β i ) (coe i ) (P i ) ] →
CanLift ((i : ι ) → α i ) ((i : ι ) → β i ) (fun f i => coe i (f i ) ) fun f => ∀ (i : ι ), P i (f i ) Pi.canLift ( ι : Sort _ ) ( α β : ι → Sort _ ) ( coe : ∀ i , β i → α i ) ( P : ∀ i , α i → Prop )
[∀ i , CanLift ( α i ) ( β i ) ( coe i ) ( P i )] :
CanLift (∀ i , α i ) (∀ i , β i ) ( fun f i ↦ coe i ( f i )) fun f ↦ ∀ i , P i ( f i ) where
prf f hf := ⟨ fun i => Classical.choose : {α : Sort ?u.411} → {p : α → Prop } → (∃ x , p x ) → α Classical.choose ( CanLift.prf ( f i ) ( hf i )),
funext : ∀ {α : Sort ?u.465} {β : α → Sort ?u.464 } {f g : (x : α ) → β x }, (∀ (x : α ), f x = g x ) → f = g funext fun i => Classical.choose_spec ( CanLift.prf ( f i ) ( hf i ))⟩
#align pi.can_lift Pi.canLift : (ι : Sort u_1) →
(α : ι → Sort u_2 ) →
(β : ι → Sort u_3 ) →
(coe : (i : ι ) → β i → α i ) →
(P : (i : ι ) → α i → Prop ) →
[inst : (i : ι ) → CanLift (α i ) (β i ) (coe i ) (P i ) ] →
CanLift ((i : ι ) → α i ) ((i : ι ) → β i ) (fun f i => coe i (f i ) ) fun f => ∀ (i : ι ), P i (f i ) Pi.canLift
theorem Subtype.exists_pi_extension : ∀ {ι : Sort u_1} {α : ι → Sort u_2 } [ne : ∀ (i : ι ), Nonempty (α i ) ] {p : ι → Prop } (f : (i : Subtype p ) → α i .val ),
∃ g , (fun i => g i .val ) = f Subtype.exists_pi_extension { ι : Sort _ } { α : ι → Sort _ } [ ne : ∀ i , Nonempty ( α i )]
{ p : ι → Prop } ( f : ∀ i : Subtype p , α i ) :
∃ g : ∀ i : ι , α i , ( fun i : Subtype p => g i ) = f := by
∃ g , (fun i => g i .val ) = f haveI : DecidablePred : {α : Sort ?u.1025} → (α → Prop ) → Sort (max1?u.1025) DecidablePred p := fun i ↦ Classical.propDecidable ( p i ) ∃ g , (fun i => g i .val ) = f
∃ g , (fun i => g i .val ) = f exact ⟨ fun i => if hi : p i then f ⟨ i , hi ⟩ else Classical.choice ( ne i ),
funext : ∀ {α : Sort ?u.1092} {β : α → Sort ?u.1091 } {f g : (x : α ) → β x }, (∀ (x : α ), f x = g x ) → f = g funext fun i ↦ dif_pos i . 2 ⟩
#align subtype.exists_pi_extension Subtype.exists_pi_extension : ∀ {ι : Sort u_1} {α : ι → Sort u_2 } [ne : ∀ (i : ι ), Nonempty (α i ) ] {p : ι → Prop } (f : (i : Subtype p ) → α i .val ),
∃ g , (fun i => g i .val ) = f Subtype.exists_pi_extension
instance PiSubtype.canLift : (ι : Sort ?u.1159) →
(α : ι → Sort ?u.1164 ) →
[inst : ∀ (i : ι ), Nonempty (α i ) ] →
(p : ι → Prop ) → CanLift ((i : Subtype p ) → α i .val ) ((i : ι ) → α i ) (fun f i => f i .val ) fun x => True PiSubtype.canLift ( ι : Sort _ ) ( α : ι → Sort _ ) [∀ i , Nonempty ( α i )] ( p : ι → Prop ) :
CanLift (∀ i : Subtype : {α : Sort ?u.1182} → (α → Prop ) → Sort (max1?u.1182) Subtype p , α i ) (∀ i , α i ) ( fun f i => f i ) fun _ => True where
prf f _ := Subtype.exists_pi_extension : ∀ {ι : Sort ?u.1454} {α : ι → Sort ?u.1455 } [ne : ∀ (i : ι ), Nonempty (α i ) ] {p : ι → Prop }
(f : (i : Subtype p ) → α i .val ), ∃ g , (fun i => g i .val ) = f Subtype.exists_pi_extension f
#align pi_subtype.can_lift PiSubtype.canLift : (ι : Sort u_1) →
(α : ι → Sort u_2 ) →
[inst : ∀ (i : ι ), Nonempty (α i ) ] →
(p : ι → Prop ) → CanLift ((i : Subtype p ) → α i .val ) ((i : ι ) → α i ) (fun f i => f i .val ) fun x => True PiSubtype.canLift
-- TODO: test if we need this instance in Lean 4
instance PiSubtype.canLift' ( ι : Sort _ ) ( α : Sort _ ) [ Nonempty α ] ( p : ι → Prop ) :
CanLift ( Subtype : {α : Sort ?u.1648} → (α → Prop ) → Sort (max1?u.1648) Subtype p → α ) ( ι → α ) ( fun f i => f i ) fun _ => True :=
PiSubtype.canLift : (ι : Sort ?u.1804) →
(α : ι → Sort ?u.1803 ) →
[inst : ∀ (i : ι ), Nonempty (α i ) ] →
(p : ι → Prop ) → CanLift ((i : Subtype p ) → α i .val ) ((i : ι ) → α i ) (fun f i => f i .val ) fun x => True PiSubtype.canLift ι ( fun _ => α ) p
#align pi_subtype.can_lift' PiSubtype.canLift'
instance Subtype.canLift : {α : Sort ?u.1932} → (p : α → Prop ) → CanLift α { x // p x } val p Subtype.canLift { α : Sort _ } ( p : α → Prop ) :
CanLift α { x // p x } Subtype.val p where prf a ha :=
⟨⟨ a , ha ⟩, rfl : ∀ {α : Sort ?u.2004} {a : α }, a = a rfl⟩
#align subtype.can_lift Subtype.canLift : {α : Sort u_1} → (p : α → Prop ) → CanLift α { x // p x } Subtype.val p Subtype.canLift
namespace Mathlib.Tactic
open Lean Parser Tactic Elab Tactic Meta
/-- Lift an expression to another type.
* Usage: `'lift' expr 'to' expr ('using' expr)? ('with' id (id id?)?)?`.
* If `n : ℤ` and `hn : n ≥ 0` then the tactic `lift n to ℕ using hn` creates a new
constant of type `ℕ`, also named `n` and replaces all occurrences of the old variable `(n : ℤ)`
with `↑n` (where `n` in the new variable). It will remove `n` and `hn` from the context.
+ So for example the tactic `lift n to ℕ using hn` transforms the goal
`n : ℤ, hn : n ≥ 0, h : P n ⊢ n = 3` to `n : ℕ, h : P ↑n ⊢ ↑n = 3`
(here `P` is some term of type `ℤ → Prop`).
* The argument `using hn` is optional, the tactic `lift n to ℕ` does the same, but also creates a
new subgoal that `n ≥ 0` (where `n` is the old variable).
This subgoal will be placed at the top of the goal list.
+ So for example the tactic `lift n to ℕ` transforms the goal
`n : ℤ, h : P n ⊢ n = 3` to two goals
`n : ℤ, h : P n ⊢ n ≥ 0` and `n : ℕ, h : P ↑n ⊢ ↑n = 3`.
* You can also use `lift n to ℕ using e` where `e` is any expression of type `n ≥ 0`.
* Use `lift n to ℕ with k` to specify the name of the new variable.
* Use `lift n to ℕ with k hk` to also specify the name of the equality `↑k = n`. In this case, `n`
will remain in the context. You can use `rfl` for the name of `hk` to substitute `n` away
(i.e. the default behavior).
* You can also use `lift e to ℕ with k hk` where `e` is any expression of type `ℤ`.
In this case, the `hk` will always stay in the context, but it will be used to rewrite `e` in
all hypotheses and the target.
+ So for example the tactic `lift n + 3 to ℕ using hn with k hk` transforms the goal
`n : ℤ, hn : n + 3 ≥ 0, h : P (n + 3) ⊢ n + 3 = 2 * n` to the goal
`n : ℤ, k : ℕ, hk : ↑k = n + 3, h : P ↑k ⊢ ↑k = 2 * n`.
* The tactic `lift n to ℕ using h` will remove `h` from the context. If you want to keep it,
specify it again as the third argument to `with`, like this: `lift n to ℕ using h with n rfl h`.
* More generally, this can lift an expression from `α` to `β` assuming that there is an instance
of `CanLift α β`. In this case the proof obligation is specified by `CanLift.prf`.
* Given an instance `CanLift β γ`, it can also lift `α → β` to `α → γ`; more generally, given
`β : Π a : α, Type _`, `γ : Π a : α, Type _`, and `[Π a : α, CanLift (β a) (γ a)]`, it
automatically generates an instance `CanLift (Π a, β a) (Π a, γ a)`.
`lift` is in some sense dual to the `zify` tactic. `lift (z : ℤ) to ℕ` will change the type of an
integer `z` (in the supertype) to `ℕ` (the subtype), given a proof that `z ≥ 0`;
propositions concerning `z` will still be over `ℤ`. `zify` changes propositions about `ℕ` (the
subtype) to propositions about `ℤ` (the supertype), without changing the type of any variable.
-/
syntax ( name := lift ) "lift " term " to " term (" using " term )?
(" with " ident ( colGt ident )? ( colGt ident )?)? : tactic
/-- Generate instance for the `lift` tactic. -/
def Lift.getInst ( old_tp new_tp : Expr ) : MetaM ( Expr × Expr × Expr ) := do
let coe ← mkFreshExprMVar ( some $ .forallE `a new_tp old_tp .default )
let p ← mkFreshExprMVar ( some $ .forallE `a old_tp ( .sort .zero ) .default )
let inst_type ← mkAppM `` CanLift #[ old_tp , new_tp , coe , p ]
let inst ← synthInstance inst_type -- TODO: catch error
return (← instantiateMVars p, ← instantiateMVars coe, ← instantiateMVars inst) : ?m.2701 × ?m.2702 (← instantiateMVars (← instantiateMVars p, ← instantiateMVars coe, ← instantiateMVars inst) : ?m.2701 × ?m.2702 p (← instantiateMVars p, ← instantiateMVars coe, ← instantiateMVars inst) : ?m.2701 × ?m.2702 , ← instantiateMVars (← instantiateMVars p, ← instantiateMVars coe, ← instantiateMVars inst) : ?m.2701 × ?m.2702 coe (← instantiateMVars p, ← instantiateMVars coe, ← instantiateMVars inst) : ?m.2701 × ?m.2702 , ← instantiateMVars (← instantiateMVars p, ← instantiateMVars coe, ← instantiateMVars inst) : ?m.2701 × ?m.2702 inst (← instantiateMVars p, ← instantiateMVars coe, ← instantiateMVars inst) : ?m.2701 × ?m.2702 )
/-- Main function for the `lift` tactic. -/
def Lift.main ( e t : TSyntax `term ) ( hUsing : Option ( TSyntax `term ))
( newVarName newEqName : Option ( TSyntax `ident )) ( keepUsing : Bool ) : TacticM Unit :=
withMainContext do
-- Are we using a new variable for the lifted var?
let isNewVar := ! newVarName . isNone
-- Name of the new hypothesis containing the equality of the lifted variable with the old one
-- rfl if none is given
let newEqName := ( newEqName . map Syntax.getId ). getD `rfl
-- Was a new hypothesis given?
let isNewEq := newEqName != `rfl
let e ← elabTerm e none
let goal ← getMainGoal
if ! (← inferType (← instantiateMVars (← goal.getType))) : ?m.4698
(← inferType (← inferType (← instantiateMVars (← goal.getType))) : ?m.4698
(← instantiateMVars (← goal.getType)) : ?m.4644
(← instantiateMVars (← instantiateMVars (← goal.getType)) : ?m.4644
(← goal.getType) : ?m.4527
(← goal (← goal.getType) : ?m.4527
. getType (← goal.getType) : ?m.4527
) (← instantiateMVars (← goal.getType)) : ?m.4644
) (← inferType (← instantiateMVars (← goal.getType))) : ?m.4698
) . isProp then throwError
"lift tactic failed. Tactic is only applicable when the target is a proposition." : TacticM (?m.4705 y✝ )throwError throwError
"lift tactic failed. Tactic is only applicable when the target is a proposition." : TacticM (?m.4705 y✝ )
"lift tactic failed. Tactic is only applicable when the target is a proposition."
if newVarName == none ∧ ! e . isFVar then throwError
"lift tactic failed. When lifting an expression, a new variable name must be given" : ?m.5559 ?m.5560
throwError throwError
"lift tactic failed. When lifting an expression, a new variable name must be given" : ?m.5559 ?m.5560
"lift tactic failed. When lifting an expression, a new variable name must be given"
let ( p , coe , inst ) ← Lift.getInst (← inferType e ) (← Term.elabType t) : ?m.6024
(← Term.elabType (← Term.elabType t) : ?m.6024
t (← Term.elabType t) : ?m.6024
)
let prf ← match hUsing with
| some h => elabTermEnsuringType elabTermEnsuringType h (p.betaRev #[e]) : TacticM (?m.6134 y✝ ) h elabTermEnsuringType h (p.betaRev #[e]) : TacticM (?m.6134 y✝ ) (p elabTermEnsuringType h (p.betaRev #[e]) : TacticM (?m.6134 y✝ ) .betaRev elabTermEnsuringType h (p.betaRev #[e]) : TacticM (?m.6134 y✝ ) #[e elabTermEnsuringType h (p.betaRev #[e]) : TacticM (?m.6134 y✝ ) ])
| none => mkFreshExprMVar mkFreshExprMVar (some (p.betaRev #[e])) : TacticM (?m.6134 y✝ ) (some mkFreshExprMVar (some (p.betaRev #[e])) : TacticM (?m.6134 y✝ ) (p mkFreshExprMVar (some (p.betaRev #[e])) : TacticM (?m.6134 y✝ ) .betaRev mkFreshExprMVar (some (p.betaRev #[e])) : TacticM (?m.6134 y✝ ) #[e mkFreshExprMVar (some (p.betaRev #[e])) : TacticM (?m.6134 y✝ ) ]))
let newVarName ← match newVarName with
| some v => pure : {f : Type ?u.6610 → Type ?u.6609 } → [self : Pure f ] → {α : Type ?u.6610} → α → f α pure v . getId
| none => e e.fvarId!.getUserName : TacticM (?m.6554 y✝ ) .fvarId! e.fvarId!.getUserName : TacticM (?m.6554 y✝ ) .getUserName
let prfEx ← mkAppOptM `` CanLift.prf #[ none , none , coe , p , inst , e , prf ]
let prfEx ← instantiateMVars prfEx
let prfSyn ← prfEx . toSyntax
-- if we have a new variable, but no hypothesis name was provided, we temporarily use a dummy
-- hypothesis name
let newEqName ← if isNewVar && ! isNewEq then withMainContext withMainContext <| getUnusedUserName `tmpVar : TacticM (?m.7869 y✝ ) <| getUnusedUserName withMainContext <| getUnusedUserName `tmpVar : TacticM (?m.7869 y✝ ) `tmpVar
else pure : {f : Type ?u.8140 → Type ?u.8139 } → [self : Pure f ] → {α : Type ?u.8140} → α → f α purepure newEqName : TacticM (?m.7869 y✝ ) newEqName
let newEqIdent := mkIdent newEqName
-- Run rcases on the proof of the lift condition
replaceMainGoal (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
(← Std.Tactic.RCases.rcases (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
#[( none (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
, prfSyn (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
)]
( .tuple (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
Syntax.missing (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
<| [ newVarName (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
, newEqName (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
]. map (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
( .one (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
Syntax.missing (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
)) goal (← Std.Tactic.RCases.rcases #[(none, prfSyn)]
(.tuple Syntax.missing <| [newVarName, newEqName].map (.one Syntax.missing)) goal) : ?m.8552
)
-- if we use a new variable, then substitute it everywhere
if isNewVar then
for decl in ← getLCtx do
if decl . userName != newEqName then
let declIdent := mkIdent decl . userName
-- The line below fails if $declIdent is there only once.
evalTactic (← `(tactic| simp only [← $newEqIdent] at $declIdent $declIdent)) : ?m.10095
(← `(tactic| (← `(tactic| simp only [← $newEqIdent] at $declIdent $declIdent)) : ?m.10095
simp (← `(tactic| simp only [← $newEqIdent] at $declIdent $declIdent)) : ?m.10095
(← `(tactic| simp only [← $newEqIdent] at $declIdent $declIdent)) : ?m.10095
only (← `(tactic| simp only [← $newEqIdent] at $declIdent $declIdent)) : ?m.10095
[← $ newEqIdent (← `(tactic| simp only [← $newEqIdent] at $declIdent $declIdent)) : ?m.10095
] (← `(tactic| simp only [← $newEqIdent] at $declIdent $declIdent)) : ?m.10095
at (← `(tactic| simp only [← $newEqIdent] at $declIdent $declIdent)) : ?m.10095
$ declIdent (← `(tactic| simp only [← $newEqIdent] at $declIdent $declIdent)) : ?m.10095
$ declIdent (← `(tactic| simp only [← $newEqIdent] at $declIdent $declIdent)) : ?m.10095
))
evalTactic evalTactic (← `(tactic| simp only [← $newEqIdent])) : TacticM (?m.8616 y✝ ) (← `(tactic| simp only [← $newEqIdent])) : ?m.10753
(← `(tactic| (← `(tactic| simp only [← $newEqIdent])) : ?m.10753
simp (← `(tactic| simp only [← $newEqIdent])) : ?m.10753
(← `(tactic| simp only [← $newEqIdent])) : ?m.10753
only (← `(tactic| simp only [← $newEqIdent])) : ?m.10753
[← $ newEqIdent (← `(tactic| simp only [← $newEqIdent])) : ?m.10753
]))
-- Clear the temporary hypothesis used for the new variable name if applicable
if isNewVar && ! isNewEq then
evalTactic evalTactic (← `(tactic| clear $newEqIdent)) : TacticM (?m.11067 y✝ ) (← `(tactic| clear $newEqIdent)) : ?m.11333
(← `(tactic| (← `(tactic| clear $newEqIdent)) : ?m.11333
clear (← `(tactic| clear $newEqIdent)) : ?m.11333
$ newEqIdent (← `(tactic| clear $newEqIdent)) : ?m.11333
))
-- Clear the "using" hypothesis if it's a variable in the context
if prf . isFVar && ! keepUsing then
let some hUsingStx := hUsing | throwError "lift tactic failed: unreachable code was reached" : ?m.12599 ?m.12600
throwError throwError "lift tactic failed: unreachable code was reached" : ?m.12599 ?m.12600
"lift tactic failed: unreachable code was reached"
evalTactic (← `(tactic| clear $hUsingStx)) : ?m.11965
(← `(tactic| (← `(tactic| clear $hUsingStx)) : ?m.11965
clear (← `(tactic| clear $hUsingStx)) : ?m.11965
$ hUsingStx (← `(tactic| clear $hUsingStx)) : ?m.11965
))
evalTactic evalTactic (← `(tactic| try clear $hUsingStx)) : TacticM (?m.11625 y✝ ) (← `(tactic| try clear $hUsingStx)) : ?m.12360
(← `(tactic| (← `(tactic| try clear $hUsingStx)) : ?m.12360
try (← `(tactic| try clear $hUsingStx)) : ?m.12360
(← `(tactic| try clear $hUsingStx)) : ?m.12360
clear (← `(tactic| try clear $hUsingStx)) : ?m.12360
$ hUsingStx (← `(tactic| try clear $hUsingStx)) : ?m.12360
))
if hUsing . isNone then withMainContext <| setGoals ( prf . mvarId! :: (← getGoals ) )
elab_rules : tactic
| `(tactic| lift $ e to $ t $[ using $ h ]?) => withMainContext <| Lift.main e t h none none False
elab_rules : tactic | `(tactic| lift $ e to $ t $[ using $ h ]?
with $ newVarName ) => withMainContext <| Lift.main e t h newVarName none False
elab_rules : tactic | `(tactic| lift $ e to $ t $[ using $ h ]?
with $ newVarName $ newEqName ) => withMainContext <| Lift.main e t h newVarName newEqName False
elab_rules : tactic | `(tactic| lift $ e to $ t $[ using $ h ]?
with $ newVarName $ newEqName $ newPrfName ) => withMainContext do
if h . isNone then Lift.main e t h newVarName newEqName False
else
let some h := h | unreachable!
if h . raw == newPrfName then Lift.main e t h newVarName newEqName True
else Lift.main e t h newVarName newEqName False
end Mathlib.Tactic