Zulip Chat Archive

Stream: general

Topic: congr lemma for invertible


Eric Wieser (Feb 10 2022 at 12:11):

Right now this fails:

import algebra.invertible

example {R : Type*} [monoid R] {r₁ r₂ : R}
  [invertible r₁] [invertible r₂] (h : r₁ = r₂) : r₁ = r₂ :=
by simp [h] -- fails

Is there a way to provide a congr lemma so that this works?

Eric Wieser (Feb 10 2022 at 12:12):

My naive attempt was:

lemma invertible_congr {R : Type*} [monoid R] {r₁ r₂ : R}
  [i₁ : invertible r₁] [i₂ : invertible r₂] (h : r₁ = r₂) : r₁ = r₂ :=
begin
  substI h,
  convert rfl,
end

but this doesn't accept a @[congr] attribute

Eric Wieser (Feb 10 2022 at 12:19):

If I introduce a stupid alias for invertible.inv_of that takes a monoid argument, then @[congr] is happy:

import algebra.invertible

variables {R : Type*} [monoid R] {r₁ r₂ : R} [invertible r₁] [invertible r₂]

/-- A duplicate of `⅟` that makes `congr` happy -/
abbreviation invertible.inv_of' (r : R) [invertible r] := r
notation `⅟'` := invertible.inv_of'

/-- `congr` is happy as long as we use `⅟'`-/
@[congr] lemma invertible_congr' (h : r₁ = r₂) : ⅟'r₁ = ⅟'r₂ :=
by { substI h, congr }

/- `simp` is happy as long as we use `⅟'` on the LHS-/
example (h : r₁ = r₂) : ⅟'r₁ = ⅟'r₂ := by simp [h] -- ok
example (h : r₁ = r₂) : ⅟'r₁ = r₂ := by simp [h] -- ok
example (h : r₁ = r₂) : r₁ = ⅟'r₂ := by simp [h] -- fails
example (h : r₁ = r₂) : r₁ = r₂ := by simp [h] -- fails

Gabriel Ebner (Feb 10 2022 at 12:26):

Yes, this is a known limitation in Lean 3. It is already gone in Lean 4.

Eric Wieser (Feb 10 2022 at 12:27):

Are there any known workarounds?

Gabriel Ebner (Feb 10 2022 at 12:29):

I guess you could state the lemma like this (untested):

class is_monoid (R) [has_mul R] [has_one R] : Prop := ...
instance {R} [monoid R] : is_monoid R := ...

@[congr]
lemma invertible_congr {R : Type*} [has_mul R] [has_one R] [is_monoid R] {r₁ r₂ : R}
  [i₁ : invertible r₁] [i₂ : invertible r₂] (h : r₁ = r₂) : r₁ = r₂ :=

Eric Wieser (Feb 10 2022 at 12:37):

Nice, that does the trick:

import algebra.invertible

variables {R : Type*} [monoid R] {r₁ r₂ : R} [invertible r₁] [invertible r₂]

class is_monoid (R) [has_mul R] [has_one R] : Prop :=
(out [] :  m : monoid R, mul_one_class.to_has_one R = _  mul_one_class.to_has_mul R = _›)

instance monoid.is_monoid {R} [monoid R] : is_monoid R :=
⟨⟨‹_›, rfl, rfl⟩⟩

@[congr]
lemma invertible_congr {R : Type*} [has_mul R] [has_one R] [is_monoid R] {r₁ r₂ : R}
  [i₁ : invertible r₁] [i₂ : invertible r₂] (h : r₁ = r₂) : r₁ = r₂ :=
by {
  unfreezingI { obtain m : monoid R, rfl, rfl := is_monoid.out R },
  substI h, congr }

example (h : r₁ = r₂) : r₁ = r₂ := by simp [h] -- ok

Eric Wieser (Feb 10 2022 at 12:37):

Is that something that is ok in mathlib? Perhaps renaming is_monoid to invertible_congr_aux or something to discourage widespread use?

Reid Barton (Feb 10 2022 at 12:39):

Don't know what's going on here but (a) is_monoid should be called is_monoid and not invertible_congr_aux (b) is_monoid shouldn't be a class, if you want a class, just use monoid

Eric Wieser (Feb 10 2022 at 12:39):

@Reid Barton, if I use [monoid R] then @[congr] complains. By using the is_monoid R hack above, it doesn't

Reid Barton (Feb 10 2022 at 12:51):

Is this really worth fixing?

Reid Barton (Feb 10 2022 at 12:51):

or at least, in this way? it seems like a quite generic sort of issue?

Eric Wieser (Feb 10 2022 at 12:52):

Right, but this is also a generic recipe for solving it - here's the version with it called aux:

import algebra.invertible

/-- typeclass to hide arguments for `invertible_congr` that `congr` rejects.
The equalities are used to substitute arguments in `invertible_congr` with expressions built from
the data fields. -/
class inductive invertible_congr.aux (R) [has_mul R] [has_one R] : Prop
| intro
    (monoid : monoid R)
    (h_one_eq : mul_one_class.to_has_one R = _›)
    (h_mul_eq : mul_one_class.to_has_mul R = _›) : invertible_congr.aux

instance invertible_congr.aux.inst {R} [monoid R] : invertible_congr.aux R :=
⟨‹_›, rfl, rfl

