Zulip Chat Archive

Stream: general

Topic: push_neg is not confluent


Johan Commelin (Dec 02 2025 at 10:46):

This is probably a known issue.

import Mathlib

example (P Q : Prop) : ¬¬(P  Q)  ¬ (¬P  ¬Q) := by
  push_neg
  fail_if_success rfl
  show P  Q  ¬P  Q
  tauto

This bit me because I tried to do

/--
error: Type mismatch
  h✝
has type
  a ≠ 0 → b = 0
but is expected to have type
  a = 0 ∨ b = 0
-/
example (a b : ) : a  0  b  0 := by
  by_contra! (rfl | rfl) : a = 0  b = 0
  sorry

Johan Commelin (Dec 02 2025 at 10:46):

@Jovan Gerbscheid Do you have any ideas here?

Johan Commelin (Dec 02 2025 at 10:47):

Maybe, to #xy the by_contra! tactic shouldn't use push_neg but rather tauto?

Jovan Gerbscheid (Dec 02 2025 at 10:49):

This works:

import Mathlib

example (P Q : Prop) : ¬¬(P  Q)  ¬ (¬P  ¬Q) := by
  set_option push_neg.use_distrib true in push_neg
  rfl

And once #31406 lands, you can write push_neg +distrib/by_contra! +distrib

Yaël Dillies (Dec 02 2025 at 10:49):

How would that make sense? It's not a closing tactic

Jovan Gerbscheid (Dec 02 2025 at 10:50):

It makes a little bit of sense for push_neg with a type annotation, for proving that the given type annotation matches the expected type. The current approach is to just push_neg in both and hope that they end up the same.

Jovan Gerbscheid (Dec 02 2025 at 10:57):

Do you think that by_contra! should use +distrib as the default?

Johan Commelin (Dec 02 2025 at 10:59):

What do you think of having it use tauto whenever the types don't line up?

Jovan Gerbscheid (Dec 02 2025 at 11:00):

Well I think that wouldn't really work because tauto is not aware of all push_neg theorems.

Johan Commelin (Dec 02 2025 at 11:02):

My question is rather why by_contra! should have anything to do with push_neg in the first place... :wink:

Jovan Gerbscheid (Dec 02 2025 at 11:04):

Well, by_contra! is the combination of by_contra and push_neg... Unless you want to put type annotations in every by_contra!, there is no other way.

Johan Commelin (Dec 02 2025 at 11:07):

Aah, so maybe I want tauto as a fall-back in the non-confluent cases?

Johan Commelin (Dec 02 2025 at 11:07):

Or maybe whenever the user gives a type annotation?

Johan Commelin (Dec 02 2025 at 11:09):

If the user gives a type annotation, there is an implicit claim that they have an idea of what the negation of the goal should look like. We should accomodate that, unless the claim is false.
tauto will do that, even when eg commutativity is needed.

Jovan Gerbscheid (Dec 02 2025 at 11:10):

Something like this should work

syntax "by_contra!_disch" ident : tactic
macro_rules | `(tactic| by_contra!_disch $h) => `(tactic| (try push_neg); exact $h)
macro_rules | `(tactic| by_contra!_disch $h) => `(tactic| tauto)

example (a b : ) : a  0  b  0 := by
  by_contra h
  push_neg at h
  replace h : a = 0  b = 0 := by by_contra!_disch h

Jovan Gerbscheid (Dec 02 2025 at 11:13):

Oh wait, that will run tauto before running try push_neg); exact $h. But if we swap them, then we get the tauto error message instead of the error message that we want.

Jovan Gerbscheid (Dec 02 2025 at 11:20):

Ok, I think it would be reasonable to simply replace the exact h with tauto in the implementation of by_contra!. The only problem I see is that tauto gives a horribly useless error message: "tauto failed to solve some goals.". Instead, I'd like it to say that it couldn't solve the goal, and then print the goal.

Johan Commelin (Dec 02 2025 at 11:59):

Can that be solved by some try-catch?

Jovan Gerbscheid (Dec 02 2025 at 12:21):

If we just fix tauto to give a better error message, it should be fine, right?

Johan Commelin (Dec 02 2025 at 12:22):

That's the more principled approach :smiley:

Patrick Massot (Dec 02 2025 at 17:53):

Jovan Gerbscheid said:

It makes a little bit of sense for push_neg with a type annotation, for proving that the given type annotation matches the expected type. The current approach is to just push_neg in both and hope that they end up the same.

FYI, this is what I do in Verbose Lean as well.

Bolton Bailey (Dec 02 2025 at 17:59):

Perhaps this is related: I find it undesirable that docs#not_and is a simp lemma. Often this lemma turns a symmetrical expression into an unsymmetrical one and makes my InfoView harder to understand, I think I might prefer it more if docs#not_and_or were the lemma simp used here instead.

Aaron Liu (Dec 02 2025 at 18:51):

I definitely want ¬∃ x ∈ s, P x to be turned into ∀ x ∈ s, ¬P x instead of ∀ x, x ∉ s ∨ ¬P x

Jovan Gerbscheid (Dec 02 2025 at 19:51):

It is possible to add a push lemma ¬(∃ x, Q x ∧ P x) = ∀ x, Q x → ¬P x, and this would make your case still work as desired. (while also having not_and_or for bare conjunctions)


Last updated: Dec 20 2025 at 21:32 UTC