Skip to content
This repository was archived by the owner on Jul 24, 2024. It is now read-only.

Commit 61fa489

Browse files
committed
feat(tactic/trunc_cases): a tactic for case analysis on trunc hypotheses (#2368)
``` /-- Perform case analysis on a `trunc` expression, preferentially using the recursor `trunc.rec_on_subsingleton` when the goal is a subsingleton, and using `trunc.rec` otherwise. Additionally, if the new hypothesis is a type class, reset the instance cache. -/ ```
1 parent 3cc7a32 commit 61fa489

File tree

6 files changed

+196
-17
lines changed

6 files changed

+196
-17
lines changed

src/tactic/core.lean

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -925,10 +925,16 @@ do l ← local_context,
925925
r ← successes (l.reverse.map (λ h, cases h >> skip)),
926926
when (r.empty) failed
927927

928-
/-- Given a proof `pr : t`, adds `h : t` to the current context, where the name `h` is fresh. -/
929-
meta def note_anon (e : expr) : tactic expr :=
930-
do n ← get_unused_name "lh",
931-
note n none e
928+
/--
929+
`note_anon t v`, given a proof `v : t`,
930+
adds `h : t` to the current context, where the name `h` is fresh.
931+
932+
`note_anon none v` will infer the type `t` from `v`.
933+
-/
934+
-- While `note` provides a default value for `t`, it doesn't seem this could ever be used.
935+
meta def note_anon (t : option expr) (v : expr) : tactic expr :=
936+
do h ← get_unused_name `h none,
937+
note h t v
932938

933939
/-- `find_local t` returns a local constant with type t, or fails if none exists. -/
934940
meta def find_local (t : pexpr) : tactic expr :=

src/tactic/equiv_rw.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ do
192192
e ← equiv_rw_type e x_ty cfg,
193193
eq ← to_expr ``(%%x' = equiv.symm %%e (equiv.to_fun %%e %%x')),
194194
prf ← to_expr ``((equiv.symm_apply_apply %%e %%x').symm),
195-
h ← assertv_fresh eq prf,
195+
h ← note_anon eq prf,
196196
-- Revert the new hypothesis, so it is also part of the goal.
197197
revert h,
198198
ex ← to_expr ``(equiv.to_fun %%e %%x'),

src/tactic/finish.lean

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,6 @@ declare_trace auto.finish
4646

4747
namespace tactic
4848

49-
/-- call `(assert n t)` with a fresh name `n`. -/
50-
meta def assert_fresh (t : expr) : tactic expr :=
51-
do n ← get_unused_name `h none,
52-
assert n t
53-
54-
/-- call `(assertv n t v)` with a fresh name `n`. -/
55-
meta def assertv_fresh (t : expr) (v : expr) : tactic expr :=
56-
do h ← get_unused_name `h none,
57-
assertv h t v
58-
5949
namespace interactive
6050

6151
meta def revert_all := tactic.revert_all
@@ -261,7 +251,7 @@ meta def do_substs : tactic unit := do_subst >> repeat do_subst
261251
and returns `tt` if anything nontrivial has been added. -/
262252
meta def add_conjuncts : expr → expr → tactic bool :=
263253
λ pr t,
264-
let assert_consequences := λ e t, mcond (add_conjuncts e t) skip (assertv_fresh t e >> skip) in
254+
let assert_consequences := λ e t, mcond (add_conjuncts e t) skip (note_anon t e >> skip) in
265255
do t' ← whnf_reducible t,
266256
match t' with
267257
| `(%%a ∧ %%b) :=

src/tactic/interval_cases.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ Here `hl` should be an expression of the form `a ≤ n`, for some explicit `a`,
192192
`hu` should be of the form `n < b`, for some explicit `b`.
193193
-/
194194
meta def interval_cases_using (hl hu : expr) : tactic unit :=
195-
to_expr ``(mem_set_elems (Ico _ _) ⟨%%hl, %%hu⟩) >>= note_anon >>= fin_cases_at none
195+
to_expr ``(mem_set_elems (Ico _ _) ⟨%%hl, %%hu⟩) >>= note_anon none >>= fin_cases_at none
196196

197197
setup_tactic_parser
198198

src/tactic/trunc_cases.lean

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/-
2+
Copyright (c) 2020 Scott Morrison. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Scott Morrison
5+
-/
6+
import tactic.chain
7+
import tactic.doc_commands
8+
import data.quot
9+
10+
namespace tactic
11+
12+
/-- Auxiliary tactic for `trunc_cases`. -/
13+
private meta def trunc_cases_subsingleton (e : expr) (ids : list name) : tactic expr :=
14+
do
15+
-- When the target is a subsingleton,
16+
-- we can just use induction along `trunc.rec_on_subsingleton`,
17+
-- generating just a single goal.
18+
[(_, [e], _)] ← tactic.induction e ids `trunc.rec_on_subsingleton,
19+
return e
20+
21+
/-- Auxiliary tactic for `trunc_cases`. -/
22+
private meta def trunc_cases_nondependent (e : expr) (ids : list name) : tactic expr :=
23+
do
24+
-- We may as well just use `trunc.lift_on`.
25+
-- (It would be nice if we could use the `induction` tactic with non-dependent recursors, too?)
26+
-- (In fact, the general strategy works just as well here,
27+
-- except that it leaves a beta redex in the invariance goal.)
28+
to_expr ``(trunc.lift_on %%e) >>= tactic.fapply,
29+
-- Replace the hypothesis `e` with the unboxed version.
30+
tactic.clear e,
31+
e ← tactic.intro e.local_pp_name,
32+
-- In the invariance goal, introduce the two arguments using the specified identifiers
33+
tactic.swap,
34+
match ids.nth 1 with
35+
| some n := tactic.intro n
36+
| none := tactic.intro1
37+
end,
38+
match ids.nth 2 with
39+
| some n := tactic.intro n
40+
| none := tactic.intro1
41+
end,
42+
tactic.swap,
43+
return e
44+
45+
/-- Auxiliary tactic for `trunc_cases`. -/
46+
private meta def trunc_cases_dependent (e : expr) (ids : list name) : tactic expr :=
47+
do
48+
-- If all else fails, just use the general induction principle.
49+
[(_, [e], _), (_, [e_a, e_b, e_p], _)] ← tactic.induction e ids,
50+
-- However even now we can do something useful:
51+
-- the invariance goal has a useless `e_p : true` hypothesis,
52+
-- and after casing on that we may be able to simplify away
53+
-- the `eq.rec`.
54+
swap, (tactic.cases e_p >> `[try { simp only [eq_rec_constant] }]), swap,
55+
return e
56+
57+
namespace interactive
58+
59+
open interactive
60+
open interactive.types
61+
open tactic
62+
63+
/--
64+
`trunc_cases e` performs case analysis on a `trunc` expression `e`,
65+
attempting the following strategies:
66+
1. when the goal is a subsingleton, calling `induction e using trunc.rec_on_subsingleton`,
67+
2. when the goal does not depend on `e`, calling `fapply trunc.lift_on e`,
68+
and using `intro` and `clear` afterwards to make the goals look like we used `induction`,
69+
3. otherwise, falling through to `trunc.rec_on`, and in the new invariance goal
70+
calling `cases h_p` on the useless `h_p : true` hypothesis,
71+
and then attempting to simplify the `eq.rec`.
72+
73+
`trunc_cases e with h` names the new hypothesis `h`.
74+
If `e` is a local hypothesis already,
75+
`trunc_cases` defaults to reusing the same name.
76+
77+
`trunc_cases e with h h_a h_b` will use the names `h_a` and `h_b` for the new hypothesis
78+
in the invariance goal if `trunc_cases` uses `trunc.lift_on` or `trunc.rec_on`.
79+
80+
Finally, if the new hypothesis from inside the `trunc` is a type class,
81+
`trunc_cases` resets the instance cache so that it is immediately available.
82+
-/
83+
meta def trunc_cases (e : parse texpr) (ids : parse with_ident_list) : tactic unit :=
84+
do
85+
e ← to_expr e,
86+
-- If `ids = []` and `e` is a local constant, we'll want to give
87+
-- the new unboxed hypothesis the same name.
88+
let ids := if ids = [] ∧ e.is_local_constant then [e.local_pp_name] else ids,
89+
-- Make a note of the expr `e`, or reuse `e` if it is already a local constant.
90+
e ← if e.is_local_constant then
91+
return e
92+
else
93+
(do n ← match ids.nth 0 with | some n := pure n | none := mk_fresh_name end, note n none e),
94+
-- Now check if the target is a subsingleton.
95+
tgt ← target,
96+
ss ← succeeds (mk_app `subsingleton [tgt] >>= mk_instance),
97+
-- In each branch here, we're going to capture the name of the new unboxed hypothesis
98+
-- so that we can later check if it's a typeclass and if so unfreeze local instances.
99+
e ← if ss then trunc_cases_subsingleton e ids
100+
else if e.occurs tgt then trunc_cases_dependent e ids
101+
else trunc_cases_nondependent e ids,
102+
c ← infer_type e >>= is_class,
103+
when c unfreeze_local_instances
104+
105+
end interactive
106+
end tactic
107+
108+
add_tactic_doc
109+
{ name := "trunc_cases",
110+
category := doc_category.tactic,
111+
decl_names := [`tactic.interactive.trunc_cases],
112+
tags := ["case bashing"] }

