I want to use a replacement rule to change log[var] into var inside a function. A minimal example follows. In one case it works; in the other it doesn't. Could someone explain the difference and how to fix in the second case?

unks = {log[n]};

foo1[x_] := Table[(unk /. log[var_] -> var) -> 5, {unk, unks}];
foo2[x_] := Table[(unk /. log[var_] -> var) -> x, {unk, unks}];

(* {n -> 5} -- good *)
(* {var -> 5} -- bad *)

Applying Trace shows that the bad foo2 contains log[var$_] -> var, which seems like the source of the problem. What's that $ doing there?

  • 1
    $\begingroup$ Looks like the x_ is causing the lexical scoping mechanism to come into play but the HoldFirst on Table is breaking that. Check out this: foo3[x_] := With[{reps={log[var_] :> var}}, Table[(unk/.reps)->x, {unk, unks}]] $\endgroup$ – b3m2a1 Mar 12 '19 at 8:33
  • $\begingroup$ Also just gonna throw it out there since I think chances are good you know it already, but you're good on the fact that this is probably not the most less efficient way to do this, right? Better would be to do a more targeted Replace[..., rule, {1}] on unks first I imagine. Or if you need it deeper just to the /. there first or do a Replace[..., rule, {1, Infinity}] or something along those lines. $\endgroup$ – b3m2a1 Mar 12 '19 at 8:34
  • 1
    $\begingroup$ @b3m2a1 Don't assume anything about my programming knowledge! But in my real problem, I've already got something like {log[n] -> some stuff, log[m] -> some other stuff} that I need to transform to {n -> some (slightly different) stuff, m -> some other (slightly different) stuff}. $\endgroup$ – Chris K Mar 12 '19 at 9:04
  • $\begingroup$ might be a place where something like Thread[Replace[Keys[#], log[n_]:>n, {1}]->Values[#]]& might be the least likely to have side effects. Alternately create your replacement rules first with Dispatch to get most of the benefit of vectorizing the call. $\endgroup$ – b3m2a1 Mar 12 '19 at 9:20
  • $\begingroup$ @b3m2a1 The HoldFirst of Table doesn't seem to be the issue: 1 /. x_ -> ((var_ -> var) -> x) returns (var$_ -> var) -> 1, which is already broken. From a few quick tests, it seems that all the rules in the example are needed for it to return strange results. $\endgroup$ – Lukas Lang Mar 12 '19 at 11:54

This is the same issue as described here (and in references therein). I suggest to read that answer and the linked discussions for an explanation of why this happens.

The workaround can be also the same as mentioned there:

foo2[x_] := 
   With[{rule = Rule},
      Table[rule[(unk /. log[var_] -> var), x], {unk, unks}]

So that now


(* {n -> 5} *)

Basically, it fools RuleDelayed (the one that results from SetDelayed, in DownValues[foo2]), so that it does not recognise the inner rule as such, and so does not rename var in one part of the inner rule during the rule application.

| improve this answer | |
  • $\begingroup$ Thanks, especially for the detailed explanation of what's actually going on. $\endgroup$ – Chris K Mar 12 '19 at 13:54
  • $\begingroup$ @ChrisK Was glad to help. Thanks for the accept. $\endgroup$ – Leonid Shifrin Mar 12 '19 at 13:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.