Zulip Chat Archive
Stream: lean4
Topic: Grind aggressively includes local hypotheses.
Wrenna Robson (Nov 11 2025 at 11:05):
In the following, if I write:
def idxToCount [BEq α] (xs : List α) (i : Nat) (hi : i < xs.length) : Nat := go xs i hi 0 where
@[specialize] go : (xs : List α) → (i : Nat) → i < xs.length → Nat → Nat
| _ :: _, 0, _, acc => acc
| x :: xs, i + 1, hi, acc =>
haveI hi := Nat.lt_of_succ_lt_succ hi
bif x == xs[i] then go xs i hi (acc + 1) else go xs i hi acc
Then go correctly only depends on its inner parameters, wheras if I write:
def idxToCount [BEq α] (xs : List α) (i : Nat) (hi : i < xs.length) : Nat := go xs i hi 0 where
@[specialize] go : (xs : List α) → (i : Nat) → i < xs.length → Nat → Nat
| _ :: _, 0, _, acc => acc
| x :: xs, i + 1, hi, acc =>
bif x == xs[i]'(by grind) then go xs i (by grind) (acc + 1) else go xs i (by grind) acc
then it gets a dependence on the outer xs etc.
Clearly this is undesirable behaviour. Can it be avoided?
Kim Morrison (Nov 11 2025 at 21:32):
What is the actual proof term grind produces here?
Wrenna Robson (Nov 11 2025 at 21:34):
I'm not at my desk so can't check. Would I use show_term?
Jovan Gerbscheid (Nov 12 2025 at 14:33):
Here's the minimization, including the generated proof term.
def oops (oh_no : List Nat) (hi : 0 < oh_no.length) : True := go where
go : True := by grind
#print oops._proof_1
/-
theorem oops._proof_1 : ∀ (oh_no : List Nat), 0 < oh_no.length → True :=
fun oh_no hi =>
Lean.Grind.intro_with_eq (0 < oh_no.length) (1 ≤ oh_no.length) True (Lean.Grind.Nat.lt_eq 0 oh_no.length)
(fun hi =>
Classical.byContradiction
(Lean.Grind.intro_with_eq (¬True) False False Lean.Grind.not_true fun h => False.casesOn (fun x => False) h))
hi
-/
Wrenna Robson (Nov 12 2025 at 16:38):
Aha, thank you.
Kim Morrison (Nov 23 2025 at 10:39):
This is fixed on the latest nightly, as a side effect of a new method of handling hypotheses.
Wrenna Robson (Nov 28 2025 at 09:35):
Oh brilliant - thank you for the update.
Markus Himmel (Nov 28 2025 at 11:39):
I am still suffering from this in one of my projects on nightly-2025-11-27. Here is my example:
theorem foo {n l r : Nat} (hn : ∃ d, n = 2 ^ d) (hlr : l < r) : l ≤ r - 1 := by
grind
The proof is long, but it starts like this:
theorem foo._proof_1_1 : ∀ {n l r : Nat}, (∃ d, n = 2 ^ d) → l < r → l ≤ r - 1 :=
fun {n l r} hn hlr =>
Classical.byContradiction
(Lean.Grind.intro_with_eq (¬l ≤ r - 1) (r - 1 + 1 ≤ l) False (Nat.not_le_eq l (r - 1)) fun h =>
id
(Exists.casesOn hn fun w h_1 =>
id
(Or.casesOn (Lean.Grind.em (-1 * ↑r + 1 ≤ 0))
(fun h_2 =>
This leads to a similar problem as in the original post:
def irrelevant (l r : Nat) (_hlr : l ≤ r - 1) : Nat :=
r - l
def irrelevant2 (n : Nat) (_hn : ∃ d, n = 2 ^ d) : Nat :=
n
def bar {n l r : Nat} (hn : ∃ d, n = 2 ^ d) (hlr : l < r) :=
baz + irrelevant2 n hn
where
baz : Nat :=
irrelevant l r (by grind)
-- I didn't want `n` and `hn` to appear in the signature of `baz` :(
/-- info: bar.baz {n l r : Nat} (hn : ∃ d, n = 2 ^ d) (hlr : l < r) : Nat -/
#guard_msgs in
#check bar.baz
Kim Morrison (Dec 02 2025 at 04:39):
@Markus Himmel, obviously we should make this better, but at least there is a workaround possible now:
theorem foo {n l r : Nat} (hn : ∃ d, n = 2 ^ d) (hlr : l < r) : l ≤ r - 1 := by
grind?
gives the suggestion:
grind => cases #aacf <;> cases #d8dd
which is certainly not very illuminating. However it's then easy to discover that
grind => cases #d8dd
gives the behaviour you want.
Kim Morrison (Dec 02 2025 at 04:39):
grind => cases?
gives a slightly more informative message, explaining which "anchor" corresponds to which case split.
Last updated: Dec 20 2025 at 21:32 UTC