test/trunc_cases.lean

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import tactic.trunc_cases
2+
import tactic.interactive
3+
import data.quot
4+
5+
example (t : trunc ℕ) : punit :=
6+
begin
7+
trunc_cases t,
8+
exact (),
9+
-- no more goals, because `trunc_cases` used the correct `trunc.rec_on_subsingleton` recursor
10+
end
11+
12+
example (t : trunc ℕ) : ℕ :=
13+
begin
14+
trunc_cases t,
15+
guard_hyp t := ℕ, -- verify that the new hypothesis is still called `t`.
16+
exact 0,
17+
-- verify that we don't even need to use `simp`,
18+
-- because `trunc_cases` has already removed the `eq.rec`.
19+
refl,
20+
end
21+
22+
example {α : Type} [subsingleton α] (I : trunc (has_zero α)) : α :=
23+
begin
24+
trunc_cases I,
25+
exact 0,
26+
end
27+
28+
/-- A mock typeclass, set up so that it's possible to extract data from `trunc (has_unit α)`. -/
29+
class has_unit (α : Type) [has_one α] :=
30+
(unit : α)
31+
(unit_eq_one : unit = 1)
32+
33+
def u {α : Type} [has_one α] [has_unit α] : α := has_unit.unit α
34+
attribute [simp] has_unit.unit_eq_one
35+
36+
example {α : Type} [has_one α] (I : trunc (has_unit α)) : α :=
37+
begin
38+
trunc_cases I,
39+
exact u, -- Verify that the typeclass is immediately available
40+
-- Verify that there's no `eq.rec` in the goal.
41+
(do tgt ← tactic.target, eq_rec ← tactic.mk_const `eq.rec, guard $ ¬ eq_rec.occurs tgt),
42+
simp [u],
43+
end
44+
45+
universes v w z
46+
47+
/-- Transport through a product is given by individually transporting each component. -/
48+
-- It's a pity that this is no good as a `simp` lemma.
49+
-- (It seems the unification problem with `λ a, W a × Z a` is too hard.)
50+
-- (One could write a tactic to syntactically analyse `eq.rec` expressions
51+
-- and simplify more of them!)
52+
lemma eq_rec_prod {α : Sort v} (W : α → Type w) (Z : α → Type z) {a b : α} (p : W a × Z a) (h : a = b) :
53+
@eq.rec α a (λ a, W a × Z a) p b h = (@eq.rec α a W p.1 b h, @eq.rec α a Z p.2 b h) :=
54+
begin
55+
cases h,
56+
simp only [prod.mk.eta],
57+
end
58+
59+
-- This time, we make a goal that (quite artificially) depends on the `trunc`.
60+
example {α : Type} [has_one α] (I : trunc (has_unit α)) : α × plift (I = I) :=
61+
begin
62+
-- This time `trunc_cases` has no choice but to use `trunc.rec_on`.
63+
trunc_cases I,
64+
{ exact ⟨u, plift.up rfl⟩, },
65+
{ -- And so we get an `eq.rec` in the invariance goal.
66+
-- Since `simp` can't handle it because of the unification problem,
67+
-- for now we have to handle it by hand.
68+
convert eq_rec_prod (λ I, α) (λ I, plift (I = I)) _ _,
69+
{ simp [u], },
70+
{ ext, } }
71+
end

0 commit comments

Comments
 (0)