Zulip Chat Archive
Stream: mathlib4
Topic: dsimp only for universal cleanup
Kyle Miller (Apr 22 2023 at 09:13):
Whether or not there is a pretty printer option, it would be nice if there was a post-tactic cleanup hook. Imagine you could tell Lean to run dsimp only at *
after every tactic completes. Then students wouldn't need to see beta reduceable lambdas, they wouldn't be frustrated by linarith not working when it obviously should, and no one would have to make their own copies of every tactic.
Yaël Dillies (Apr 22 2023 at 09:16):
I was thinking, could it be as simple as (famous last words :upside_down:) overriding the tactic block macro so that it inserts those dsimp
after each tactic call?
Heather Macbeth (Apr 22 2023 at 09:16):
For technical reasons I don't think that would be convenient for teaching, since dsimp only
resolves numeric goals about Nats.
example : (3:Nat) * 5 = 15 := by dsimp only -- works
See lean4#1994.
Yaël Dillies (Apr 22 2023 at 09:17):
dsimp
has extensive configuration options, though. You can instruct it to only beta-reduce.
Heather Macbeth (Apr 22 2023 at 09:17):
Oh, what's that option?
Yaël Dillies (Apr 22 2023 at 09:17):
.. unless those numeric goals also are beta reduction? I would think not, and instead come from the decide
call.
Heather Macbeth (Apr 22 2023 at 09:18):
dsimp
doesn't call decide
any more.
Heather Macbeth (Apr 22 2023 at 09:18):
Yaël Dillies (Apr 22 2023 at 09:19):
Hmm, puzzling. Let me do some trace-gazing.
Yaël Dillies (Apr 22 2023 at 09:24):
Okay, docs4#Lean.Meta.Simp.Config is the relevant declaration. I wish it was easier to find :sad:
Yaël Dillies (Apr 22 2023 at 09:25):
I assume simp
and dsimp
both use this as configuration? Not so sure, actually...
Eric Wieser (Apr 22 2023 at 09:25):
dsimp only
is sometimes harmful if you have something like (fun x => 1 * x) '' s
, which in lean3 it would reduce to has_mul.mul 1 '' s
Yaël Dillies (Apr 22 2023 at 09:27):
Ahah! There's also docs4#Lean.Meta.DSimp.Config
Yaël Dillies (Apr 22 2023 at 09:32):
This looks like it's above my paygrade, but here's my attempt
def heatherConfig : Lean.Meta.DSimp.Config where
zeta := false
beta := true
eta := false
etaStruct := .none
iota := false
proj := false
decide := false
autoUnfold := false
example (x : ℕ × ℕ) : (x.1, x.2) = x := by dsimp -- works
example (x : ℕ × ℕ) : (x.1, x.2) = x := by dsimp (config := heatherConfig) -- works??
example : (3:Nat) * 5 = 15 := by dsimp (config := heatherConfig) -- works??
Heather Macbeth (Apr 22 2023 at 09:34):
I believe the point is what I wrote up in lean4#1994, as I linked before:
example : (3:Nat) * 5 = 15 := by with_reducible rfl
also works.
Heather Macbeth (Apr 22 2023 at 09:34):
Here's a previous discussion:
https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/use/near/317444788
Yaël Dillies (Apr 22 2023 at 09:35):
Does dsimp
try rfl
?
Notification Bot (Apr 22 2023 at 09:45):
17 messages were moved here from #mathlib4 > pp.beta by Heather Macbeth.
Heather Macbeth (Apr 22 2023 at 18:02):
I wouldn't be surprised if dsimp
tried with_reducible rfl
. But the real reason I suspect it's the same issue is that it's again a Nat-only issue.
Kyle Miller (Apr 22 2023 at 18:19):
Here's a beta_reduce
tactic that definitely only does beta reduction and nothing else:
import Lean
import Mathlib.Tactic.Basic
namespace Mathlib.Tactic
open Lean Parser Tactic Elab Tactic Meta
syntax (name := beta_reduce) "beta_reduce" (ppSpace location)? : tactic
elab_rules : tactic | `(tactic| beta_reduce $[$loc]?) => do
withLocation (expandOptLocation (Lean.mkOptionalNode loc))
(atLocal := fun h ↦ do
let ty ← Core.betaReduce (← instantiateMVars (← h.getType))
let newGoal ← (← getMainGoal).changeLocalDecl' h ty
replaceMainGoal [newGoal])
(atTarget := withMainContext do
let ty ← Core.betaReduce (← getMainTarget)
let newGoal ← (← getMainGoal).change ty
replaceMainGoal [newGoal])
(failed := fun _ ↦ throwError "beta_reduce failed")
end Mathlib.Tactic
example : (fun _ => 3) () * 5 = 15 := by
beta_reduce
guard_target =ₛ 3 * 5 = 15
rfl
example (h : x = (fun _ => 3) ()) : x * 5 = 15 := by
beta_reduce at h
guard_hyp h :ₛ x = 3
rw [h]
Kyle Miller (Apr 22 2023 at 18:20):
I haven't found where this can be hooked into anything in a general way though...
Kyle Miller (Apr 22 2023 at 18:21):
This lets you do beta_reduce at *
Heather Macbeth (Apr 22 2023 at 18:25):
Kyle Miller said:
I haven't found where this can be hooked into anything in a general way though...
If you do find it, this seems like a good solution.
Kyle Miller (Apr 22 2023 at 18:37):
Since we're here, I figured it would be worth abstracting that code a bit to be able to quickly define tactics for other kinds of transformations, for example whnf
.
def myeq {α : Sort _} (x y : α) := x = (fun _ => y) ()
example (h : myeq x 3) : x * 5 = 15 := by
whnf at h
guard_hyp h :ₛ x = (fun _ => 3) ()
beta_reduce at h
guard_hyp h :ₛ x = 3
rw [h]
code
Anand Rao Tadipatri (Apr 23 2023 at 08:47):
I have extended @Kyle Miller's code to implement @Yaël Dillies's suggestion of modifying the by
tactic to perform a beta-reduction after each tactic call.
--import Mathlib.Lean.Expr.Basic
import Lean
import Mathlib.Tactic.Basic
namespace Mathlib.Tactic
open Lean Elab Parser Term Meta Tactic
/-- `f` is some function that transforms a type to a defeq type. -/
def mkTypeTransformerTactic (name : String) (f : Expr → TacticM Expr)
(loc : Option (TSyntax `Lean.Parser.Tactic.location)) : TacticM Unit :=
withLocation (expandOptLocation (Lean.mkOptionalNode loc))
(atLocal := fun h ↦ do
let newGoal ← (← getMainGoal).changeLocalDecl h <| ← f (← instantiateMVars (← h.getType))
replaceMainGoal [newGoal])
(atTarget := withMainContext do
let newGoal ← (← getMainGoal).change <| ← f (← getMainTarget)
replaceMainGoal [newGoal])
(failed := fun _ ↦ throwError "{name} failed")
syntax (name := beta_reduce) "beta_reduce" (ppSpace location)? : tactic
elab_rules : tactic | `(tactic| beta_reduce $[$loc]?) => do
mkTypeTransformerTactic "beta_reduce" (fun e => Core.betaReduce e) loc
syntax (name := whnf) "whnf" (ppSpace location)? : tactic
elab_rules : tactic | `(tactic| whnf $[$loc]?) => do
mkTypeTransformerTactic "whnf" (fun e => Meta.whnf e) loc
end Mathlib.Tactic
example : (fun _ => 3) () * 5 = 15 := by
beta_reduce
guard_target =ₛ 3 * 5 = 15
rfl
example (h : x = (fun _ => 3) ()) : x * 5 = 15 := by
beta_reduce at h
guard_hyp h :ₛ x = 3
rw [h]
def myeq {α : Sort _} (x y : α) := x = (fun _ => y) ()
example (h : myeq x 3) : x * 5 = 15 := by
whnf at h
guard_hyp h :ₛ x = (fun _ => 3) ()
beta_reduce at h
guard_hyp h :ₛ x = 3
rw [h]
namespace Array
def interleave (as : Array α) (x : α) : Array α := Id.run do
let mut r := #[]
for a in as do
r := r.push x
r := r.push a
return r
end Array
namespace Mathlib.Tactic
open Lean Elab Parser Term Meta Tactic
-- a clone of the `by` tactic
syntax (name := byTactic') "by' " tacticSeq : term
@[term_elab byTactic'] def elabByTactic' : TermElab := fun stx expectedType? => do
match expectedType? with
| some expectedType =>
let mvar ← mkFreshExprMVar expectedType MetavarKind.syntheticOpaque
let mvarId := mvar.mvarId!
let ref ← getRef
registerSyntheticMVar ref mvarId <| SyntheticMVarKind.tactic stx (← saveContext)
return mvar
| none =>
tryPostpone
throwError ("invalid 'by\'' tactic, expected type has not been provided")
macro_rules
| `(by $[$tacs]*) => do
let tacs' := tacs.interleave <| ← `(tactic| beta_reduce at *)
`(by' $[$tacs']*)
end Mathlib.Tactic
example (h : myeq x 3) : x * 5 = 15 := by
whnf at h
rw [h]
This almost does what is needed - in the final example, the hypothesis is automatically simplified to x = 3
when the cursor is placed before rw [h]
. However, I don't know how to make the change happen immediately, instead of on the next line.
Kyle Miller (Apr 23 2023 at 08:53):
@Anand Rao Nice; maybe there's a way to tag the beta_reduce at *
tactic with a fake source position so that the generated info is available where you'd expect.
I wish overriding by
were a complete solution. This doesn't help with ·
tactic blocks, or blocks of tactics that appear inside other tactics (like structured cases
or induction
) unfortunately, since those call evalTactic
themselves directly without going through by
syntax.
Kyle Miller (Apr 23 2023 at 09:01):
If there were a way to override evalTactic
at runtime then that would be a clear place to insert a hook... (Lean 3 would let you do this, but since Lean 4 is compiled this doesn't seem to be possible.)
If evalTactic started with a call to some user-defined hook (ideally, something to wrap the whole evaluation, like what profileitM
is doing), then we could insert this beta_reduce
cleanup code easily.
Anand Rao Tadipatri (Apr 23 2023 at 09:22):
@Kyle Miller I think it might be still possible to get this to work by overriding the by
tactic block. The problem you mentioned seems to arise precisely when a tactic uses a tacticSeq
as a part of its syntax. I have been working on intercepting the by
tactic for unrelated reasons (my code is here), and I think I might be able to adapt this to produce a "nested" version of the code I posted above.
Kyle Miller (Apr 23 2023 at 09:30):
I wonder if we could take the raw Syntax
, scan through the whole thing looking for tacticSeq
, and then intersperse whatever we want between everything. That might save you some work in your traceTacticSnap
function, so you don't have to parse every piece of tactic syntax yourself. On the other hand, it might not be 100% accurate to do what I'm suggesting.
Anand Rao Tadipatri (Apr 23 2023 at 09:40):
That seems like a sound approach that should work in general - even with completely new tactics that use tacticSeq
as a part of their syntax. I don't mind doing a case-by-case analysis of the tactic syntax for now though, since there are not too many tactics in Lean core and mathlib4
that use tacticSeq
. I'd like to implement try your approach too, since it is probably more robust and I'll get to learn how to do a deep traversal of a syntax tree.
Yaël Dillies (Apr 23 2023 at 09:46):
rw [a, b, c]
is morally rw a, rw b, rw c
, so I'm wondering whether we should have somewhere in the tactic framework a "tactic clock" that each tactic is free to tick when a tactic call "morally" happened. So for example every tactic would tick at least once, and rw [a, b, c]
would tick thrice. Then we could run the tactic hook at every tactic tick.
Kyle Miller (Apr 23 2023 at 09:55):
@Yaël Dillies rw
is implemented as a macro that expands out to three rewrite
tactics in that case, so that should happen automatically (if I understand it correctly -- edit I didn't, it doesn't work this way).
Anand Rao Tadipatri (Apr 23 2023 at 10:00):
Here is a version of my code that works for "nested" tactic sequences:
--import Mathlib.Lean.Expr.Basic
import Lean
import Mathlib.Tactic.Basic
open Lean Elab Parser Term Meta Tactic
namespace Mathlib.Tactic
/-- `f` is some function that transforms a type to a defeq type. -/
def mkTypeTransformerTactic (name : String) (f : Expr → TacticM Expr)
(loc : Option (TSyntax `Lean.Parser.Tactic.location)) : TacticM Unit :=
withLocation (expandOptLocation (Lean.mkOptionalNode loc))
(atLocal := fun h ↦ do
let newGoal ← (← getMainGoal).changeLocalDecl h <| ← f (← instantiateMVars (← h.getType))
replaceMainGoal [newGoal])
(atTarget := withMainContext do
let newGoal ← (← getMainGoal).change <| ← f (← getMainTarget)
replaceMainGoal [newGoal])
(failed := fun _ ↦ throwError "{name} failed")
syntax (name := beta_reduce) "beta_reduce" (ppSpace location)? : tactic
elab_rules : tactic | `(tactic| beta_reduce $[$loc]?) => do
mkTypeTransformerTactic "beta_reduce" (fun e => Core.betaReduce e) loc
syntax (name := whnf) "whnf" (ppSpace location)? : tactic
elab_rules : tactic | `(tactic| whnf $[$loc]?) => do
mkTypeTransformerTactic "whnf" (fun e => Meta.whnf e) loc
end Mathlib.Tactic
example : (fun _ => 3) () * 5 = 15 := by
beta_reduce
guard_target =ₛ 3 * 5 = 15
rfl
example (h : x = (fun _ => 3) ()) : x * 5 = 15 := by
beta_reduce at h
guard_hyp h :ₛ x = 3
rw [h]
def myeq {α : Sort _} (x y : α) := x = (fun _ => y) ()
example (h : myeq x 3) : x * 5 = 15 := by
whnf at h
guard_hyp h :ₛ x = (fun _ => 3) ()
beta_reduce at h
guard_hyp h :ₛ x = 3
rw [h]
namespace Array
def interleave (as : Array α) (x : α) : Array α := Id.run do
let mut r := #[]
for a in as do
r := r.push x
r := r.push a
return r
end Array
syntax (name := reduce_and_eval_tac) "reduce_and_eval_tac" tactic : tactic
syntax (name := reduce_seq) "reduce_seq" tacticSeq : tactic
macro_rules
| `(tactic| reduce_seq $[$tacs]*) => do
let tacs' ← tacs.mapM <| fun tac ↦ `(tactic| reduce_and_eval_tac $tac)
`(tactic| {$[$tacs']*})
def matchAltTac := Term.matchAlt (rhsParser := matchRhs)
@[tactic reduce_and_eval_tac]
def reduceAndEvalTac : Tactic
| `(tactic| reduce_and_eval_tac $tac) =>
match tac with
| `(tactic| focus $[$tacs]*) => do
evalTactic <| ← `(tactic| focus reduce_seq $[$tacs]*)
| `(tactic| · $[$tacs]*) => do
evalTactic <| ← `(tactic| · reduce_seq $[$tacs]*)
| `(tactic| {$[$tacs]*}) => do
evalTactic <| ← `(tactic| {reduce_seq $[$tacs]*})
| `(tactic| ($[$tacs]*)) => do
evalTactic <| ← `(tactic| (reduce_seq $[$tacs]*))
| `(tactic| case $[$tag $hs*]|* =>%$arr $tac:tacticSeq) => do
evalTactic <| ← `(tactic| case $[$tag $hs*]|* =>%$arr reduce_seq $tac:tacticSeq)
| `(tactic| case' $[$tag $hs*]|* =>%$arr $tac:tacticSeq) => do
evalTactic <| ← `(tactic| case' $[$tag $hs*]|* =>%$arr reduce_seq $tac:tacticSeq)
| `(tactic| induction $[$ts],* $[using $id:ident]? $[generalizing $gs*]? with $[$tac]? $is*) => do
let is' : TSyntaxArray ``inductionAlt ←
is.mapM <| fun
| `(inductionAlt| $il* => $ts:tacticSeq) => `(inductionAlt| $il* => reduce_seq $ts)
| i => return ⟨i⟩
evalTactic <| ← `(tactic| induction $[$ts],* $[using $id:ident]? $[generalizing $gs*]? with $[$tac]? $is'*)
| `(tactic| cases $[$cs],* $[using $id:ident]? with $[$tac]? $is*) => do
let is' : TSyntaxArray ``inductionAlt ←
is.mapM <| fun
| `(inductionAlt| $il* => $ts:tacticSeq) => `(inductionAlt| $il* => reduce_seq $ts)
| i => return ⟨i⟩
evalTactic <| ← `(tactic| cases $[$cs],* $[using $id:ident]? with $[$tac]? $is'*)
| `(tactic| match $[$gen]? $[$motive]? $discrs,* with $alts:matchAlt*) => do
let alts' : TSyntaxArray ``matchAlt ←
alts.mapM <| fun
| `(matchAltTac| | $[$pats,*]|* => $rhs:tacticSeq) => do
let alt ← `(matchAltTac| | $[$pats,*]|* => reduce_seq $rhs)
return ⟨alt⟩
| alt => return ⟨alt⟩
evalTactic <| ← `(tactic| match $[$gen]? $[$motive]? $discrs,* with $alts':matchAlt*)
| _ => do
evalTactic <| ← `(tactic| beta_reduce at *)
evalTactic tac
| _ => panic!"Invalid syntax for `eval_tac_and_reduce`."
namespace Mathlib.Tactic
-- a clone of the `by` tactic
syntax (name := byTactic') "by' " tacticSeq : term
@[term_elab byTactic'] def elabByTactic' : TermElab := fun stx expectedType? => do
match expectedType? with
| some expectedType =>
let mvar ← mkFreshExprMVar expectedType MetavarKind.syntheticOpaque
let mvarId := mvar.mvarId!
let ref ← getRef
registerSyntheticMVar ref mvarId <| SyntheticMVarKind.tactic stx (← saveContext)
return mvar
| none =>
tryPostpone
throwError ("invalid 'by\'' tactic, expected type has not been provided")
macro_rules
| `(by $tacs) => `(by' reduce_seq $tacs)
end Mathlib.Tactic
set_option linter.unreachableTactic false
example (h : myeq x 3) : 1 = 1 ∧ x * 5 = 15 := by
refine' ⟨_, _⟩
. rfl
· whnf at h
rw [h]
Anand Rao Tadipatri (Apr 23 2023 at 10:05):
The rough idea is that I have two new tactics - reduce_and_eval_tactic
and reduce_seq
. The first is meant to run beta_reduce at *
and then evaluate the given tactic, while the second is meant to modify a tacticSeq
to perform the intermediate beta-reduction. The behaviour of reduce_and_eval_tac
for ·
tactic blocks, induction
applications, and a few other cases is handled in a special way by making a call to reduce_seq
. My code covers most of the main tactics in Lean4 and mathlib4
that use tacticSeq
; there are a few like classical
and permute_goals
that I have excluded for now.
Anand Rao Tadipatri (Apr 23 2023 at 10:18):
@Yaël Dillies I have modified my code to perform a beta-reduction after each rewrite:
--import Mathlib.Lean.Expr.Basic
import Lean
import Mathlib.Tactic.Basic
open Lean Elab Parser Term Meta Tactic
namespace Mathlib.Tactic
/-- `f` is some function that transforms a type to a defeq type. -/
def mkTypeTransformerTactic (name : String) (f : Expr → TacticM Expr)
(loc : Option (TSyntax `Lean.Parser.Tactic.location)) : TacticM Unit :=
withLocation (expandOptLocation (Lean.mkOptionalNode loc))
(atLocal := fun h ↦ do
let newGoal ← (← getMainGoal).changeLocalDecl h <| ← f (← instantiateMVars (← h.getType))
replaceMainGoal [newGoal])
(atTarget := withMainContext do
let newGoal ← (← getMainGoal).change <| ← f (← getMainTarget)
replaceMainGoal [newGoal])
(failed := fun _ ↦ throwError "{name} failed")
syntax (name := beta_reduce) "beta_reduce" (ppSpace location)? : tactic
elab_rules : tactic | `(tactic| beta_reduce $[$loc]?) => do
mkTypeTransformerTactic "beta_reduce" (fun e => Core.betaReduce e) loc
syntax (name := whnf) "whnf" (ppSpace location)? : tactic
elab_rules : tactic | `(tactic| whnf $[$loc]?) => do
mkTypeTransformerTactic "whnf" (fun e => Meta.whnf e) loc
end Mathlib.Tactic
example : (fun _ => 3) () * 5 = 15 := by
beta_reduce
guard_target =ₛ 3 * 5 = 15
rfl
example (h : x = (fun _ => 3) ()) : x * 5 = 15 := by
beta_reduce at h
guard_hyp h :ₛ x = 3
rw [h]
def myeq {α : Sort _} (x y : α) := x = (fun _ => y) ()
example (h : myeq x 3) : x * 5 = 15 := by
whnf at h
guard_hyp h :ₛ x = (fun _ => 3) ()
beta_reduce at h
guard_hyp h :ₛ x = 3
rw [h]
namespace Array
def interleave (as : Array α) (x : α) : Array α := Id.run do
let mut r := #[]
for a in as do
r := r.push x
r := r.push a
return r
end Array
syntax (name := reduce_and_eval_tac) "reduce_and_eval_tac" tactic : tactic
syntax (name := reduce_seq) "reduce_seq" tacticSeq : tactic
macro_rules
| `(tactic| reduce_seq $[$tacs]*) => do
let tacs' ← tacs.mapM <| fun tac ↦ `(tactic| reduce_and_eval_tac $tac)
`(tactic| {$[$tacs']*})
def matchAltTac := Term.matchAlt (rhsParser := matchRhs)
@[tactic reduce_and_eval_tac]
def reduceAndEvalTac : Tactic
| `(tactic| reduce_and_eval_tac $tac) =>
match tac with
| `(tactic| focus $[$tacs]*) => do
evalTactic <| ← `(tactic| focus reduce_seq $[$tacs]*)
| `(tactic| · $[$tacs]*) => do
evalTactic <| ← `(tactic| · reduce_seq $[$tacs]*)
| `(tactic| {$[$tacs]*}) => do
evalTactic <| ← `(tactic| {reduce_seq $[$tacs]*})
| `(tactic| ($[$tacs]*)) => do
evalTactic <| ← `(tactic| (reduce_seq $[$tacs]*))
| `(tactic| case $[$tag $hs*]|* =>%$arr $tac:tacticSeq) => do
evalTactic <| ← `(tactic| case $[$tag $hs*]|* =>%$arr reduce_seq $tac:tacticSeq)
| `(tactic| case' $[$tag $hs*]|* =>%$arr $tac:tacticSeq) => do
evalTactic <| ← `(tactic| case' $[$tag $hs*]|* =>%$arr reduce_seq $tac:tacticSeq)
| `(tactic| rw $[$cfg]? [$rs,*] $[$loc]?) => do
evalTactic <| ← `(tactic| beta_reduce at *)
for r in (rs : TSyntaxArray `Lean.Parser.Tactic.rwRule) do
evalTactic <| ← `(tactic| beta_reduce at *)
evalTactic <| ← `(tactic| rw $[$cfg]? [$r] $[$loc]?)
if (← getUnsolvedGoals).length > 0 then
evalTactic <| ← `(tactic| beta_reduce at *)
| `(tactic| erw [$rs,*] $[$loc]?) => do
evalTactic <| ← `(tactic| beta_reduce at *)
for r in (rs : TSyntaxArray `Lean.Parser.Tactic.rwRule) do
evalTactic <| ← `(tactic| beta_reduce at *)
evalTactic <| ← `(tactic| erw [$r] $[$loc]?)
if (← getUnsolvedGoals).length > 0 then
evalTactic <| ← `(tactic| beta_reduce at *)
| `(tactic| rwa [$rs,*] $[$loc]?) => do
evalTactic <| ← `(tactic| beta_reduce at *)
for r in (rs : TSyntaxArray `Lean.Parser.Tactic.rwRule) do
evalTactic <| ← `(tactic| beta_reduce at *)
evalTactic <| ← `(tactic| rw [$r] $[$loc]?)
evalTactic <| ← `(tactic| beta_reduce at *)
evalTactic <| ← `(tactic| assumption)
| `(tactic| induction $[$ts],* $[using $id:ident]? $[generalizing $gs*]? with $[$tac]? $is*) => do
let is' : TSyntaxArray ``inductionAlt ←
is.mapM <| fun
| `(inductionAlt| $il* => $ts:tacticSeq) => `(inductionAlt| $il* => reduce_seq $ts)
| i => return ⟨i⟩
evalTactic <| ← `(tactic| induction $[$ts],* $[using $id:ident]? $[generalizing $gs*]? with $[$tac]? $is'*)
| `(tactic| cases $[$cs],* $[using $id:ident]? with $[$tac]? $is*) => do
let is' : TSyntaxArray ``inductionAlt ←
is.mapM <| fun
| `(inductionAlt| $il* => $ts:tacticSeq) => `(inductionAlt| $il* => reduce_seq $ts)
| i => return ⟨i⟩
evalTactic <| ← `(tactic| cases $[$cs],* $[using $id:ident]? with $[$tac]? $is'*)
| `(tactic| match $[$gen]? $[$motive]? $discrs,* with $alts:matchAlt*) => do
let alts' : TSyntaxArray ``matchAlt ←
alts.mapM <| fun
| `(matchAltTac| | $[$pats,*]|* => $rhs:tacticSeq) => do
let alt ← `(matchAltTac| | $[$pats,*]|* => reduce_seq $rhs)
return ⟨alt⟩
| alt => return ⟨alt⟩
evalTactic <| ← `(tactic| match $[$gen]? $[$motive]? $discrs,* with $alts':matchAlt*)
| _ => do
evalTactic <| ← `(tactic| beta_reduce at *)
evalTactic tac
if (← getUnsolvedGoals).length > 0 then
evalTactic <| ← `(tactic| beta_reduce at *)
| _ => panic!"Invalid syntax for `eval_tac_and_reduce`."
namespace Mathlib.Tactic
-- a clone of the `by` tactic
syntax (name := byTactic') "by' " tacticSeq : term
@[term_elab byTactic'] def elabByTactic' : TermElab := fun stx expectedType? => do
match expectedType? with
| some expectedType =>
let mvar ← mkFreshExprMVar expectedType MetavarKind.syntheticOpaque
let mvarId := mvar.mvarId!
let ref ← getRef
registerSyntheticMVar ref mvarId <| SyntheticMVarKind.tactic stx (← saveContext)
return mvar
| none =>
tryPostpone
throwError ("invalid 'by\'' tactic, expected type has not been provided")
macro_rules
| `(by $tacs) => `(by' reduce_seq $tacs)
end Mathlib.Tactic
set_option linter.unreachableTactic false
example (h : myeq x 3) : 1 = 1 ∧ x * 5 = 15 := by
refine' ⟨_, _⟩
. rfl
· whnf at h
simp [h]
Anand Rao Tadipatri (Apr 23 2023 at 10:19):
This is correct only if the substitution rw [a, b, c]
-> rw [a]; rw [b]; rw [c]
is valid in Lean.
Ruben Van de Velde (Apr 23 2023 at 10:59):
It's not valid if rw [a, b]
already gets the goal in a state where the implicit rfl
solves it - in that case rw [c]
separately will fail with "no goals"
Kyle Miller (Apr 23 2023 at 11:11):
Here's another implementation that overrides a number of sequencing forms and doesn't peer into too much syntax, so it's fairly short. It's not able to do anything between rw
steps though (I was wrong about how rw
works -- handles inserting tactic info step by step itself, and it doesn't macro expand into anything).
import Lean
import Mathlib.Tactic.Basic
import Mathlib.Tactic.Linarith
namespace Mathlib.Tactic
open Lean Parser Tactic Elab Tactic Meta
/-- Runs `f` on every local variable and the target. `f` is allowed to fail, and if it
does that local variable or target is not changed. -/
def transformAllTypes (mvarId : MVarId) (f : Expr → MetaM Expr) := do
let mut mvarId := mvarId
for fvarId in (← mvarId.getDecl).lctx.getFVarIds.reverse do
try
let ty := ((← mvarId.getDecl).lctx.get! fvarId).type
mvarId ← mvarId.changeLocalDecl' fvarId <| ← f (← instantiateMVars ty)
catch _ => pure ()
try
mvarId ← mvarId.change <| ← f (← instantiateMVars (← mvarId.getType))
catch _ => pure ()
return mvarId
/-- Beta reduce all local variables and targets across all goals. -/
def betaReduceEverything : TacticM Unit := do
let gs ← getGoals
let gs' ← gs.mapM (fun g => transformAllTypes g (fun e => Core.betaReduce e))
setGoals gs'
/-- `tactic_wrapper` is inserted around every tactic in a tactic sequence.
Implement this tactic to alter the behavior of the tactic processor. -/
syntax (name := wrapper) "tactic_wrapper " tactic : tactic
def sepByTacticWrapper (stx : Syntax) : TacticM Syntax := do
let .node info kind args := stx | throwUnsupportedSyntax
let mut args' := #[]
for arg in args, i in [:args.size] do
if i % 2 == 0 then
if arg.getKind == ``wrapper then
args' := args'.push arg
else
args' := args'.push <| (← withRef arg `(tactic| tactic_wrapper $(.mk arg))).raw
else
args' := args'.push arg
return .node info kind args'
/-- Inserts a `tactic_wrapper` around every tactic in a tactic sequence.
Different sequence-like tactics store their tactic sequences in different syntax positions. -/
def transformTacticSeq (stx : Syntax) (pos : Nat) : TacticM Syntax := do
let .node info kind args := stx | throwUnsupportedSyntax
let args' := args.setD pos (← sepByTacticWrapper (args.getD pos default))
return .node info kind args'
@[tactic tacticSeq1Indented] def evalTacticSeq1Indented' : Tactic := fun stx => do
evalTacticSeq1Indented (← transformTacticSeq stx 0)
@[tactic tacticSeqBracketed] def evalTacticSeqBracketed' : Tactic := fun stx => do
evalTacticSeqBracketed (← transformTacticSeq stx 1)
@[tactic cdot] def evalTacticCDot' : Tactic := fun stx => do
evalTacticCDot (← transformTacticSeq stx 1)
@[tactic seq1] def evalSeq1' : Tactic := fun stx => do
evalSeq1 (← transformTacticSeq stx 0)
elab_rules : tactic
| `(tactic| tactic_wrapper $tac) => do
--logInfo m!"tactic: {tac}"
betaReduceEverything
evalTactic tac
betaReduceEverything
--logInfo m!"finished {tac}"
end Mathlib.Tactic
example : (fun _ => 3) () * 5 = 15 := by
guard_target =ₛ 3 * 5 = 15
rfl
example (h : x = (fun _ => 3) ()) : x * 5 = 15 := by
guard_hyp h :ₛ x = 3
rw [h]
example (h : (fun _ => x) () = y) (h' : y = z) : x = z := by
rw [h, h']
example (f : ℤ → ℤ) (h : 0 < (fun y => - f y) x + 1) : f x < 1 := by
linarith -- works! (would usually need `dsimp at h`)
example (n : Nat) (f : ℤ → ℤ) (h : 0 < (fun y => - f y) x + 1) : f x < 1 := by
cases n with
| zero =>
change 0 < (fun y => - f y) x + 1 at h
linarith
| succ => linarith
Kyle Miller (Apr 23 2023 at 11:19):
This at least solves the problem I ran into myself with Tutorial 0079 and linarith. Here's an analog:
example (f : ℤ → ℤ) (h : 0 < (fun y => - f y) x + 1) : f x < 1 := by
linarith -- works! (would usually need `dsimp at h`)
Kyle Miller (Apr 23 2023 at 11:21):
Here's a test that it still works even with structured tactics:
example (n : Nat) (f : ℤ → ℤ) (h : 0 < - f x + 1) : f x < 1 := by
cases n with
| zero =>
change 0 < (fun y => - f y) x + 1 at h
linarith
| succ => linarith
Kyle Miller (Apr 23 2023 at 11:55):
@Anand Rao It doesn't appear to be necessary to do it, but if you're interested here's another version that overrides by
without needing to re-implement by
or create any new syntax. It does a syntax transformation ahead of time, while also overriding the other sequencing tactics just in case there's a macro expansion and we want to intervene.
code
Kyle Miller (Apr 23 2023 at 11:59):
@Heather Macbeth I think so long as you don't have any student tacticians who might try writing something too fancy, the meta code in this comment should do global beta reduction between all tactic steps, except for inside tactics like rw
. (Caveat emptor: it should probably be better tested!)
Yaël Dillies (Apr 23 2023 at 12:09):
Wow! Progress was quick.
Anand Rao Tadipatri (Apr 23 2023 at 12:12):
Kyle Miller said:
Anand Rao It doesn't appear to be necessary to do it, but if you're interested here's another version that overrides
by
without needing to re-implementby
or create any new syntax. It does a syntax transformation ahead of time, while also overriding the other sequencing tactics just in case there's a macro expansion and we want to intervene.
Thanks! This is very helpful.
Kyle Miller (Apr 23 2023 at 12:13):
I'm surprised it worked out! @Anand Rao's idea of transforming the syntax (which I hadn't considered myself since it seemed impractical) ended up actually being practical, which I'm not sure I'd have believed without seeing the first implementation.
Yaël Dillies (Apr 23 2023 at 12:13):
Kyle Miller said:
I do want to mention that in Patrick's tutorials, number 0079 is a source of periodic questions since pp.beta is set to true but it's easy to get lambdas in this exercise, and these cause linarith to mysteriously fail.
Heather Macbeth (Apr 23 2023 at 15:17):
Thank you for working on this, Anand and Kyle!
I tried it and it solves my beta-reduction problem with Function.Injective
beautifully. Here is another related annoyance which it doesn't solve:
notation3 "forall_sufficiently_large "(...)", "r:(scoped P => ∃ C, ∀ x ≥ C, P x) => r
example : forall_sufficiently_large n : ℤ, n ^ 3 ≥ 4 * n ^ 2 + 7 := by
use 5
sorry
(Need to add the import
import Mathlib.Mathport.Notation
to Kyle's code.)
Heather Macbeth (Apr 23 2023 at 15:18):
I get the goal state
⊢ ∀ (x : ℤ), x ≥ 5 → x ^ 3 ≥ 4 * x ^ 2 + 7
after the use 5
, just as desired. But the goal state before the use
is not beta-reduced:
⊢ ∃ C, ∀ (x : ℤ), x ≥ C → (fun n => n ^ 3 ≥ 4 * n ^ 2 + 7) x
Heather Macbeth (Apr 23 2023 at 15:20):
Some ideas for fixing this:
- also beta-reduce at the start of the tactic block, before any tactic?
- fix this one by just pretty-printing with
pp.beta
(once that's implemented) and ignoring the internals? - fix my "sufficiently large" notation so that it pretty-prints (it doesn't currently) so that this issue is moot?
Yaël Dillies (Apr 23 2023 at 15:46):
I'm now wondering whether beta-reducing everything in sight might have undesirable side effects. It will turn ∑ x, f x
into finset.univ.sum f
, which is unfortunate. Is it possible to tag specific arguments to a function as "beta-irreducible"? (Note, this isn't specific to this "beta-reduce everything" approach. I personally get this kind of misbehavior a lot when using simp_rw
)
Kyle Miller (Apr 23 2023 at 15:51):
It looks like it's beta reducing is happening before use
, but it's just not being reported if your cursor is in any of the whitespace leading up to use
. Your cursor has to be right before the u
. I haven't been able to figure out how to fix this (even using the idea to wrap the contents of a by
block with the beta reducer).
Kyle Miller (Apr 23 2023 at 15:51):
@Yaël Dillies Isn't that eta reduction, not beta reduction?
Heather Macbeth (Apr 23 2023 at 15:54):
Maybe then we want a hybrid approach: the po.beta option to make it display correctly, together with the tactic block beta-reduction-at-each-step to make it behave correctly.
Kyle Miller (Apr 23 2023 at 15:56):
Another question is whether we can make a hook of our own to preprocess expressions before they're pretty printed, since then we can ensure we perform the exact same transformations.
Kyle Miller (Apr 23 2023 at 16:05):
@Heather Macbeth Here's a "pretty printer option" hack for pp.beta
:
open Lean PrettyPrinter Delaborator
def betaReduceFirst : Delab := do
let e ← SubExpr.getExpr
let e' ← Core.betaReduce e
if e == e' then failure
withTheReader SubExpr (fun cfg => { cfg with expr := e'}) delab
attribute [delab app, delab lam, delab forallE, delab letE, delab mdata, delab proj]
betaReduceFirst
Kyle Miller (Apr 23 2023 at 16:08):
Maybe it's safe (or even better) doing just delab app
rather than every expression type, and if so there's probably a better function than Core.betaReduce
that would just beta reduce the current application.
(attribute [delab app] betaReduceFirst
alone definitely works for the forall_sufficiently_large
example)
Heather Macbeth (Apr 23 2023 at 16:13):
Nice!! Thanks!
Yaël Dillies (Apr 23 2023 at 16:13):
Oh yeah, sorry. I shall repost my concerns in an eta thread.
Heather Macbeth (Apr 23 2023 at 16:14):
@Kyle Miller Can you PR it? It would be nice to make sure this continues to work, I'm not confident in my ability to maintain it.
Reid Barton (Apr 23 2023 at 16:33):
Would it be reasonable/possible to make the notation forall_sufficiently_large
itself beta reduce the application P x
?
Kyle Miller (Apr 23 2023 at 16:47):
@Reid Barton Looks like it. This seems to work:
syntax (name := beta_reduce) "beta_reduced% " term : term
@[term_elab beta_reduce] def elabBetaReduce : Elab.Term.TermElab := fun stx expectedType? => do
let e ← Elab.Term.elabTerm stx[1] expectedType?
let e ← Core.betaReduce e
return e
notation3 "forall_sufficiently_large "(...)", "r:(scoped P => ∃ C, ∀ x ≥ C, beta_reduced% P x) => r
Reid Barton (Apr 23 2023 at 16:50):
Nice! Does Core.betaReduce
only beta-reduce the outermost application, or could it also apply to stuff inside P
?
Kyle Miller (Apr 23 2023 at 16:51):
It's global, so this would be more controlled:
syntax (name := beta_reduce) "beta_reduce_app% " term : term
@[term_elab beta_reduce] def elabBetaReduce : Elab.Term.TermElab := fun stx expectedType? => do
let e ← Elab.Term.elabTerm stx[1] expectedType?
let e := Expr.beta e.getAppFn e.getAppArgs
return e
notation3 "forall_sufficiently_large "(...)", "r:(scoped P => ∃ C, ∀ x ≥ C, beta_reduce_app% P x) => r
Kyle Miller (Apr 23 2023 at 16:52):
It might still beta reduce more than you bargained for, but at least it's localized to a single application sequence.
Last updated: Dec 20 2023 at 11:08 UTC