Zulip Chat Archive
Stream: metaprogramming / tactics
Topic: auto specialize
Johan Commelin (Jul 01 2021 at 13:51):
Can someone please hack a tactic together that adds f x
to the tactic state for all f
and x
in the local context for which f x
typechecks? (It shouldn't throw away f
or x
.)
Eric Wieser (Jul 01 2021 at 13:58):
Recursively?
Johan Commelin (Jul 01 2021 at 13:59):
No, I can call repeat { foo }
if I need that.
Yakov Pechersky (Jul 01 2021 at 14:46):
import tactic.basic
open lean lean.parser tactic interactive interactive.types expr
meta def tactic.specialize_all (n : name) (l : loc) : tactic unit := do
val ← get_local n,
hs ← l.get_locals,
hs.mmap' (λ h,
try (do
let h' := expr.mk_app h [val],
t' ← infer_type h',
note (h.local_pp_name ++ n) none h'))
namespace tactic.interactive
meta def specialize_all (val : parse ident) (loc : parse location) : tactic unit :=
tactic.specialize_all val loc
end tactic.interactive
example {α β γ : Type*}
(f : α → β)
(g : α → β)
(h : β → γ)
(x y : α) : true := begin
specialize_all x at *,
extract_goal,
trivial
end
/--
example {α : Type ?} {β : Type ?} {γ : Type ?}
(f g : α → β)
(h : β → γ)
(x y : α)
(f.x g.x : β)
(h.x : γ) :
true :=
begin
admit,
end
-/
Yakov Pechersky (Jul 01 2021 at 14:49):
As you can see, it's not perfect because applying x to h shouldn't work.
Johan Commelin (Jul 01 2021 at 15:12):
Is there an easy way to fix that?
Johan Commelin (Jul 01 2021 at 15:13):
Note that the version that I'm interested in is morally specialize_all * at *
.
Yakov Pechersky (Jul 01 2021 at 17:01):
import tactic.basic
open lean lean.parser tactic interactive interactive.types expr
meta def tactic.specialize_single (n : name) (l : loc) : tactic unit := do
val ← get_local n,
valt ← infer_type val,
hs ← l.get_locals,
hs.mmap' (λ h, do
ht ← infer_type h,
try $ match ht with
| (pi _ _ vt _) := do
is_def_eq vt valt >> note (h.local_pp_name ++ n) none (h.mk_app [val]) >> pure ()
| (lam _ _ vt _) := do
is_def_eq vt valt >> note (h.local_pp_name ++ n) none (h.mk_app [val]) >> pure ()
| _ := pure ()
end)
meta def tactic.specialize_all (hs : list simp_arg_type) (l : loc) : tactic unit := do
(lp, ln, ln', b) ← decode_simp_arg_list hs,
lp.mmap' (λ e, do
e ← to_expr e,
if e.is_local_constant then tactic.specialize_single e.local_pp_name l
else pure ()),
if !b then pure () else (do
ls ← tactic.local_context,
ls.mmap' (λ e, do
if expr.is_local_constant e then tactic.specialize_single e.local_pp_name l else pure ()),
pure ())
namespace tactic.interactive
meta def specialize_all1 (val : parse ident) (loc : parse location) : tactic unit :=
tactic.specialize_single val loc
meta def specialize_all (vals : parse simp_arg_list) (loc : parse location) : tactic unit :=
tactic.specialize_all vals loc
end tactic.interactive
example {α β γ : Type*}
(f : α → β)
(g : α → β)
(h : β → γ)
(x y : α) (z : β) : γ := begin
specialize_all [*] at *,
extract_goal,
exact h.z
end
/--
example {α : Type ?} {β : Type ?} {γ : Type ?}
(f g : α → β)
(h : β → γ)
(x y : α)
(z f.x g.x f.y g.y : β)
(h.z : γ) :
γ :=
begin
admit,
end
-/
Yakov Pechersky (Jul 01 2021 at 17:01):
It can be made neater with loc.try_apply
, but this works for your specifications at least.
Yakov Pechersky (Jul 01 2021 at 17:02):
And works if you repeat it, as you expect.
Johan Commelin (Jul 01 2021 at 17:16):
@Yakov Pechersky wonderful! thank you so much!
Johan Commelin (Jul 01 2021 at 17:38):
@Yakov Pechersky one more plea:
variables {X Y Z : Type}
def surjective (f : X → Y) :=
∀ y, ∃ x, f x = y
example (f : X → Y) (g : Y → Z) (hyp : surjective (g ∘ f)) :
surjective g :=
begin
intro z,
unfold surjective at hyp, -- can we avoid this step?
specialize_all [*] at *,
tactic.auto_cases,
tactic.auto_cases,
simp * at *,
end
Johan Commelin (Jul 01 2021 at 17:38):
I think that currently it is not "aggresive" enough. It only works for things that are visibly pi-like.
Eric Wieser (Jul 01 2021 at 18:11):
how docs tactic#specialize do it? Can we just copy the approach used there?
Yakov Pechersky (Jul 01 2021 at 18:28):
specialize f x y z
does it by basically doing replace f := f x y z
. I don't know what happens if one did a type-illogical specialize f f nat
or something like that. So specialize
does no typechecking other than what happens in i_to_expr
. I can fake it with something like e <-
(%%hyp %%val)` and hope that this deals with reducibility properly.
Jannis Limperg (Jul 01 2021 at 18:32):
Should be enough to whnf
before the match
with the right reducibility setting.
Johan Commelin (Jul 01 2021 at 19:24):
I tried
ht' ← whnf ht transparency.reducible,
try $ match ht' with
but that didn't help
Yakov Pechersky (Jul 01 2021 at 19:39):
import tactic.basic
open lean lean.parser tactic interactive interactive.types expr
meta def tactic.specialize_single (n : name) (l : loc) : tactic unit := do
val ← get_local n,
valt ← infer_type val,
hs ← l.get_locals,
hs.mmap' (λ h, try (do
ht ← infer_type h,
e ← to_expr ``(%%h %%val),
et ← infer_type e,
note (h.local_pp_name ++ n) none e))
meta def tactic.specialize_all (hs : list simp_arg_type) (l : loc) : tactic unit := do
(lp, ln, ln', b) ← decode_simp_arg_list hs,
lp.mmap' (λ e, do
e ← to_expr e,
if e.is_local_constant then tactic.specialize_single e.local_pp_name l
else pure ()),
if !b then pure () else (do
ls ← tactic.local_context,
ls.mmap' (λ e, do
if expr.is_local_constant e then tactic.specialize_single e.local_pp_name l else pure ()),
pure ())
namespace tactic.interactive
meta def specialize_all1 (val : parse ident) (loc : parse location) : tactic unit :=
tactic.specialize_single val loc
meta def specialize_all (vals : parse simp_arg_list) (loc : parse location) : tactic unit :=
tactic.specialize_all vals loc
end tactic.interactive
variables {X Y Z : Type}
def surjective (f : X → Y) :=
∀ y, ∃ x, f x = y
example (f : X → Y) (g : Y → Z) (hyp : surjective (g ∘ f)) :
surjective g :=
begin
intro z,
specialize_all [*] at *,
tactic.auto_cases,
tactic.auto_cases,
simp * at *,
end
Yakov Pechersky (Jul 01 2021 at 19:40):
So my brute-force "create pexpr, elaborate it, make sure it is valid type" approach also works.
Johan Commelin (Jul 01 2021 at 19:45):
Great! That does exactly what I want!
Johan Commelin (Jul 02 2021 at 07:54):
I think this is a tactic that could potentially be nice to add to tidy
.
Johan Commelin (Jul 02 2021 at 07:55):
But there is one catch: currently, if you have f : X -> Y
and x : X
, then repeatedly calling specialize_all [*] at *
will add many copies of f x : Y
. So we somehow need to teach the tactic that it should fail if it isn't adding anything "new".
Johan Commelin (Jul 02 2021 at 07:56):
But even then there is a catch. Because with endomorphisms f : X -> X
and x : X
, you will end up getting f (f (f x))
etc...
Johan Commelin (Jul 02 2021 at 07:57):
I'm not sure whether such loops can be easily detected. So we might need to run the tactic on some gas
(and if it is added to tidy
, then tidy
would need to run on gas as well).
Johan Commelin (Jul 02 2021 at 07:58):
Finally, I think that tidy
could use a auto_choose
tactic, that breaks open existential hypotheses, even if the goal is a type, instead of a prop.
Johan Commelin (Jul 02 2021 at 07:58):
With a combi of specialize_all
and auto_choose
, we could have a tidy
that automatically proves surjective (g \circ f) -> surjective g
.
Eric Wieser (Jul 02 2021 at 10:40):
Why do you need auto_choose
for that surjective proof?
Johan Commelin (Jul 02 2021 at 11:02):
Because fsplit
might turn \ex x, f x = y
into two goals x : X
and f m_1? = y
before we get a chance to do cases
on the existential hyp.
Johan Commelin (Jul 02 2021 at 11:02):
So then you are left with the X
goal, and you need to use choice to extract a witness out of the existential hyp.
Johan Commelin (Jul 02 2021 at 11:02):
I agree that this will produce a "overpowered" proof, and choice can be avoided. But I don't really care too much, in the case of tidy
proofs.
Last updated: Dec 20 2023 at 11:08 UTC