Zulip Chat Archive

Stream: new members

Topic: Strange behavior of `simp`


Ching-Tsun Chou (May 23 2025 at 01:56):

Why does the first example below work but the second doesn't? Lean tells me that simp made no progress. That doesn't make any sense.

import Mathlib
open Classical

example (k : ) (h : k > 0) : 42 = if k = 0 then 13 else 42 := by
  have h' := (by omega : k  0)
  simp [h']

example (k : ) (h : k > 0) : 42 = if k = 0 then 13 else 42 := by
  simp [(by omega : k  0)]
  sorry

Kyle Miller (May 23 2025 at 02:04):

The short answer is that simp [show k ≠ 0 by omega] works.

Kyle Miller (May 23 2025 at 02:06):

The longer answer is that have h' := (by omega : k ≠ 0) only works because of the order that unification happens. Roughly, by omega is deferred and is a metavariable, it gets the type k ≠ 0, and this unifies with the type of h' (which starts as a metavariable) which "locks in" the type.

Kyle Miller (May 23 2025 at 02:07):

However, as a simp argument, (by omega : k ≠ 0) doesn't have any such place the intended type is stored. Tactics don't guarantee that the term they produce is structurally the expected type, any more than other terms do. Using show adds in a discretionary term that ensures the type of the term is what you want.

Kyle Miller (May 23 2025 at 02:08):

You can verify it:

variable (k : ) (h : k > 0)
#check (by omega : k  0)
-- fun a => _check._proof_1 k h a : k = 0 → False

So omega creates a term of type k = 0 → False.

Kyle Miller (May 23 2025 at 02:09):

There are other ways to incidentally capture the type too. For example simp [id (by omega : k ≠ 0)].

Ching-Tsun Chou (May 23 2025 at 02:16):

But putting (by omega : ...)inside the simp bracket seems to work most of the time. I just occasionally encounter the above behavior.

Kyle Miller (May 23 2025 at 03:11):

Yes, that is consistent with the fact that tactics don't guarantee that the term they produce has a type that is structurally the expected type. When it happens to work, it happens to work.

Mario Carneiro (May 23 2025 at 03:14):

if the proof gets extracted, it seems like it should have saved the type at that point though

Kyle Miller (May 23 2025 at 03:21):

What do you mean by 'extracted' @Mario Carneiro?

If simp fully elaborates each term in the simp list before taking a look, then even though the by metavariable has k ≠ 0 as its type, once the tactic assigns a term to the metavariable, that's gone. Tactics don't generally insert type hint terms, right?

Mario Carneiro (May 23 2025 at 03:21):

note that the proof above is _check._proof_1 and not a proof term

Mario Carneiro (May 23 2025 at 03:22):

at the point of extraction, you have to decide on the type of the term to extract

Mario Carneiro (May 23 2025 at 03:22):

and the logical choice for this is the expected type you started with, which if done would ensure that you get k ≠ 0 in this example

Kyle Miller (May 23 2025 at 03:22):

Omega proves by contradiction though, so I'm not surprised :shrug:

Kyle Miller (May 23 2025 at 03:23):

This is a common gotcha, the fact that type ascriptions don't ensure structural type hints, and the tip is to use show instead.

Mario Carneiro (May 23 2025 at 03:23):

the actual proof approach of omega doesn't really matter here

Mario Carneiro (May 23 2025 at 03:23):

because whatever it does is wrapped in "extract the result of this tactic"

Mario Carneiro (May 23 2025 at 03:24):

I'm saying that extraction is another kind of show

Kyle Miller (May 23 2025 at 03:24):

I'm not sure what point you're making — "since omega does extract proofs, it should try to use the expected type even though there's no guarantee that tactics in general create types that are structurally the expected type"?

Mario Carneiro (May 23 2025 at 03:24):

yes

Mario Carneiro (May 23 2025 at 03:24):

although many tactics do ensure this property

Mario Carneiro (May 23 2025 at 03:25):

norm_num pays specific attention to this, not because of making : T) work but because defeq unfolding can cause surprising slowdowns when the kernel decides to unfold the wrong thing

Mario Carneiro (May 23 2025 at 03:26):

dsimp also specifically puts in unfolding hints

Mario Carneiro (May 23 2025 at 03:26):

in fact most cases where the hints were skipped later get refactored when it turns out it was a bad idea

Kyle Miller (May 23 2025 at 03:51):

Whether or not there are concerns about the terms omega is creating, it's still not something you can rely on, that a tactic produces a term whose type is structurally equal to the expected type. (E.g. exact, refine, apply, intro, constructor, and so on.)

Using "show k ≠ 0 by omega" rather than a type ascription is a way to ensure the type is k ≠ 0.


Last updated: Dec 20 2025 at 21:32 UTC