Zulip Chat Archive

Stream: mathlib4

Topic: dsimpNF


Kim Morrison (Jun 13 2024 at 00:59):

@Johan Commelin et al, here is the output from the new simpNF linter on Mathlib.

Kim Morrison (Jun 13 2024 at 00:59):

(There are failures.)

Kim Morrison (Jun 13 2024 at 03:00):

I think maybe we should revert the Batteries change, make a new PR, and sort out the Mathlib failures by pointing a branch of Mathlib at the branch.

Johan Commelin (Jun 13 2024 at 03:58):

Ok, I guess that's a better path forward.

Johan Commelin (Jun 13 2024 at 04:36):

@Kim Morrison Here's a PR for the revert: batteries#841

Damiano Testa (Jun 13 2024 at 05:09):

I am trying out #13793 for the syntax linter deprecating "no since" deprecated.

To be honest, my main motivation is to practice self-correcting code, rather than the linter itself: I would like to add a step that automatically adds the since part, wherever the linter flags a warning.

Johan Commelin (Jun 13 2024 at 06:55):

Here is a batteries-free MWE of the first dsimpNF complaint:

section Equiv

variable {α β : Type}

/-- `α ≃ β` is the type of functions from `α → β` with a two-sided inverse. -/
structure Equiv (α β : Type) where
  protected toFun : α  β
  protected invFun : β  α

@[inherit_doc]
infixl:25 " ≃ " => Equiv

/-- Inverse of an equivalence `e : α ≃ β`. -/
@[symm]
protected def Equiv.symm (e : α  β) : β  α := e.invFun, e.toFun

end Equiv

variable {M N : Type} [Mul M] [Mul N]

section MulHom

/-- `M →ₙ* N` is the type of functions `M → N` that preserve multiplication. -/
structure MulHom (M N : Type) [Mul M] [Mul N] where
  protected toFun : M  N
  protected map_mul' :  x y, toFun (x * y) = toFun x * toFun y

/-- `M →ₙ* N` denotes the type of multiplication-preserving maps from `M` to `N`. -/
infixr:25 " →ₙ* " => MulHom

end MulHom

/-- `MulEquiv α β` is the type of an equiv `α ≃ β` which preserves multiplication. -/
structure MulEquiv (M N : Type) [Mul M] [Mul N] extends M  N, M →ₙ* N

/-- Notation for a `MulEquiv`. -/
infixl:25 " ≃* " => MulEquiv

namespace MulEquiv

/-- The inverse of an isomorphism is an isomorphism. -/
def symm (e : M ≃* N) : N ≃* M :=
  e.toEquiv.symm, sorry

namespace MulEquiv

