Zulip Chat Archive

Stream: new members

Topic: Why can't rw do unfolding?


Dan Abramov (Sep 10 2025 at 15:43):

I suspect that it's a very common beginner mistake to do rw [Foo] to unfold a let Foo := binding.

I've gotten used to doing simp only [Foo] or unfold Foo at this point, but this got me wondering — why not just allow it?

Clearly simp can do it, why not give this ability to rw too? Is there any fundamental reason why it's a bad idea, or is it historical?

Aaron Liu (Sep 10 2025 at 15:46):

implementation-wise, it's a bit different from any of the other things that rw does

Dan Abramov (Sep 10 2025 at 15:46):

Another related question is I often want to do the inverse of unfolding. I.e. I have some existing definition let Foo := bar baz and I have bar baz somewhere in the goal, and I wish it would just say Foo instead everywhere for clarity. I (think) I can achieve that by redeclaring with set like set Foo := bar baz but that's weird. What intuitively I want to write is rw [← Foo] in this case.

Kyle Miller (Sep 10 2025 at 15:46):

It's on the roadmap for the new rw :-)

I'm looking forward to rw [<- Foo] for some refolding as well.

Kyle Miller (Sep 10 2025 at 15:47):

(You beat me to hitting enter)

Dan Abramov (Sep 10 2025 at 15:47):

Super nice, looking forward to it!

Julia Scheaffer (Sep 10 2025 at 15:49):

Would this just do the same thing as rw [Foo.eq_def] and rw [<- Foo.eq_def]?

Aaron Liu (Sep 10 2025 at 15:50):

well Foo doesn't have an eq_def lemma since it's a local decl

Aaron Liu (Sep 10 2025 at 15:51):

example : 0  1 := by
  let Foo := 0
  -- doesn't work
  rw [ Foo.eq_def]

Julia Scheaffer (Sep 10 2025 at 15:51):

Ah, makes sense now. Thanks

Kyle Miller (Sep 10 2025 at 15:52):

I think basically any question that goes "why can simp/rw do such-and-such which rw/simp can't" right now has the answer "we're working on a unified rewrite that can do what both do". At least as far as what can be rewritten. The simp tactic is meant for applying any rewrite rules that can apply, so it's got some fundamental differences. Still, every simp call should be translatable into some rw in the future.

Kyle Miller (Sep 10 2025 at 15:53):

@Dan Abramov Do you know about refold_let?

Kyle Miller (Sep 10 2025 at 15:55):

Was it you or someone else that complained about how unfold didn't work with let definitions? It used to be only possible with unfold_let or simp, but we changed unfold to handle let definitions too.

I wonder if there ought to be a low-level refold tactic that can handle both top-level definitions and let definitions? It might not be necessary with the new rw, but specialized low-level tactics can sometimes be nice to have.

Alfredo Moreira-Rosa (Sep 10 2025 at 15:59):

What would be nice also, is automatic unfolding up to a custom threshold, not necessarily visibility boundaries like opaque.
For ex, if i define

@[level 3]
def Foo := ....

Then it would be nice if simp +level 3 [...] would automatically unfold everything tagged with levels 1 to 3.

Ruben Van de Velde (Sep 10 2025 at 16:00):

TIL about the unified rewrite :)

Dan Abramov (Sep 10 2025 at 16:07):

Kyle Miller said:

Do you know about refold_let?

I did not! But I'm also not sure what it does (I did read the docstring several times but the meaning eludes me without an example, and trying to apply it in a few places seems to yield no result).

Kyle Miller (Sep 10 2025 at 16:09):

refold_let Foo should be like "rw [<- Foo]".

It's got some limitations, for example if Foo is a function it very likely won't work. It doesn't know how to un-beta-reduce. There are also some limitations with refold_let x at h; it won't refold if x was defined after h. I think you have to revert h; refold_let x; intro h or something similar.

Kyle Miller (Sep 10 2025 at 16:09):

There are a few examples of it in MathlibTest/DefEqTransformations.lean

Dan Abramov (Sep 10 2025 at 16:35):

Ah interesting, it seems to work at h when h is a hypothesis but not at other let bindings (which is often where I want it).

Dan Abramov (Sep 10 2025 at 16:39):

Might be something more subtle, I'll see if I can extract it next time I can't get it working.

Kyle Miller (Sep 10 2025 at 16:41):

(The revert trick is a good one to remember. It's often a way to make let values accessible to such tactics.)

Kyle Miller (Sep 10 2025 at 16:44):

The tactic definitely doesn't affect let values. It uses Mathlib.Tactic.runDefEqTactic, which only touches types.

There's no good reason that it doesn't. This is a gap in Lean's tactic design in general — it's unclear to what extent tactics are meant to operate on let values. Or, should they operate on just the value?

Kyle Miller (Sep 10 2025 at 16:45):

In Rocq it's possible to specify whether you want to rewrite the value or the type. Lean's at syntax doesn't have that specificity.

Dan Abramov (Sep 10 2025 at 16:45):

Ohhh I get it! I guess I haven't fully conceptualized that I'm usually rewriting types.

Robin Arnez (Sep 10 2025 at 17:08):

I actually wrote something that allows you to rewrite on values some time ago:

add_value, simp_value and conv_value tactics

Klaus Gy (Sep 11 2025 at 09:11):

@Dan Abramov I asked a similar question recently, maybe you find something relevant in that thread: https://leanprover.zulipchat.com/#narrow/channel/113489-new-members/topic/rw.20works.20with.20defs.20but.20not.20with.20lets


Last updated: Dec 20 2025 at 21:32 UTC