variables {R : Type*} [monoid R] {r₁ r₂ : R} [invertible r₁] [invertible r₂]

/--
This can't take a `[monoid]` argument as `@[congr]` does not allow the `mul_one_class.to_has_one`
term that appears in the goal. Instead we take a typeclass that allows us to replace the `has_one R`
term with such an expression within the proof. -/
@[congr]
lemma invertible_congr {R : Type*} [has_mul R] [has_one R] [invertible_congr.aux R] {r₁ r₂ : R}
  [i₁ : invertible r₁] [i₂ : invertible r₂] (h : r₁ = r₂) : r₁ = r₂ :=
begin
  unfreezingI { obtain m, rfl, rfl := invertible_congr.aux R },
  substI h,
  congr
end

example (h : r₁ = r₂) : r₁ = r₂ :=
begin
  simp [h],
end

Reid Barton (Feb 10 2022 at 12:53):

I don't really think we should be adding a bunch of workarounds to mathlib like this

Eric Wieser (Feb 10 2022 at 12:53):

The alternative is facing annoying situations where simp makes no progress

Reid Barton (Feb 10 2022 at 12:53):

seems okay!

Reid Barton (Feb 10 2022 at 12:54):

There are other tactics

Eric Wieser (Feb 10 2022 at 12:57):

Sure, but the tradeoff is either:

  • have a bizarre workaround like the one above, that no one ever looks at again, which gets silently removed in mathlib4
  • have multiple users run into the same problem every time, and produce weird proofs as workarounds that stick around in mathlib4

Eric Wieser (Feb 10 2022 at 12:57):

How would you solve this goal?

example  {R : Type*} [monoid R] (foo :   R) (bar :   R)
  [invertible (foo 1)] [invertible (bar 2)] (h : foo 1 = bar 2) :
  37 + (foo 1) = 37 + (bar 2) :=
sorry

Eric Wieser (Feb 10 2022 at 12:58):

The natural sequence of thing to try is rw h, simp_rw h, and simp [h] in some order, and all of those fail.

Eric Wieser (Feb 10 2022 at 12:58):

With the code above hidden away in mathlib, simp_rw h works fine

Reid Barton (Feb 10 2022 at 13:00):

It just looks worse to me to add 20 lines of obscurities per instance of this issue. That's all

Eric Wieser (Feb 10 2022 at 13:01):

I suppose there are two more options, which are:

  • Fix the problem in Lean 3 core
  • Write a metaprogram to generate the above boilerplate

I think having Lean 4 on the horizon makes these look less desirable than accumulating the 20 lines each time.

Mario Carneiro (Feb 10 2022 at 16:53):

I agree with Reid, you should just use congr_arg or something if this comes up, adding typeclasses seems like it will cause lots of downstream problems for a proof-local issue

Eric Wieser (Feb 10 2022 at 17:21):

What proof would you suggest for the above sorry, Mario?

Gabriel Ebner (Feb 10 2022 at 17:34):

I might be biased because I suggested the hack, but I don't think it will cause any technical issues (as long as it is only used for this congruence lemma). It's a type class with a single instance, that is only required by a single lemma. And this congruence lemma is only triggered when the simplifier sees an inv_of constant. Any potential negative impact is extremely limited.

The simplifier should be able to rewrite under inv_of; we have similar congruence lemmas for fintype.card etc. Adding this congruence lemma would only be consistent.

Finally, the hack is short-lived. We can remove it immediately after switching to Lean 4. (The congruence lemma will work when written with [Monoid R] instead of [Mul R] [One R] [IsMonoid R].)

Mario Carneiro (Feb 10 2022 at 17:41):

I see, if it's a specialized typeclass then I agree it's more contained. The docs should make it clear that it's not intended for use beyond that though

Mario Carneiro (Feb 10 2022 at 17:42):

the is_monoid name sounds like it will get used elsewhere

Yaël Dillies (Feb 10 2022 at 17:43):

What about monoid_congr_class_warning_do_not_use?

Mario Carneiro (Feb 10 2022 at 17:44):

@Eric Wieser Is that a MWE? 37 + ⅟(foo 1) doesn't typecheck

Eric Wieser (Feb 10 2022 at 17:45):

Yaël Dillies said:

What about monoid_congr_class_warning_do_not_use?

At that point, invertible_congr.aux as I use above is better

Eric Wieser (Feb 10 2022 at 17:46):

Fixed to be a MWE

Mario Carneiro (Feb 10 2022 at 17:49):

import algebra.invertible

lemma invertible_congr {R : Type*} [monoid R] {r₁ r₂ : R}
  [i₁ : invertible r₁] [i₂ : invertible r₂] (h : r₁ = r₂) : r₁ = r₂ :=
begin
  substI h,
  convert rfl,
end

example {R : Type*} [ring R] (foo :   R) (bar :   R)
  [invertible (foo 1)] [invertible (bar 2)] (h : foo 1 = bar 2) :
  37 + (foo 1) = 37 + (bar 2) :=
by { congr' 1, exact invertible_congr h }

Eric Wieser (Feb 10 2022 at 18:53):

Probably the argument goes that we should have invertible_congr in that form for human use anyway, and then provide invertible_congr' for Lean 3's sake with the weird typeclass


Last updated: Dec 20 2023 at 11:08 UTC