Zulip Chat Archive

Stream: general

Topic: Question on `grind` multi-pattern matching with `a < b`


Ayhon (Dec 12 2025 at 15:52):

All code

I had a question about grind and multipatterns. I would like to teach grind to work with compare, in particular by recognizing that if a < b is in the context, then compare a b = .lt. At first, I thought it would be enough to define a multipattern where both a < b and compare a b must match. However, although this pattern is accepted, it doesn't appear to do what I would expect.

theorem foo(a b: Int): a < b  compare a b = .lt := by grind [Int.compare_eq_lt]

local grind_pattern foo => a < b, compare a b

example(n: Int): n < 0  compare n 0 = .lt := by
  grind -- Failure

If I remove a < b from my statement, the proof goes on fine. But why is grind getting hung up with the added a < b?

Robin Arnez (Dec 12 2025 at 16:12):

Look at the goal state in the failure of grind:

error: `grind` failed
case grind
n : Int
h : n + 1  0
h_1 : ¬compare n 0 = Ordering.lt
 False

grind normalized n < 0 to n + 1 ≤ 0 so it doesn't find an <. In general, it's best not to pattern on arithmetic stuff, in particular on Nat and Int (including <, , +, *) because grind may decide to do whatever kind of normalization to those.

Ayhon (Dec 12 2025 at 16:14):

But even putting the goal as a + 1 ≤ b doesn't fix the issue

Ayhon (Dec 12 2025 at 16:15):

I understand how, even if grind closed my goal when adding the parameter a + 1 ≤ b, this would not be still not be ideal. But I'm surprised that this doesn't seem to be the solution either. So there's something happening here which I don't understand

Ayhon (Dec 12 2025 at 16:18):

Some exhaustive checking I've tried: here

grind_pattern ex_lt =>      a + 1  b,          compare a b
grind_pattern ex_lt =>          a < b,          compare a b
grind_pattern ex_lt =>          b > a,          compare a b
grind_pattern ex_lt =>          b   a + 1,     compare a b
grind_pattern ex_lt =>   -1*a - 1   -1*b,      compare a b
grind_pattern ex_lt =>       -1*a > -1*b,       compare a b
grind_pattern ex_lt =>       -1*b < -1*a,       compare a b
grind_pattern ex_lt =>       -1*b  -1*a - 1,   compare a b

Jakub Nowak (Dec 13 2025 at 16:54):

This works

theorem foo(a: Int): a + 1  0  compare a 0 = .lt := by grind [Int.compare_eq_lt]

local grind_pattern foo => a + 1  0, compare a 0

example(n: Int): n < 0  compare n 0 = .lt := by
  grind -- Failure

Ayhon (Dec 13 2025 at 16:55):

I'm confused. What do you mean by "it works"?

Jakub Nowak (Dec 13 2025 at 16:55):

I forgot to remove Failure comment, but now grind does prove the goal.

Ayhon (Dec 13 2025 at 16:56):

Ah, I see

Jakub Nowak (Dec 13 2025 at 16:56):

It looks to me like grind's normalization is actually hindering us, instead of helping, but maybe it's because we're using grind wrong?

Ayhon (Dec 13 2025 at 16:59):

A friend suggested that I'd probably want to use gring_pattern constraints, probably guard.
https://github.com/leanprover/lean4/blob/38c401cf3bd02d1ebf7dcf134f941aee08fbc2db/src/Lean/Meta/Tactic/Grind/Parser.lean#L108

Jakub Nowak (Dec 13 2025 at 17:01):

Hmm, that could work.

Ayhon (Dec 13 2025 at 17:02):

Not available in 4.26 though :/

Jakub Nowak (Dec 13 2025 at 17:06):

You can test Lean nightly at https://live.lean-lang.org/#project=lean-nightly&codez=C4Cwpg9gTmC2AEAzCEAUBDeAjAXPAkgHbACUemAPNvIEmE8AxhLAA7ozyZbwC88AdABtg8HLywBPeAHMoAS0IATeAG0iwPoxZswAfTABHHUIC6AKFMBnMMB0RmwWRELxgUdPTB8Z8hXzjpgehA+eQtgdEIPFygAVzBzAQh6dAFpOUUdVmBgMChnZAgeAD4GJlZ2TngAd3AYU3hpGLYlSmxzMAAPdBYBMFRCPDUyeGcqAAZaUq12ZwneQWFRbHF6tJ94AFoN+AAxdFkBGLqgA by switching to nightly in the upper-right corner.

Jakub Nowak (Dec 13 2025 at 17:07):

Notice, how this fails:

theorem foo(a b: Int): a < b  compare a b = .lt := by grind [Int.compare_eq_lt]

set_option trace.grind.ematch.instance true

local grind_pattern foo => compare a b where
  guard a < b

example(n: Int): compare n 1 = .lt  compare n 2 = .lt := by
  grind -- Failure

While this doesn't:

theorem foo(a b: Int): a < b  compare a b = .lt := by grind [Int.compare_eq_lt]

set_option trace.grind.ematch.instance true

local grind_pattern foo => compare a b

example(n: Int): compare n 1 = .lt  compare n 2 = .lt := by
  grind

Ayhon (Dec 13 2025 at 17:08):

Hmm, :/

Jakub Nowak (Dec 13 2025 at 17:09):

I think that's good and what you expected? In the first case grind didn't finish the proof, because it never tried to use foo. The guard a < b prevented it from using it.

Ayhon (Dec 13 2025 at 17:11):

Oh, my bad! I hadn't noticed how you changed the examples (tbh, I was checking your messages while playing factorio, I have stopped now and I could look at it with more pause)

Ayhon (Dec 13 2025 at 17:12):

Yeah, that's a bit what I was expecting my multi-patterns to achieve

Jakub Nowak (Dec 13 2025 at 17:13):

Ah, sorry, I forgot to also mention, that it works with your original example:

theorem foo(a b: Int): a < b  compare a b = .lt := by grind [Int.compare_eq_lt]

set_option trace.grind.ematch.instance true

local grind_pattern foo => compare a b where
  guard a < b

example(n: Int): n < 3  compare n 3 = .lt := by
  grind

Ayhon (Dec 13 2025 at 17:13):

So I guess then that what the guard enables you to do is handle the problem of grind normalizing expressions a bit more gracefully.

Jakub Nowak (Dec 13 2025 at 17:15):

It can't prove this though:

theorem foo(a b: Int): a < b  compare a b = .lt := by grind [Int.compare_eq_lt]

set_option trace.grind.ematch.instance true

local grind_pattern foo => compare a b where
  guard a < b

example(n: Int): n < 4  compare n 3 = .lt := by
  grind -- Failure

So I'm guessing that grind doesn't make any special effort to try and prove the guard expression?

Ayhon (Dec 13 2025 at 17:16):

Hmm, interesting

Jakub Nowak (Dec 13 2025 at 17:19):

Ah, wait, no, it's me being dumb, that example was just false. This works:

theorem foo(a b: Int): a < b  compare a b = .lt := by grind [Int.compare_eq_lt]

set_option trace.grind.ematch.instance true

local grind_pattern foo => compare a b where
  guard a < b

example(n: Int): n < 2  compare n 3 = .lt := by
  grind

Ruben Van de Velde (Dec 13 2025 at 19:14):

A theorem being false is a common cause for it being hard to prove

Ayhon (Dec 13 2025 at 19:15):

I would even argue "the most common"


Last updated: Dec 20 2025 at 21:32 UTC