Zulip Chat Archive
Stream: lean4
Topic: simp without rfl
xiao (May 10 2024 at 08:30):
I come from Coq. In Coq, if you want to prove not true = false
, you have to first simplify not true
to false
, then use rfl
to prove the equality by reflexivity.
Example not_true: (negb true) = false.
Proof. simpl. reflexivity. Qed.
But in lean, a single simp
will finish the proof.
example: not true = false := by
simp
Is there any tactic to simplify terms without finishing the proof just like Coq? I know lean's behavior might be useful, but if I want to explain to others how a proof assistant works, I need those intermediate steps like true=true
to illustrate Curry-Howard correspondence.
Mario Carneiro (May 10 2024 at 08:47):
I don't believe so. You can use rewrite [not]
or unfold not
to unfold not
without trying to close the goal, but this yields a match expression and the usual tool to reduce these, dsimp only
, also tries to close syntactic reflexivity goals, and I don't believe there is a simp option to turn this off
Mario Carneiro (May 10 2024 at 08:50):
of course you can prevent it from closing the goal by not making it a syntactic reflexivity:
def myFalse := false
example: not true = myFalse := by
dsimp only [not]
-- ⊢ false = myFalse
xiao (May 10 2024 at 09:24):
Then it is a bit disappointing. I think the essence behind simp is trying all equalities, but they are propositional ones, while the simp in Coq only does beta reductions till a normal form, which is a judgemental(definitional) equality. I don’t understand why dsimp also tries to close a propositional equality as it is called definitional.
Mario Carneiro (May 10 2024 at 09:28):
definitional equalities in lean are usually written using =
and a proof using rfl
Joël Riou (May 10 2024 at 09:29):
dsimp
tries only those simp
lemmas that are definitional equalities (proved by rfl
).
Mario Carneiro (May 10 2024 at 09:29):
ultimately this is just a tactic trying to be useful and not create busywork for the user
Mario Carneiro (May 10 2024 at 09:29):
it is possible to make your own dsimp'
that skips this step
Mario Carneiro (May 10 2024 at 09:32):
oh, you can also use the dsimp
conv it seems, this one does not try to close the goal... although I think the conv
block itself might
Mario Carneiro (May 10 2024 at 09:38):
import Lean
namespace MyDSimp
open Lean.Parser.Tactic
syntax (name := dsimp') "dsimp'" (config)? (discharger)? (&" only")?
(" [" withoutPosition((simpErase <|> simpLemma),*,?) "]")? (location)? : tactic
open Lean Elab Tactic Meta Simp
def dsimpGoal' (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (simplifyTarget : Bool := true) (fvarIdsToSimp : Array FVarId := #[])
(stats : Stats := {}) : MetaM (Option MVarId × Stats) := do
mvarId.withContext do
mvarId.checkNotAssigned `simp
let mut mvarIdNew := mvarId
let mut stats : Stats := stats
for fvarId in fvarIdsToSimp do
let type ← instantiateMVars (← fvarId.getType)
let (typeNew, stats') ← dsimp type ctx simprocs
stats := stats'
if typeNew.isFalse then
mvarIdNew.assign (← mkFalseElim (← mvarIdNew.getType) (mkFVar fvarId))
return (none, stats)
if typeNew != type then
mvarIdNew ← mvarIdNew.replaceLocalDeclDefEq fvarId typeNew
if simplifyTarget then
let target ← mvarIdNew.getType
let (targetNew, stats') ← dsimp target ctx simprocs stats
stats := stats'
-- if targetNew.isTrue then
-- mvarIdNew.assign (mkConst ``True.intro)
-- return (none, stats)
-- if let some (_, lhs, rhs) := targetNew.consumeMData.eq? then
-- if (← withReducible <| isDefEq lhs rhs) then
-- mvarIdNew.assign (← mkEqRefl lhs)
-- return (none, stats)
if target != targetNew then
mvarIdNew ← mvarIdNew.replaceTargetDefEq targetNew
if ctx.config.failIfUnchanged && mvarId == mvarIdNew then
throwError "dsimp made no progress"
return (some mvarIdNew, stats)
def dsimpLocation' (ctx : Simp.Context) (simprocs : Simp.SimprocsArray) (loc : Location) : TacticM Unit := do
match loc with
| Location.targets hyps simplifyTarget =>
withMainContext do
let fvarIds ← getFVarIds hyps
go fvarIds simplifyTarget
| Location.wildcard =>
withMainContext do
go (← (← getMainGoal).getNondepPropHyps) (simplifyTarget := true)
where
go (fvarIdsToSimp : Array FVarId) (simplifyTarget : Bool) : TacticM Unit := withSimpDiagnostics do
let mvarId ← getMainGoal
let (result?, stats) ← dsimpGoal' mvarId ctx simprocs (simplifyTarget := simplifyTarget) (fvarIdsToSimp := fvarIdsToSimp)
match result? with
| none => replaceMainGoal []
| some mvarId => replaceMainGoal [mvarId]
if tactic.simp.trace.get (← getOptions) then
mvarId.withContext <| traceSimpCall (← getRef) stats.usedTheorems
return stats.diag
@[tactic dsimp'] def evalDSimp' : Tactic := fun stx => do
let { ctx, simprocs, .. } ← withMainContext <| mkSimpContext stx (eraseLocal := false) (kind := .dsimp)
dsimpLocation' ctx simprocs (expandOptLocation stx[5])
end MyDSimp
example: not true = false := by
dsimp' [not]; rfl
Arthur Paulino (May 10 2024 at 10:30):
xiao said:
Then it is a bit disappointing
This is similar to my first frustration with Lean, since I had started learning about theorem proving in Coq and I wanted Lean to behave in the same way. But later on I realized that tactics are really customizable (though with a learning curve), as Mario has just shown
Mario Carneiro (May 10 2024 at 11:45):
and just to be clear, I didn't write all that code myself, it's a copy paste of the original dsimp
with the offending lines commented out
xiao (May 10 2024 at 18:03):
Thank you guys. I finally noticed that lean admits uniqueness of identity proof (uip), which is not admitted in Coq. Since I am learning homotopy type theory, the equalities used in definitions (judgemental, in meta language) and the provable equalities (propositional) are significantly different to me. This is why I tend to use a simplification only for computation (judgement).
theorem uip.{u}: forall (A: Sort u) (x y: A) (p q: x=y), p = q := by
simp
Last updated: May 02 2025 at 03:31 UTC