theorem symm_mk' (f f') (h) :
    (⟨⟨f, f', h : M ≃* N).symm =
      {(⟨⟨f, f', h : M ≃* N).symm with
        toFun := f'
        invFun := f } :=
  rfl

theorem works_as_expected (f f') (h) :
    (⟨⟨f, f', h : M ≃* N).symm =
      {(⟨⟨f, f', h : M ≃* N).symm with
        toFun := f'
        invFun := f } := by
  simp? only [symm_mk'] -- succeeds

/--
error: tactic 'simp' failed, nested error:
maximum recursion depth has been reached
use `set_option maxRecDepth <num>` to increase limit
use `set_option diagnostics true` to get diagnostic information
-/
#guard_msgs in
theorem fails_as_unexpected (f f') (h) :
    (⟨⟨f, f', h : M ≃* N).symm =
      {(⟨⟨f, f', h : M ≃* N).symm with
        toFun := f'
        invFun := f } := by
  dsimp only [symm_mk'] -- fails, but `symm_mk'` is a rfl lemma

end MulEquiv

Notification Bot (Jun 13 2024 at 06:56):

7 messages were moved here from #mathlib4 > Technical Debt Counters by Johan Commelin.

Johan Commelin (Jun 13 2024 at 06:56):

I haven't looked into the others. But it seems to me that this is maybe just a bad lemma to begin with?

Johan Commelin (Jun 13 2024 at 07:17):

@Eric Wieser do you have an idea whether symm_mk' is desirable or not?

Eric Wieser (Jun 13 2024 at 07:46):

My guess here is that dsimp is recursing into the proof fields

Eric Wieser (Jun 13 2024 at 07:47):

At first glance the lemma looks suspicious because the LHS contains the RHS, but in fact this loop only happens within the proofs which should be ok.

Johan Commelin (Jun 13 2024 at 11:59):

But do we want to have lemmas like this in our default simp-set? From that angle it still looks very suspicious to me.

Eric Wieser (Jun 13 2024 at 12:04):

This works fine:

def symm_mk'.aux (f f') (h) := (⟨⟨f, f', h : M ≃* N).symm.map_mul'

theorem symm_mk' (f f') (h) :
    (⟨⟨f, f', h : M ≃* N).symm =
      { toFun := f'
        invFun := f
        map_mul' := symm_mk'.aux f f' h } :=
  rfl

Eric Wieser (Jun 13 2024 at 12:04):

So I claim that either this is a dsimp bug; or that there should be some way to generate symm_mk'.aux automatically to protect its contents from dsimp

Johan Commelin (Jun 13 2024 at 12:54):

Updated version of the MWE, displaying the two versions: the MWE coming from mathlib, and Eric's workaround.

section Equiv

variable {α β : Type}

/-- `α ≃ β` is the type of functions from `α → β` with a two-sided inverse. -/
structure Equiv (α β : Type) where
  protected toFun : α  β
  protected invFun : β  α

@[inherit_doc]
infixl:25 " ≃ " => Equiv

/-- Inverse of an equivalence `e : α ≃ β`. -/
@[symm]
protected def Equiv.symm (e : α  β) : β  α := e.invFun, e.toFun

end Equiv

variable {M N : Type} [Mul M] [Mul N]

section MulHom

/-- `M →ₙ* N` is the type of functions `M → N` that preserve multiplication. -/
structure MulHom (M N : Type) [Mul M] [Mul N] where
  protected toFun : M  N
  protected map_mul' :  x y, toFun (x * y) = toFun x * toFun y

/-- `M →ₙ* N` denotes the type of multiplication-preserving maps from `M` to `N`. -/
infixr:25 " →ₙ* " => MulHom

end MulHom

/-- `MulEquiv α β` is the type of an equiv `α ≃ β` which preserves multiplication. -/
structure MulEquiv (M N : Type) [Mul M] [Mul N] extends M  N, M →ₙ* N

/-- Notation for a `MulEquiv`. -/
infixl:25 " ≃* " => MulEquiv

namespace MulEquiv

/-- The inverse of an isomorphism is an isomorphism. -/
def symm (e : M ≃* N) : N ≃* M :=
  e.toEquiv.symm, sorry

namespace MulEquiv

namespace case1 -- MWE from mathlib

-- lemma similar to something in mathlib
theorem symm_mk (f f') (h) :
    (⟨⟨f, f', h : M ≃* N).symm =
      {(⟨⟨f, f', h : M ≃* N).symm with
        toFun := f'
        invFun := f } :=
  rfl

attribute [local simp] symm_mk -- leads to breakage
-- attribute [simp] symm_mk' -- works

theorem works_as_expected (f f') (h) :
    (⟨⟨f, f', h : M ≃* N).symm =
      {(⟨⟨f, f', h : M ≃* N).symm with
        toFun := f'
        invFun := f } := by
  simp -- succeeds

/--
error: tactic 'simp' failed, nested error:
maximum recursion depth has been reached
use `set_option maxRecDepth <num>` to increase limit
use `set_option diagnostics true` to get diagnostic information
-/
#guard_msgs in
theorem fails_as_unexpected (f f') (h) :
    (⟨⟨f, f', h : M ≃* N).symm =
      {(⟨⟨f, f', h : M ≃* N).symm with
        toFun := f'
        invFun := f } := by
  dsimp -- fails, but `symm_mk'` is a rfl lemma

end case1

namespace case2 -- workaround by Eric Wieser

-- aux lemma for `symm_mk'`
theorem symm_mk'.aux (f f') (h :  x y, f (x * y) = f x * f y) (x y : N) :
    (f, f' : M  N).symm.toFun (x * y) =
      (f', f : N  M).toFun x * (f', f : N  M).toFun y :=
  (⟨⟨f, f', h : M ≃* N).symm.map_mul' _ _

-- variant of `symm_mk` above, but this one does not cause problems
theorem symm_mk' (f f') (h) :
    (⟨⟨f, f', h : M ≃* N).symm =
      { toFun := f'
        invFun := f
        map_mul' := symm_mk'.aux f f' h } :=
  rfl

attribute [local simp] symm_mk' -- works

theorem works_as_expected (f f') (h) :
    (⟨⟨f, f', h : M ≃* N).symm =
      {(⟨⟨f, f', h : M ≃* N).symm with
        toFun := f'
        invFun := f } := by
  simp -- succeeds

theorem does_not_fail (f f') (h) :
    (⟨⟨f, f', h : M ≃* N).symm =
      {(⟨⟨f, f', h : M ≃* N).symm with
        toFun := f'
        invFun := f } := by
  dsimp

end case2

end MulEquiv

Eric Wieser (Jun 13 2024 at 13:30):

Is there some elaborator foo% x which means "create an auxiliary declaration with value x, and reference it here"?

Johan Commelin (Jun 13 2024 at 14:22):

Johan Commelin said:

But do we want to have lemmas like this in our default simp-set? From that angle it still looks very suspicious to me.

Nevertheless :this:

Eric Wieser (Jun 13 2024 at 14:24):

I think the lemma is very reasonable to have in the dsimp set

Eric Wieser (Jun 13 2024 at 14:25):

⟨⟨f, f'⟩, h⟩ : M ≃* N).symm is clearly less simple than ⟨⟨f', f⟩, h2⟩ : M ≃* N)

Eric Wieser (Jun 13 2024 at 14:25):

The fact that h2 is a mess should be irrelevant, it's a proof term

Johan Commelin (Jun 13 2024 at 14:34):

But I think you should compare

 { toFun := f, invFun := f', map_mul' := h }.symm
-- vs
let __src := { toFun := f, invFun := f', map_mul' := h }.symm;
  { toFun := f', invFun := f, map_mul' :=  }

Maybe that let expression shouldn't appear in the RHS of a dsimp lemma.

Johan Commelin (Jun 13 2024 at 14:36):

In Lean 3 there was a tactic dsimp_result, which might have been handy here.

Matthew Ballard (Jun 13 2024 at 14:52):

What happens if you manually expand the RHS and change __src to src?

Johan Commelin (Jun 13 2024 at 15:17):

Like so?

theorem symm_mk (f f') (h) :
    (⟨⟨f, f', h : M ≃* N).symm =
      let src := (⟨⟨f, f', h : M ≃* N).symm;
  { toFun := f', invFun := f, map_mul' := src.map_mul' } :=
  rfl

Same problem...

Eric Wieser (Jun 13 2024 at 15:18):

The let is a distraction here, the problem persists if you inline the src

Johan Commelin (Jun 13 2024 at 18:15):

#13804 fixes some of these lemmas: dsimp visits proof subterms for a good reason

Matthew Ballard (Jun 13 2024 at 18:40):

To keep things well typed?

Johan Commelin (Jun 13 2024 at 18:40):

To get rid of artificial dependencies

Johan Commelin (Jun 13 2024 at 18:45):

Here is another mystery

variable {R M : Type}

class Zero (α : Type) where
  zero : α

instance (priority := 300) Zero.toOfNat0 {α} [Zero α] : OfNat α (nat_lit 0) where
  ofNat := Zero α›.1

/-- Typeclass for the `⊥` (`\bot`) notation -/
class Bot (α : Type) where
  /-- The bot (`⊥`, `\bot`) element -/
  bot : α

/-- The bot (`⊥`, `\bot`) element -/
notation "⊥" => Bot.bot

/-- Typeclass for types with a scalar multiplication operation, denoted `•` (`\bu`) -/
class SMul (M α : Type) where
  smul : M  α  α

infixr:73 " • " => SMul.smul

structure Submodule (R : Type) (M : Type) [Zero M] [SMul R M] where
  carrier : M  Prop
  zero_mem : carrier (0 : M)

variable [Zero M] [SMul R M]

instance : Bot (Submodule R M) where
  bot := (· = 0), rfl

instance : Zero (Submodule R M) where
  zero := 

@[simp]
theorem zero_eq_bot : (0 : Submodule R M) =  :=
  rfl

theorem ohai : (0 : Submodule R M) =  := by simp -- works

/-- error: dsimp made no progress -/
#guard_msgs in
theorem oops : (0 : Submodule R M) =  := by dsimp -- fails

Johan Commelin (Jun 13 2024 at 18:59):

Simpler:

class Zero (α : Type) where
  zero : α

instance (priority := 300) Zero.toOfNat0 {α} [Zero α] : OfNat α (nat_lit 0) where
  ofNat := Zero α›.1

/-- Typeclass for the `⊥` (`\bot`) notation -/
class Bot (α : Type) where
  /-- The bot (`⊥`, `\bot`) element -/
  bot : α

/-- The bot (`⊥`, `\bot`) element -/
notation "⊥" => Bot.bot

variable {α : Type} [Zero α]

structure copy (α : Type) where
  val : α

instance : Bot (copy α) where
  bot := 0

instance : Zero (copy α) where
  zero := 

@[simp]
theorem zero_eq_bot : (0 : copy α) =  :=
  rfl

theorem ohai : (0 : copy α) =  := by simp -- works

/-- error: dsimp made no progress -/
#guard_msgs in
theorem oops : (0 : copy α) =  := by dsimp -- fails

Eric Wieser (Jun 13 2024 at 19:06):

OfNat is to blame here, everything works with Zero.zero

Eric Wieser (Jun 13 2024 at 19:08):

Specifically, the builtin OfNat is special-cased internally, and that breaks things

Eric Wieser (Jun 13 2024 at 19:08):

It works fine if you roll your own:

class Zero (α : Type) where
  zero : α


/-- Typeclass for the `⊥` (`\bot`) notation -/
class Bot (α : Type) where
  /-- The bot (`⊥`, `\bot`) element -/
  bot : α

/-- The bot (`⊥`, `\bot`) element -/
notation "⊥" => Bot.bot

variable {α : Type} [Zero α]

structure copy (α : Type) where
  val : α

instance : Bot (copy α) where
  bot := Zero.zero

instance : Zero (copy α) where
  zero := 

class MyOfNat (α) (n : Nat) where
  ofNat : α

instance (priority := 300) Zero.toMyOfNat0 {α} [Zero α] : MyOfNat α (nat_lit 0) where
  ofNat := Zero α›.1

instance (priority := 300) Zero.toOfNat0 {α} [Zero α] : OfNat α (nat_lit 0) where
  ofNat := Zero α›.1

@[simp]
theorem zero_eq_bot : (MyOfNat.ofNat (nat_lit 0) : copy α) =  :=
  rfl

@[simp]
theorem zero_eq_bot' : (OfNat.ofNat (nat_lit 0) : copy α) =  :=
  rfl

theorem ohai : (MyOfNat.ofNat (nat_lit 0) : copy α) =  := by dsimp -- works

theorem oops : (OfNat.ofNat (nat_lit 0) : copy α) =  := by dsimp -- fails

Eric Wieser (Jun 13 2024 at 19:19):

possibly lean4#2867?

Johan Commelin (Jun 14 2024 at 05:42):

Could well be! So I think we should tag this one as nolint simpNF and add a comment pointing to that issue.

Johan Commelin (Jun 14 2024 at 06:51):

Another PR fixes two of the dsimpNF problems flagged by the (temporarily reverted) linter fix.

chore(Topology/Spectral/Hom): fix problem with dsimp lemma, fix porting note #13818

After this one, I think we are ready to re-revert the linter fix in batteries.

Johan Commelin (Jun 14 2024 at 09:01):

@Kim Morrison @Mario Carneiro I think we are ready to refix the linter.

Reapply "chore(Tactic/Lint/Simp): make simpNF linter dsimp-aware (batteries#839)" (batteries#841) batteries#844

Mario Carneiro (Jun 14 2024 at 23:32):

I think this PR needs a mathlib companion PR to ensure the issue is not repeated

Kim Morrison (Jun 15 2024 at 06:53):

If you could just add a link to a Mathlib PR using this Batteries PR, we can check CI looks good.

Johan Commelin (Jun 15 2024 at 18:42):

testing new dsimpNF linter #13855

just passed CI

Johan Commelin (Jun 15 2024 at 19:27):

I have also added a link to this testing PR as requested.

Kim Morrison (Jun 16 2024 at 00:36):

I've merged batteries#841, and will merge #13855 as soon as it passes. Thanks!

Johan Commelin (Jun 18 2024 at 20:35):

chore: remove many nolint simpNF #13889

tries to remove a bunch of nolint simpNFs. Is there an easy way to automate this? So far, I've just removed all of them, and then manually restored a whole bunch of them...

Yaël Dillies (Jun 18 2024 at 20:36):

Sounds like a job for Damiano auto-correction Testa

Michael Rothgang (Jun 18 2024 at 20:46):

... though this one is one level harder, as the simpNF linter is global on all of mathlib (if I understood correctly...)

Eric Wieser (Jun 18 2024 at 20:47):

I think a sensible direction would be to make nolint simpNF an error if the lemma does not trigger the linter in the first place

Eric Wieser (Jun 18 2024 at 20:47):

Of course, this doesn't help for cases where we nolint some_linter to prevent the linter crashing

Damiano Testa (Jun 18 2024 at 21:35):

Funny that you mention this, but I've been just writing a prototype "unnecessary syntax" linter that flags syntax uses that do not cause errors when removed. I can try it with no_lint simpNF when I'm back at a computer, if you want.

Damiano Testa (Jun 19 2024 at 22:05):

#13970 is a preliminary version of the "unnecessary nolint simpNF"-linter. This is a linter that flags when a flag to not lint for simpNF is in fact not necessary. :upside_down:

Damiano Testa (Jun 19 2024 at 22:05):

I look forward to a linter that flags when this linter is unnecessary.

Damiano Testa (Jun 19 2024 at 22:05):

If anyone wants to take a look at the nolints flagged by the linter, feel free to do so! I would like to make the linter self-correcting, but this one is a little challenging, since nolint takes a space-separated list of linters and nolint itself is in a comma-separated list of attributes, so either I rely on the pretty-printed syntax, or removing by hand the right stuff is going to be tricky.

Damiano Testa (Jun 19 2024 at 22:10):

Oh, if you do look at the nolints flagged by the linter and find some mistakes, please let me know!

As a by-product of this experiment, the linter essentially implements a syntax-version of the environment simpNFlinter. Thus, if there is interest in making the syntax simpNF linter available, then getting it to align with the environment linter would be good! Of course, the syntax linter has the advantage that it gives live warnings. However, the environment linter also flags auto-generated declarations and post-hoc attributes, which is trickier with the syntax linter.

Damiano Testa (Jun 20 2024 at 06:23):

#13982 removes 86 unused nolint simpNFs.

Damiano Testa (Jun 20 2024 at 06:25):

As a consequence of the removal, I also have essentially implemented a syntax version of the simpNF linter. It does the "local" checking, so misses some global lints, but flags issues "live".

Is there interest in polishing it and PRing it?

Damiano Testa (Jun 20 2024 at 06:27):

For reference, the linter flagged the 86 nolints in the PR, together with 22 extra ones that the global linter flags, but the local one does not.

Damiano Testa (Jun 20 2024 at 06:27):

I also wonder whether these "polishing up code" linters should self-correct and should only be run every once in a while by CI.

Damiano Testa (Jun 20 2024 at 07:10):

Kim, you delegated the removal of nolint simpNF, but you also PRed some adaptations where you add some nolints back: can I check somehow that the nolints that I would be removing would not have to be added back with the bump?

Damiano Testa (Jun 20 2024 at 07:15):

Specifically, some of the ones in Mathlib/Data/NNRat/Defs.lean may fit the general pattern of the ones you had to re-nolint.

Kim Morrison (Jun 20 2024 at 07:21):

Can you just run your machinery on nightly-testing?

Damiano Testa (Jun 20 2024 at 07:21):

Oh, sure: I'll look for the nightly-testing branch.

Damiano Testa (Jun 20 2024 at 07:54):

Ok, I am not sure that I will have much more time soon, but this is an initial observation, that may be helpful in case you know what else has changed!

The Expr-tree of the lhs of docs#Pi.one_apply

theorem one_apply [ i, One <| f i] : (1 :  i, f i) i = 1 :=

goes from being

-- on master
inspect: '1'

'OfNat.ofNat' -- app
|-'[anonymous]' -- app
|   |-'_uniq.421' -- fvar
|-'1' -- lit
|-'One.toOfNat1' -- app
|   |-'[anonymous]' -- app
|   |   |-'_uniq.421' -- fvar
|   |-'[anonymous]' -- app
|   |   |-'_uniq.421' -- fvar

to being

-- on nightly-testing
inspect: '1 i'

'OfNat.ofNat' -- app
|-('i') -- forallE
|   |-'_uniq.415' -- fvar
|   |-'[anonymous]' -- app
|   |   |-'0' -- bvar
|-'1' -- lit
|-'One.toOfNat1' -- app
|   |-('i') -- forallE
|   |   |-'_uniq.415' -- fvar
|   |   |-'[anonymous]' -- app
|   |   |   |-'0' -- bvar
|   |-'Pi.instOne' -- app
|   |   |-'_uniq.415' -- fvar
|   |   |-'_uniq.416' -- fvar
|   |   |-'_uniq.418' -- fvar
|-'_uniq.417' -- fvar

Eric Wieser (Jun 20 2024 at 08:59):

Surely that first one is a typo, and it shoudn't say inspect: '1'?

Damiano Testa (Jun 20 2024 at 09:01):

Actually, both should be correct: the LHS even pretty prints differently

Eric Wieser (Jun 20 2024 at 09:01):

So on master the LHS of (1 : ∀ i, f i) i = 1 is 1? Are you reducing somehow?

Damiano Testa (Jun 20 2024 at 09:02):

No, that is just what you see with the cursor right after :=

Damiano Testa (Jun 20 2024 at 09:03):

("no" was to reducing, yes, on master that is how the LHS looks like, just 1)

Damiano Testa (Jun 20 2024 at 09:08):

You are making me doubt, but I checked a few times. I'm on mobile now, though, but feel like I should be checking again! :smile:

Damiano Testa (Jun 20 2024 at 09:54):

I am now at a computer and I no longer see the lhs as 1, but instead as 1 i. I am not sure what was happening when I was looked at this earlier.


Last updated: May 02 2025 at 03:31 UTC