Zulip Chat Archive

Stream: lean4

Topic: Trouble with a `rw`


Mac (Feb 23 2022 at 22:11):

So I have been toying around with Nat.add that is not left or right recursive and while trying to prove add_succ I ran into the following problem, which I think may be an bug in Lean, but I am not sure.

open Nat (zero succ)

def add : Nat  Nat  Nat
| 0, 0 => 0
| succ n, 0 => succ n
| 0, succ n => succ n
| succ m, succ n => succ (succ (add m n))

infixl:65 (priority := high) " + " => add

instance : Add Nat := add -- removing this fixes the theorem error

@[simp] theorem succ_zero : succ 0 = 1 := rfl

@[simp] theorem zero_add_zero : 0 + 0 = 0 := rfl

@[simp] theorem add_zero : n + 0 = n := by cases n; repeat rfl
@[simp] theorem zero_add : 0 + n = n := by cases n; repeat rfl

theorem succ_add_succ : succ m + succ n = succ (succ (m + n)) := rfl

@[simp] theorem add_one : n + 1 = succ n := by
  cases n with | zero => rfl | succ n => rw [succ_add_succ, add_zero]

@[simp] theorem one_add : 1 + n = succ n := by
  cases n with | zero => rfl | succ => rw [succ_add_succ, zero_add]

-- error: theorem fails despite goals accomplished
theorem add_succ {m n : Nat} : m + succ n = succ (m + n) := by
  revert m
  induction n with
  | zero => simp
  | succ n ih =>
    intro m
    cases m with
    | zero => simp
    | succ m =>
      rw [succ_add_succ, succ_add_succ] -- produces an `n + 1` for some reason
      show succ (succ (m + succ n)) = succ (succ (succ (m + n)))
      rw [@ih m]

The error on add_succ is as follows:

application type mismatch
  @Eq.ndrec Nat (succ m + succ (n + 1))
    (fun _a => (succ m + succ (succ n) = succ (succ m + succ n)) = (_a = succ (succ m + succ n)))
    (Eq.refl (succ m + succ (succ n) = succ (succ m + succ n)))
argument has type
  (succ m + succ (succ n) = succ (succ m + succ n)) = (succ m + succ (succ n) = succ (succ m + succ n))
but function has type
  (fun _a => (succ m + succ (succ n) = succ (succ m + succ n)) = (_a = succ (succ m + succ n)))
      (succ m + succ (n + 1)) →
    ∀ {b : Nat},
      succ m + succ (n + 1) = b →
        (fun _a => (succ m + succ (succ n) = succ (succ m + succ n)) = (_a = succ (succ m + succ n))) b

I am very confused as to why the n + 1 is introduced during rewriting. I am also curious as to why removing the Add instance fixes the error (and why the n + 1 still appears even then). Is this some weird bug in rw (or show) or are there details I am missing?

Mario Carneiro (Feb 23 2022 at 22:47):

Here's a minimized version:

open Nat

def add : Nat  Nat  Nat
| 0, 0 => 0
| succ n, 0 => succ n
| 0, succ n => succ n
| succ m, succ n => succ (succ (add m n))

instance : Add Nat := add -- removing this fixes the theorem error

theorem succ_add_succ : succ m + succ n = succ (succ (m + n)) := rfl

theorem add_succ : succ m + succ (succ n) = 0 := by
  rw [succ_add_succ]
  sorry

This is certainly a bug, and my guess is that it is caused by something that has a hardcoded match on Add.add Nat thinking that it's surely Nat.add and your alternate definition is confusing it. (Lean 3 simp has the same problem with numerals encoded using nonstandard instances of standard typeclasses)

Kevin Buzzard (Feb 24 2022 at 05:24):

So the problem should go away if you try this on a custom nat'?

James Gallicchio (Jan 08 2024 at 20:20):

I seem to have run into this bug trying to simplify a definition not involving Nat. I'll try to minimize but it's pretty deep in LeanSAT's propositional theory.

James Gallicchio (Jan 08 2024 at 21:08):

ok, removed all of our library imports, now it just has one mathlib import. if someone else has time I'd appreciate help reducing further:

import Mathlib.Data.Set.Finite

abbrev PropFun (ν : Type u) := (ν  Bool)  Bool

namespace PropFun

variable [DecidableEq ν]

def semVars' (φ : PropFun ν) : Set ν :=
  { x |  (τ : ν  Bool), φ (Function.update τ x false) }

instance semVars'_finite (φ : PropFun ν) : Set.Finite φ.semVars' := sorry

noncomputable def semVars (φ : PropFun ν) : Finset ν :=
  Set.Finite.toFinset φ.semVars'_finite

def invImage [DecidableEq ν'] (f : ν  ν')
      (vs : Finset ν) (φ : PropFun ν')
      (h : φ.semVars  vs.map f) : PropFun ν :=
  sorry

end PropFun

abbrev Cnf (L : Type u) := Array (Array L)

namespace Cnf

def toPropFun (φ : Cnf L) : PropFun L := sorry

def addClause (C : Array L) (f : Cnf L) : Cnf L := f.push C

theorem toPropFun_addClause (C : Array L) (f : Cnf L)
  : (f.addClause C).toPropFun = f.toPropFun
  := sorry

noncomputable def interp [DecidableEq L] (s : Cnf L) : PropFun L :=
  s.toPropFun.invImage sorry sorry sorry

theorem interp_addClause [DecidableEq L]
        (C : Array L) (s : Cnf L)
  : interp (addClause C s) = sorry := by
  ext τ
  unfold addClause
  unfold interp
  simp [Array.push]
  sorry

Kim Morrison (Jan 09 2024 at 01:29):

Presumably the problem here is with a instance resynthesisation: some non-canonical instance is being replaced by simp / rw with a canonical instance inappropriately.

James Gallicchio (Jan 09 2024 at 04:35):

I see -- so is the "bug" here is just an unclear error message? Or is the re-synthesizing behavior not intended?

Kim Morrison (Jan 09 2024 at 05:11):

No, Mario's example above definitely counts as a bug to me. The aggressive resynthesisation of instances is new in Lean 4. So far I've been surprised how unproblematic it is, but I think this is an example of it causing problems.


Last updated: May 02 2025 at 03:31 UTC