Zulip Chat Archive
Stream: mathlib4
Topic: Difference in `simp` behaviour
Chris Hughes (Dec 07 2022 at 14:56):
The following proof works in Lean3 but not Lean4. I'm a bit surprised that it does work in Lean3, but the fact that it doesn't work breaks a bunch of proofs in Data.Set.Basic
. What's the best fix to this?
import logic.basic
example (p : Prop) (q : p → Prop) (h : p) :
(∀ (h2 : p), q h2) ↔ q h :=
by simp only [h, forall_true_left]
Kevin Buzzard (Dec 07 2022 at 17:54):
The problem seems to be the following:
import logic.basic
example (p : Prop) (q : p → Prop) (h : p) :
(∀ (h2 : p), q h2) ↔ q h :=
begin
have foo : p = true := by simp only [h],
simp only [foo], -- `⊢ (∀ (h : true), q _) ↔ q h`
simp only [iff_self, forall_true_left],
end
The simp only [foo]
line works in Lean 3, but does nothing in Lean 4:
import Mathlib.Logic.Basic
example (p : Prop) (q : p → Prop) (h : p) :
(∀ (h2 : p), q h2) ↔ q h := by
have foo : p = True := by simp only [h]
simp only [foo] -- no change in goal in Lean 4
-- rw [foo] -- motive not type correct :-/
subst foo
simp -- works fine now
David Renshaw (Dec 07 2022 at 17:55):
This vaguely reminds me of https://github.com/leanprover/lean4/issues/1549
David Renshaw (Dec 07 2022 at 17:56):
I wonder if there's a way to add a type annotation to make it work (like this workaround).
Scott Morrison (Dec 07 2022 at 22:39):
Can we #mwe this for an issue on the Lean 4 repo?
Jakob von Raumer (Dec 07 2022 at 23:09):
slightly shorter but not much
example (q : p → Prop) (h : p = True)
(h' : ∀(q : True → Prop), (∀ x, q x) ↔ q True.intro) : (∀ h', q h') ↔ q (h.symm ▸ trivial) := by
simp only [h, h'] -- no change in goal in Lean 4
-- rw [h] -- motive not type correct :-/
subst h
simp [h'] -- works fine now
Jakob von Raumer (Dec 07 2022 at 23:10):
I'll file it. I noticed this a few months ago as well but thought it was just a lack of @[simp]
s
Scott Morrison (Dec 07 2022 at 23:11):
It would be best if you can minimise this so it doesn't include Mathlib at all
Scott Morrison (Dec 07 2022 at 23:11):
Just copy-paste anything needed from Logic.Basic. The Lean 4 devs will need a test case.
Jakob von Raumer (Dec 07 2022 at 23:16):
Right, edited
Scott Morrison (Dec 08 2022 at 00:24):
Just adding the link to Jakob's issue here: https://github.com/leanprover/lean4/issues/1926
Heather Macbeth (Dec 08 2022 at 13:46):
In #903 I encountered a bug which I think is related. If someone can sanity-check this then I'll add it to the issue Jakob opened. The following fails in Lean 4 but works in Lean 3:
example (P Q R : Prop) (hQ : Q = False) (hR : R = False) : P ∨ Q ↔ P ∨ R :=
by simp only [hQ, hR]
Yakov Pechersky (Dec 08 2022 at 14:13):
Does adding docs#iff_self to the list help?
Heather Macbeth (Dec 08 2022 at 14:20):
It does, but that doesn't seem to explain the difference between the Lean 3 and Lean 4 behaviour?
Kevin Buzzard (Dec 08 2022 at 14:32):
Lean 3:
set_option trace.simplify.rewrite true
example (P Q R : Prop) (hQ : Q = false) (hR : R = false) : P ∨ Q ↔ P ∨ R :=
by simp only [hQ, hR] -- works
/-
debugging output:
0. [simplify.rewrite] [hQ]: Q ==> false
0. [simplify.rewrite] [hR]: R ==> false
-/
-- but
example (P : Prop) : P ∨ false ↔ P ∨ false := by simp only -- fails
Lean 3 simp
is doing some kind of magic. Is it trying refl
if a rewrite succeeds or something?
Heather Macbeth (Dec 08 2022 at 14:37):
It's strange because P ∨ false ↔ P ∨ false
isn't even true by refl!
Heather Macbeth (Dec 08 2022 at 14:37):
Or more precisely, it's true by refl
but not with rfl
. Maybe that's an important difference.
Yaël Dillies (Dec 08 2022 at 14:38):
It's true by docs#iff.rfl
Yaël Dillies (Dec 08 2022 at 14:38):
Maybe a missing @[refl]
tag on Iff.rfl
?
Yaël Dillies (Dec 08 2022 at 14:38):
Oh i guess not, because Sorry, got Lean 3 and Lean 4 confused.refl
works here.
Kevin Buzzard (Dec 08 2022 at 14:39):
I think that lean 3 rw
tries tactic refl
if it succeeds.
Heather Macbeth (Dec 08 2022 at 14:41):
example (P : Prop) : P ∨ False ↔ P ∨ False := by rfl
fails in Lean 4, but
example (P : Prop) : P ∨ false ↔ P ∨ false := by refl
succeeds in Lean 3.
Kevin Buzzard (Dec 08 2022 at 15:01):
Regarding the initial issue of rewriting under binders: the minimised example in the github issue doesn't work in lean 3 either without imports. In fact it seems that import logic.basic
changes the behaviour of simp only
. This surprised me! Lean 3 code:
import logic.basic
example (p : Prop) (q : p → Prop) (h : p) :
(∀ (h2 : p), q h2) ↔ q h := by {
have foo : p = true := by simp only [h],
-- goal currently `⊢ (∀ (h2 : p), q h2) ↔ q h`
simp only [foo], -- goal changes to `⊢ (∀ (h : true), q _) ↔ q h`
sorry,
}
If you comment out the mathlib3 import, simp only [foo]
fails to make progress. So the mathlib import is changing the behaviour of simp only
. How?? Note that this means that the original issue in this conversation might not be a Lean 4 issue at all, perhaps mathlib3 is doing something clever which mathlib4 isn't doing. Note in particular that the claim in the lean 4 issue that lean 3 does something different is incorrect as it stands, and also the original issue Chris posted is behaving the same way in bare lean 3 and bare lean 4; it's only when mathlib gets involved that simp only
becomes more powerful in lean 3.
Kevin Buzzard (Dec 08 2022 at 15:07):
Got it: (again this is Lean 3 code)
-- comment out this lemma to make `simp only [foo]` fail below
@[congr] lemma forall_prop_congr' {p p' : Prop} {q q' : p → Prop}
(hq : ∀ h, q h ↔ q' h) (hp : p ↔ p') : (∀ h, q h) = ∀ h : p', q' (hp.2 h) :=
sorry
example (p : Prop) (q : p → Prop) (h : p) :
(∀ (h2 : p), q h2) ↔ q h := by {
have foo : p = true := by simp only [h],
-- goal currently `⊢ (∀ (h2 : p), q h2) ↔ q h`
simp only [foo], -- goal changes to `⊢ (∀ (h : true), q _) ↔ q h`
sorry,
}
So adding a congr
lemma can change the behaviour of simp only
in Lean 3 and this is perhaps the real cause of the original issue.
Heather Macbeth (Dec 08 2022 at 15:09):
Nice detective work. Inter alia, you're saying that my issue is not related to Jakob's issue, right?
Kevin Buzzard (Dec 08 2022 at 15:11):
Tactic rfl
in Lean 4 only tries eq.refl
because it was written before the @[refl]
attribute. I haven't got back to your issue yet but I'll get there :-)
Kevin Buzzard (Dec 08 2022 at 15:12):
Chris' original issue in Lean 3:
@[simp] lemma forall_true_left (p : true → Prop) : (∀ x, p x) ↔ p true.intro :=
sorry
example (p : Prop) (q : p → Prop) (h : p) :
(∀ (h2 : p), q h2) ↔ q h :=
by simp only [h, forall_true_left] -- fails
@[congr] lemma forall_prop_congr' {p p' : Prop} {q q' : p → Prop}
(hq : ∀ h, q h ↔ q' h) (hp : p ↔ p') : (∀ h, q h) = ∀ h : p', q' (hp.2 h) :=
sorry
example (p : Prop) (q : p → Prop) (h : p) :
(∀ (h2 : p), q h2) ↔ q h :=
by simp only [h, forall_true_left] -- works
Notification Bot (Dec 08 2022 at 15:12):
16 messages were moved here from #mathlib4 > Difference in simp
behaviour by Heather Macbeth.
Notification Bot (Dec 08 2022 at 15:12):
16 messages were moved from this topic to #mathlib4 > simp
(or refl
?) difference Lean 3/4 by Heather Macbeth.
Heather Macbeth (Dec 08 2022 at 15:14):
OK, I think I disaggregated the messages :)
Heather Macbeth (Dec 08 2022 at 15:15):
So do we want to be tagging every congr
lemma in mathlib4 with simp
? Or is there a better fix?
Kevin Buzzard (Dec 08 2022 at 15:15):
What is weirding me out is that this congr lemma is changing the behaviour of simp only!
Kevin Buzzard (Dec 08 2022 at 15:17):
From mathlib4:
-- Porting note: `@[congr]` commented out for now.
-- @[congr]
theorem forall_prop_congr' {p p' : Prop} {q q' : p → Prop} (hq : ∀ h, q h ↔ q' h) (hp : p ↔ p') :
(∀ h, q h) = ∀ h : p', q' (hp.2 h) :=
propext (forall_prop_congr hq hp)
;-)
attribute [congr] forall_prop_congr'
/-
invalid 'congr' theorem, equality left/right-hand sides must be applications of the same function
(∀ (h : ?p), ?q h) = ∀ (h : ?p'), ?q' (_ : ?p)
-/
:-(
Mario Carneiro (Dec 08 2022 at 15:25):
congr lemmas are not selected by simp only, it always uses all of them
Kevin Buzzard (Dec 08 2022 at 15:29):
Do you have any idea why we can't tag forall_prop_congr'
with @[congr]
in Lean 4 whereas we could in Lean 3? Are we doomed to a life of simp [...] with lean_3_congr_simps_which_arent_congr_in_lean_4
?
Mario Carneiro (Dec 08 2022 at 15:31):
because no one has filed a bug report for this yet
Kevin Buzzard (Dec 08 2022 at 15:31):
-- lean 3
@[simp] lemma forall_true_left (p : true → Prop) : (∀ x, p x) ↔ p true.intro :=
sorry
@[simp] lemma forall_prop_congr' {p p' : Prop} {q q' : p → Prop}
(hq : ∀ h, q h ↔ q' h) (hp : p ↔ p') : (∀ h, q h) = ∀ h : p', q' (hp.2 h) :=
sorry
-- uncomment to fix
-- attribute [congr] forall_prop_congr'
example (p : Prop) (q : p → Prop) (h : p) :
(∀ (h2 : p), q h2) ↔ q h :=
by simp only [h, forall_true_left, forall_prop_congr'] -- still fails
Kevin Buzzard (Dec 08 2022 at 15:32):
It's more than "just add this as a simp
lemma"
Mario Carneiro (Dec 08 2022 at 15:33):
yeah simp doesn't take congr lemmas as input at all
Kevin Buzzard (Dec 08 2022 at 15:33):
Mario Carneiro said:
because no one has filed a bug report for this yet
I added some comments at https://github.com/leanprover/lean4/issues/1926 -- does that count as filing a bug report?
Mario Carneiro (Dec 08 2022 at 15:33):
if you put them in the simp list they will be treated as simp lemmas, not congr lemmas
Mario Carneiro (Dec 08 2022 at 15:35):
I think so... you just have to make sure that the issue being solved implies this problem is also solved
Mario Carneiro (Dec 08 2022 at 15:35):
because leo likes closing issues :)
Patrick Massot (Dec 08 2022 at 15:53):
Who doesn't?
Jakob von Raumer (Dec 09 2022 at 11:58):
As soon as I find the time I'll try to see what happens if one just disables the check here which prevents forall_prop_congr'
to be marked as [congr]
...
Scott Morrison (Dec 12 2022 at 01:20):
@Jakob von Raumer, I disabled it in a branch: https://github.com/leanprover/lean4/compare/master...semorrison:lean4:disable_congr_check?expand=1
We would need to include a test including @[congr] theorem forall_prop_congr`
.
Jakob von Raumer (Dec 12 2022 at 01:46):
It's funny that for most purposes, ∀
behaves like a normal function Forall : (α : Type) → (α → Prop) → Prop
Scott Morrison (Dec 12 2022 at 01:54):
I added the test, which failed:
Scott Morrison (Dec 12 2022 at 01:54):
https://github.com/leanprover/lean4/pull/1942
Scott Morrison (Dec 12 2022 at 01:54):
Oh, that's unfortunate, closing the PR deleted the CI outputs?
Scott Morrison (Dec 12 2022 at 01:55):
Ah, here it is:
Scott Morrison (Dec 12 2022 at 01:55):
https://github.com/leanprover/lean4/actions/runs/3671852401/jobs/6207473096#step:11:1914
Scott Morrison (Dec 12 2022 at 01:55):
[02:15 (00.050441)] lean-test-stage1> 664/1623 Test #664: leanruntest_1926.lean .....................................***Failed 0.13 sec
[02:15 (00.000713)] lean-test-stage1> Unexpected return code 1 executing 'lean -j 0 -Dlinter.all=false 1926.lean'; expected 0. Output:
[02:15 (00.000481)] lean-test-stage1> 1926.lean:1:8: warning: declaration uses 'sorry'
[02:15 (00.000441)] lean-test-stage1> 1926.lean:3:8: warning: declaration uses 'sorry'
[02:15 (00.000451)] lean-test-stage1> 1926.lean:7:11: error: invalid 'congr' theorem, argument #1 of parameter #5 contains unresolved parameter
[02:15 (00.000655)] lean-test-stage1> ?p
[02:15 (00.000768)] lean-test-stage1> 1926.lean:11:0: error: unsolved goals
[02:15 (00.000384)] lean-test-stage1> p : Prop
[02:15 (00.000408)] lean-test-stage1> q : p → Prop
[02:15 (00.000429)] lean-test-stage1> h : p
[02:15 (00.000446)] lean-test-stage1> ⊢ (∀ (h2 : p), q h2) ↔ q h
Scott Morrison (Dec 12 2022 at 01:56):
So there's still a later step where this lemma would be rejected by @[congr]
.
Jakob von Raumer (Dec 12 2022 at 10:09):
Would this not be a way around it?
def propForall {p : Prop} (q : p → Prop) : Prop := ∀ x, q x
@[simp] theorem forall_eq_forall_iff {p p' : Prop} {q : p → Prop} {q' : p' → Prop} :
((∀ x, q x) = (∀ x, q' x)) ↔ propForall q = propForall q' :=
⟨(·), (·)⟩
@[congr] theorem propForall_prop_congr' {p p' : Prop} {q q' : p → Prop}
(hq : ∀ h, q h ↔ q' h) (hp : p ↔ p') :
propForall q = propForall (fun h => q' (hp.2 h)) :=
sorry --propext (forall_prop_congr hq hp)
Scott Morrison (Dec 12 2022 at 10:13):
I'm out of time for now: would you mind editing my PR to use that test?
Jakob von Raumer (Dec 12 2022 at 10:20):
Will do
Jakob von Raumer (Dec 12 2022 at 10:20):
So this works:
def propForall {p : Prop} (q : p → Prop) : Prop := ∀ x, q x
@[simp] theorem forall_eq_propForall {p : Prop} (q : p → Prop) : (∀ x, q x) ↔ propForall q :=
Iff.rfl
@[simp] theorem propForall_true_left : ∀ (p : True → Prop), propForall p ↔ p True.intro :=
sorry
@[congr] theorem propForall_prop_congr' {p p' : Prop} {q q' : p → Prop}
(hq : ∀ h, q h ↔ q' h) (hp : p ↔ p') :
propForall q = propForall (fun h => q' (hp.2 h)) :=
sorry --propext (forall_prop_congr hq hp)
example (p : Prop) (q : p → Prop) (h : p) :
(∀ (h2 : p), q h2) ↔ q h :=
by simp only [forall_eq_propForall, h, propForall_true_left, iff_self]
Jakob von Raumer (Dec 12 2022 at 10:21):
So maybe this is better than tweaking SimpCongrTheorems.lean
? I don't know :thinking:
Chris Hughes (Dec 13 2022 at 01:37):
Then you're leaving propForall
in people's goal after non-terminal simp
. I know non-terminal simp
is bad but it's still not ideal.
Jakob von Raumer (Dec 13 2022 at 08:57):
Yea, would be better if we could engineer it in a way that only triggers the [simp]
if the [congr]
is applicable
Chris Hughes (Dec 15 2022 at 11:08):
As a temporary hack for when these come up, I've been using the lemmas forall_prop_of_true
and forall_prop_of_false
Last updated: Dec 20 2023 at 11:08 UTC