Zulip Chat Archive

Stream: general

Topic: simpa using


David Loeffler (Mar 12 2023 at 15:53):

I just came across some weird behaviour with simpa using. I was under the impression that simpa using foo is supposed to be more robust than plain simp -- less likely to be broken by changes to the simp-lemma set. But I just encountered a case where adding a new simp lemma elsewhere in the library broke a proof of the form by simpa using foo, causing it to fail with the error match failed: no goals.

Given the nature of this bug it's rather hard to produce an MWE. You can reproduce it as follows:

  • grab a copy of the current mathlib3
  • in the file src/analysis/upper_half_plane/basic.lean, add the following lemma at line 314 (just after modular_S_smul):
@[simp] lemma modular_T_smul (z : ) : modular_group.T  z = (1 : ) +ᵥ z :=
begin
  suffices : ((modular_group.T  z : ) : ) = ((1 : ) +ᵥ z), by rwa subtype.coe_inj at this,
  rw [coe_vadd, add_comm, special_linear_group_apply, coe_mk, modular_group.coe_T],
  simp only [of_apply, cons_val_zero, algebra_map.coe_one, complex.of_real_one, one_mul,
    cons_val_one, head_cons, algebra_map.coe_zero, zero_mul, zero_add, div_one],
end
  • open src/number_theory/modular.lean and wait for it to recompile as far as line 327, which has the lemma
lemma im_T_smul : (T  z).im = z.im := by simpa using im_T_zpow_smul z 1

This will now fail, where it previously worked before. The new simp lemma in basic.lean means that im_T_smul can now be proved just with by simp, and apparently that causes by simpa using ... to fail.

The documentation says that simpa using h calls the simplifier on both h and the goal, and succeeds if they simplify to the same thing. My guess is that here the simplifier has simplified the goal away to nothing, then complains because it hasn't got anything to match h against. But surely an unexpectedly easy success shouldn't count as a failure!

Is this the intended behaviour, or am I correct that it's a bug in the simpa tactic?

Martin Dvořák (Mar 12 2023 at 15:59):

We were discussing simpa on Discord yesterday. If I understood correctly what I was told, it behaves as follows:
If simp succeeds on its own, then simpa will fail.

Calling simpa using foo is supposed to do something like simp [foo] at ⊢ foo, assumption and, therefore, if the simp itself manages to close the goal (possibly because a new simp lemma was added), then assumption has no goals to work on.

But surely an unexpectedly easy success shouldn't count as a failure!

This is exactly what I thought, and I was wrong.

Eric Wieser (Mar 12 2023 at 16:01):

It's not clear to me that modular_T_smul is a good simp lemma

Eric Wieser (Mar 12 2023 at 16:01):

At least, it doesn't seem confluent with the rest of the simp set

David Loeffler (Mar 12 2023 at 16:06):

@Eric Wieser Why do you say that? It was intended as a counterpart to the existing simp lemma

@[simp] lemma modular_S_smul (z : ) : modular_group.S  z = mk (-z : )⁻¹ z.im_inv_neg_coe_pos

(S and T are the two standard generators of the modular group). Do you consider that one to be morally unsound too?

Eric Wieser (Mar 12 2023 at 16:07):

That looks like a weird simp lemma to me too, yes; usually we don't introduce constructors (mk) on the RHS like that

Eric Wieser (Mar 12 2023 at 16:07):

It's a good lemma but weird to be simp

David Loeffler (Mar 12 2023 at 16:18):

Martin Dvořák said:

But surely an unexpectedly easy success shouldn't count as a failure!

This is exactly what I thought, and I was wrong.

Well, I think we've established that it does count as a failure, but I think there's still a case to be made that it shouldn't! But tactic hacking is beyond my pay grade -- let's wait and see if any metaprogramming experts can weigh in on this.

Eric Wieser (Mar 12 2023 at 16:24):

Is this the intended behaviour, or am I correct that it's a bug in the simpa tactic?

To answer your question, this is intended behavior; it forces you to simplify downstream proofs (or re-evaluate the addition of a simp lemma) when the simp set gets bigger

Eric Wieser (Mar 12 2023 at 16:25):

David Loeffler said:

I was under the impression that simpa using foo is supposed to be more robust than plain simp -- less likely to be broken by changes to the simp-lemma set.

I think the correct conclusion is that simpa using foo is more robust than simp at foo, simp [foo]

Adam Topaz (Mar 12 2023 at 16:28):

I think this behaviour of simpa is fixed in lean4. You will get a warning telling you that simp suffices instead of an unhelpful failure

David Loeffler (Mar 12 2023 at 16:34):

Eric Wieser said:

To answer your question, this is intended behavior; it forces you to simplify downstream proofs (or re-evaluate the addition of a simp lemma) when the simp set gets bigger

If that's the case, then why do we have the rule against non-terminal simps? I thought the primary reason for that rule was that authors of downstream proofs were responsible for ensuring robustness against future additions to the simp set -- rather than anyone changing the simp set being responsible for changing a whole load of other people's downstream code! I think this principle is a good one, so I'm puzzled that the policy forsimpa is apparently the exact opposite of that for simp.

Martin Dvořák (Mar 12 2023 at 16:36):

