Zulip Chat Archive

Stream: new members

Topic: why does `rewrite at` create a new variable?


Yannick Seurin (Nov 18 2025 at 10:55):

In the following example, why does tactic rw at create a new variable? I've never encountered this behavior before.
(NB: replacing the line rw [this] at b with simp only [nfoldProd] at b works, i.e., it yields b : β without creating a new variable; but curiously, simp only [zero_add] at b also creates a new variable.)

import Mathlib

def nfoldProd.{u} (α : Type u) :   Type u
  | 0     => PUnit
  | 1     => α
  | n + 2 => α × nfoldProd α (n + 1)

def nfoldMap.{u,v} {α : Type u} {β : Type v} (f : α  β) : (n : )  nfoldProd α n  nfoldProd β n
  | 0, _       => PUnit.unit
  | 1, x       => f x
  | n + 2, (x, xs) => (f x, nfoldMap f (n + 1) xs)

#eval nfoldMap (· + 1) 3 (7, (8, (9 : ))) -- (8, 9, 10)

variable {α β : Type} {f : α  β}

theorem Function.bijective_nfold (n : ) (hf : Function.Bijective f) :
    Function.Bijective (nfoldMap f n) := by
  apply (Function.bijective_iff_existsUnique (nfoldMap f n)).mpr
  intro b
  cases n with
    | zero =>
      simp only [nfoldMap]
      exact existsUnique_eq
    | succ n =>
      induction' n with n ih
      · simp only [Nat.reduceAdd, nfoldProd, nfoldMap] at *
        have : nfoldProd β (0 + 1) = β := rfl
        rw [this] at b -- creates b✝ : nfoldProd β (0 + 1) and replaces b by b✝ in the goal
        sorry
      sorry

Aaron Liu (Nov 18 2025 at 11:05):

It's because of b occurs in the goal and you rewrite it's type there's a change the goal will not typecheck anymore, so to be safe it gets duplicated. The reason simp doesn't do this sometimes is probably that it keeps track of if the transformation is defeq (if actually only dsimp would have done the same thing) and since everything is definitional it knows it doesn't have to make a new variable.

Yannick Seurin (Nov 18 2025 at 12:38):

Thanks Aaron. So are you saying that any time one does a rw [...] at h and h appears in the goal, this will happen?

Aaron Liu (Nov 18 2025 at 13:09):

yes, that's correct

Aaron Liu (Nov 18 2025 at 13:09):

and not just the goal, but anywhere

Aaron Liu (Nov 18 2025 at 13:09):

so it could be because it's used in the type of another hypothesis, or in the value of a local let-decl

Yannick Seurin (Nov 18 2025 at 14:07):

OK, thanks! I don't think I've seen this explained anywhere, might be worth an addition to rewrite documentation.

Aaron Liu (Nov 18 2025 at 16:43):

It's not specific to rewrite, there's a function docs#Lean.MVarId.replaceLocalDecl and all the tactics that use this function in their implementation will show the same behavior


Last updated: Dec 20 2025 at 21:32 UTC