4
$\begingroup$

When you use a lexically-scoped construct like With or Function to perform replacements in an expression, Mathematica might rename pattern names in that expression. It does so if the patterns appear on the lhs of a lexical scoping construct (such as Rule) whose rhs is altered by performing the replacement.

For example, we have

With[{s = c}, {t_ -> s, t_ -> 1}]

(* Out: {t$_ -> c, t_ -> 1} *)

Mathematica does this (I'm guessing) to prevent binding conflicts; if t_ lexically scopes the rhs of -> in t_ -> s, then we don't want to accidentally bind any t's that might occur in c once we substitute it in. For example, if we instead had s = f[t], we might accidentally create t_ -> f[t].

How do I prevent this? Since I'm trying to extract and manipulate pattern names from expressions, this is a bit of an issue for me! So far I've found two possibilities, but I don't know if I can trust them or why.

I'd be any sort of lexical replacement mechanism; but it would be cool to see a general-purpose way to prevent things from being renamed! :)

Work so far

1. The first possibility comes from noticing that Function only seems to rename variables when using a symbol as a formal parameter, and that e.g.

(t_ -> # &)[c]

(* Out: t_ -> c *)

might not ever rename anything. However, this makes replacements which require formal parameters (e.g. when having functions within functions and no other way to eliminate the ambiguity between which argument belongs to which function) difficult. So, I'd say this is only a partial solution without some other technique.

2. The second possibility comes from the sense that maybe we can "freeze" the expressions on the lhs's and prevent them from being touched by the renaming mechanism. Unfortunately, Hold (and all its evaluation-controlling variations, like Unevaluated and Inactivate) don't get in the way. However, Verbatim seems to, at least when surrounding (only) the scoping pattern:

With[{s = c}, {Verbatim[t_] -> s, Verbatim[t_ -> s]}]

(* Out: {Verbatim[t_] -> c, Verbatim[t$_ -> c]} *)

Is this robust? I couldn't see anything in the docs about it (but I could have missed it). This seems to work, but I have no guarantee that it does. So, that would also be useful as an answer, even if not the "holy grail" of turning off/reverting all $-renaming. :)

Further Background

In With[{s = c}, {t_ -> s, t_ -> 1}], since the rhs of t_ -> 1 is not touched by the replacement, there's no need to rename patterns on its lhs. In general, though, any touching of the rhs causes all variables that scope it on the lhs to be renamed:

With[{s = 1}, {a_, b_} -> s]

(* {a$_, b$_} -> 1 *)

At the top I said MMA renames "patterns which appear on the lhs of a lexical scoping construct...", but to be more precise I should have said something like "which are bound by a lexical scoping construct...", as being on the "lhs" is not always the criterion for being bound (and thus having a scope).

So, replacement happens provided the pattern names on the lhs actually do scope the part of the expression the replacement touched! If the scope of a given pattern name does not actually include the replacement, e.g. if the lhs is bound by some outer Rule instead, it is not renamed. Compare the two elements of the list output below:

With[{s = 1}, {(t_ -> s) -> 2, (t_ -> 2) -> s}]

(* Out: {(t_ -> 1) -> 2, (t$_ -> 2) -> 1} *)

The scope of t_ in (t_ -> s) -> 2 is 2, not s, as the outer -> binds it first (as far as I can tell).

There's also a discussion of this at this question, but it doesn't answer the question here. It's also discussed in the docs here.

Aside

If you try to "anticipate" the renaming by using e.g. t$ in your replacement expression, you can get away with inducing a binding:

With[{s = f[t$]}, t_ :> s]

(* Out: t$_ :> f[t$] *)

w /. With[{s = f[t$]}, t_ :> s]

(* Out: f[w] *)

This seems like an oversight—or maybe it's in the name of efficiency, and we just trust no one would ever feel ok ending a variable with $ in the first place?

If you're not careful, even two expressions where everything's the same, but you chose to write in the With constant explicitly, can wind up different:

3 /. {With[{d = 1}, With[{s = (e[t_] :> d)}, {t_ :> s}]], 
      With[{d = 1}, With[{s = (e[t_] :> 1)}, {t_ :> s}]]}

(* Out: {e[Pattern[3, _]] :> 1, e[t_] :> 1} *)

Mathematica warns you something's weird with RuleDelayed::rhs, but still. Can anyone find any other exploits?

$\endgroup$
3
  • 5
    $\begingroup$ The 'usual' approach is With[{s = c}, Rule @@@ {{t_, s}, {t_, 1}}] see full discussion in Enforcing correct variable bindings... $\endgroup$
    – Kuba
    Commented Aug 13, 2021 at 8:48
  • 1
    $\begingroup$ @Kuba ah, right, the good old "construct it afterwards"—which I'm doing two lines above in the same cell, for not too dissimilar reasons. of course...well, that's what i get for coding after 3 am, I guess :) thx! I'm still interested in whether/why Verbatim is a good idea, and whether there's a "general" way, so i encourage ppl to still answer, but that does work for now! $\endgroup$
    – thorimur
    Commented Aug 13, 2021 at 9:12
  • 1
    $\begingroup$ Note that using Verbatim changes the meaning of the pattern: Verbatim[t_] will no longer match any expression, but only a literal t_. This also explains the lack of renaming: The t is no longer a named pattern, but part of the thing to match (so renaming it would actually change the meaning) $\endgroup$
    – Lukas Lang
    Commented Aug 13, 2021 at 19:24

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.