I think the rule against non-terminal simps is not to prevent failures when the set of simp lemmas changes, but to make corrections easier (because the immediate goal of the simp is always known).

Eric Wieser (Mar 12 2023 at 16:37):

The problem with non-terminal simps is when simp, rw foo fails because foo no longer matches; and then the person editing the file has no idea what went wrong. If simp, rw foo fails because the goal is already solved it's trivial to fix the proof

Patrick Massot (Mar 12 2023 at 16:51):

David Loeffler said:

Martin Dvořák said:

But surely an unexpectedly easy success shouldn't count as a failure!
Well, I think we've established that it does count as a failure, but I think there's still a case to be made that it shouldn't!

Maybe I read too quickly but I certainly hope to get an error if simpa using foo succeeds without using foo. Leaving it like this would be a really really misleading proof script. Or are you saying something else?

Eric Wieser (Mar 12 2023 at 16:54):

Lean4 has a better approach to this where we can use warning messages for "this proof doesn't use foo" and errors for "this proof doesn't work", as Adam remarks above.

David Loeffler (Mar 12 2023 at 17:04):

Maybe I read too quickly but I certainly hope to get an error if simpa using foo succeeds without using foo. Leaving it like this would be a really really misleading proof script. Or are you saying something else?

Yes, you have read correctly what I am saying. It definitely does not seem reasonable to me that the result of a tactic succeeding too easily should be an opaque, incomprehensible error. However, it's clearly optimal to return an informative message instructing the user how to fix the issue, so I'm happy to concede that the Lean 4 solution is the best one.

Patrick Massot (Mar 12 2023 at 17:06):

Oh yes, better error messages are always welcome, but very often it's a lot of work.

Yaël Dillies (Mar 12 2023 at 17:20):

It emitting a warning is still annoying if you're using simpa to solve several goals in parallel: tac1; simpa

Michael Stoll (Mar 12 2023 at 18:53):

Maybe have a version of simpa that succeeds also when the simp was successful for cases like this?

Eric Wieser (Mar 12 2023 at 18:57):

I think simpa vs simpa using x has different considerations here. What does rwa do if no assumption is needed?

Michael Stoll (Mar 12 2023 at 19:06):

rwa seems to be happy in that case.

example (a b : ) (h : a = 1) (h' : b = a) : a = 1 :=
begin
  rwa h,
end

succeds without complaint.

Michael Stoll (Mar 12 2023 at 19:08):

(As an aside: I think that rwa ... at h behaves a bit strangely, as it seems to only work when the goal can be closed by h after the rewrite. I.e., it seems to be equivalent to rw ... at h, exact h and not to rw ... at h, assumption. Is this impression correct, and if so, is the behavior indended?)

Kevin Buzzard (Mar 12 2023 at 19:15):

I did not have this impression, and this test seems to suggest that my model is OK (i.e. the goal is closed by a random assumption here)

example (a b c : ) (P : Prop) (hP : P) (h' : a = b) (h : a = c) : P := by rwa h' at h

Kevin Buzzard (Mar 12 2023 at 19:17):

Here's the tactic code: I don't know much about tactics but I can guess what's going on here:

meta def rwa (q : parse rw_rules) (l : parse location) (cfg : rewrite_cfg := {}) : tactic unit :=
rewrite q l cfg >> try assumption

(just right click on rwa to see it)

Michael Stoll (Mar 12 2023 at 19:26):

Ah, OK, sorry, I may have confused things.
I think, simpa [...] at h does not work as I would expect (i.e., like simp [...] at h, assumption) -- the syntax is not even accepted.

example (a b c : ) (h : b = c) (h' : b = a) (h'' : c = 1) : a = 1 :=
begin
  simpa [h', h''] at h,
end

gives invalid 'begin-end' expression, ',' expected.

Alex J. Best (Mar 12 2023 at 19:29):

I'm confused why you want this? Surely simpa [...] using h does all you would ever need? Do you just think the syntax is inconsistent (indeed it sortof is but by now we all got used to it)?

Michael Stoll (Mar 12 2023 at 19:34):

Maybe my mental model of simpa was not quite correct. I guess I did not realize that simpa ... using h would also simplify h; my model was rather simp ..., exact h, with simplification of the goal only.
So thanks; I've learned something!

Mario Carneiro (Mar 13 2023 at 05:05):

Yaël Dillies said:

It emitting a warning is still annoying if you're using simpa to solve several goals in parallel: tac1; simpa

The linter framework is capable of handling this case. (Individual linters have to opt in to the behavior, but) if you use simpa twice with the same syntax then you only get the linter warning if both uses are unnecessary

Mario Carneiro (Mar 13 2023 at 05:08):

Some linter-like behavior is done directly in tactic execution, which will have the indicated failure mode. An example of a linter which does this correctly is the unreachable tactic linter - split <;> simp <;> rfl doesn't complain on rfl even if it was only used in one subgoal, but if simp closes both goals then you will get a warning on rfl saying it wasn't called

Mario Carneiro (Mar 13 2023 at 05:10):

(This is all regarding lean 4 behavior. In lean 3 the error message is the best we can do.)

Yaël Dillies (Mar 13 2023 at 08:09):

Oh perfect! Then I'm very happy with the warning solution.


Last updated: Dec 20 2023 at 11:08 UTC