8
$\begingroup$

Say I have the following, straightforward-seeming functions:

makeARuleDelayed[a_, b_] :=
 With[{anotherRule = makeAnotherRuleDelayed[b]},
  v : a[b] :> ((v == 1 - v) /. anotherRule)];

makeAnotherRuleDelayed[x_] :=
  v : h_[x] :> foo[h, x];

If I use them to create a RuleDelayed, I get an error message:

a[b] /. makeARuleDelayed[a, b]

RuleDelayed::rhs: Pattern v$:h$_[b] appears on the right-hand side of rule v$:a[b]:>(v$==1-v$/. v$:h$_[b]:>foo[h$,b]) .

Examining the result indicates the problem:

v$ : a[b] :> (v$ == 1 - v$ /. v$ : h$_[b] :> foo[h$, b])

Using this rule fails in about the way you'd expect it to. The only workaround I could think of is adding a Module to the body of makeAnotherRule:

makeAnotherRuleDelayed[x_] :=
 Module[{v},
  v : h_[x] :> foo[h, x]]

This doesn't help at all; evidently there must be some sort of magical renaming going on somewhere inside of RuleDelayed that truncates the $nnn part of the name of the symbol generated by Module.

Without being able to nest rules without name clashes, I'm not sure how to go about creating nontrivial rules programmatically.

$\endgroup$
3
  • 1
    $\begingroup$ It seems to me that you somehow mixed up some of your functions names. Please try running the code you posted after a ClearAll $\endgroup$ Jun 18, 2012 at 19:58
  • 2
    $\begingroup$ @belisarius youtube.com/watch?v=xhy7dXWjpAA $\endgroup$
    – Mr.Wizard
    Jun 18, 2012 at 20:12
  • $\begingroup$ Pillsy I think you've forgotten something. Where is makeARule defined? $\endgroup$
    – Mr.Wizard
    Jun 18, 2012 at 20:51

3 Answers 3

11
$\begingroup$

This is a renaming mechanism at work. I think, the "canonical" way to fool it is something like this:

makeARuleDelayed[a_, b_] :=
  With[{anotherRule = makeAnotherRuleDelayed[b]},
     v : a[b] :> ((v == 1 - v) /. anotherRule)];

makeAnotherRuleDelayed[x_] :=
  RuleDelayed @@ Hold[v : h_[x], foo[h, x]];

By using RuleDelayed@@Hold, we fool the renaming mechanism of With. Note that this whole issue is because With is a scoping construct which cares about inner scoping constructs and possible name collisions. You can use rules instead, which are much more intruding:

Clear[makeARuleDelayedAlt];
makeARuleDelayedAlt[a_, b_] :=
    v : a[b] :> ((v == 1 - v) /. makeAnotherRuleDelayed[b])

and then keep the original definition for makeAnotherRuleDelayed. I discussed these issues in more detail here.

$\endgroup$
4
  • 1
    $\begingroup$ Wouldn't you prevent the whole renaming issue by using Module instead of With? $\endgroup$
    – Heike
    Jun 18, 2012 at 20:17
  • 1
    $\begingroup$ @Heike Yes, you are right - it works for this particular case, because it effectively does the same thing: at the time Module is resolving its bindings, it can not sense conflicts, because collision only happens at run-time. This is different for With, since it does substitutions earlier. But my suggestion is a general method, which will work in any such situation. $\endgroup$ Jun 18, 2012 at 20:27
  • $\begingroup$ +1 and accepted. The linked explanation of how With renames things was very helpful for finding a generalized solution. $\endgroup$
    – Pillsy
    Jun 19, 2012 at 15:24
  • $\begingroup$ @Pillsy Was happy to help, and thanks for the accept. $\endgroup$ Jun 19, 2012 at 15:40
7
$\begingroup$

Thanks to the answers left by Mr.Wizard and Leonid Shifrin (and especially the explanation of how With renames things that Leonid linked), I was able to come up with a general answer encapsulated by a function which will "sanitize" a RuleDelayed and make it "hygenic". First, in a bit of overkill, I use Unique and Remove to make truly unique symbols (like Lisp's GENSYMs):

ClearAll[toGensym, makeGensym];

toGensym[s_String] := With[{sym = Unique[s]}, Remove[sym]; sym]

Attributes[makeGensym] = HoldFirst;
makeGensym[s_Symbol] := toGensym[SymbolName@Unevaluated[s] <> "$"];

Now I can use Cases to collect all the names in a rule which name a pattern on the left-hand side, and replace them with gensyms.

ClearAll[Sanitize];
Sanitize[rule : HoldPattern[lhs_ :> rhs_]] :=
 With[{renamings =
    Cases[Hold[lhs],
     HoldPattern[Verbatim[Pattern][s_Symbol, _]] :>
      (HoldPattern[s] -> makeGensym[s]),
     Infinity,
     Heads -> True]},
  rule /. renamings];

Now I can just plug in Sanitize when I want to nest programmatically created rules:

ClearAll[makeARuleDelayed, makeAnotherRuleDelayed, a, b, foo];

makeARuleDelayed[a_, b_] :=
 With[{anotherRule = Sanitize@makeAnotherRuleDelayed[b]},
  v : a[b] :> ((v == 1 - v) /. anotherRule)];

makeAnotherRuleDelayed[x_] :=
 v : h_[x] :> foo[h, x];

I no longer get any error messages, though the resulting rules aren't really fit for human consumption anymore:

makeARuleDelayed[a, b]
v$: a[b] :> (v$== 1 - v$ /. Removed[v$$75]: (Removed[h$$74]:_)[b] :> 
 foo[Removed[h$$74],b])
a[b] /. makeARuleDelayed[a, b]
foo[a, b] == 1 - foo[a, b]
$\endgroup$
3
  • $\begingroup$ Good stuff - +1. I once wrote a tiny framework, which is somewhat similar in spirit but works the other way around - it tries to cure the leaks of the Mathematica lexical scoping (the latter exist because it is actually an emulation of lexical scoping) - I thought you might be interested. $\endgroup$ Jun 19, 2012 at 15:43
  • $\begingroup$ @LeonidShifrin Thanks for the link. I am, indeed, interested. :) $\endgroup$
    – Pillsy
    Jun 21, 2012 at 13:37
  • $\begingroup$ Sure, no problem :) $\endgroup$ Jun 21, 2012 at 13:47
4
$\begingroup$

Does this do what you desire?

makeARuleDelayed[a_, b_] := 
 v : a[b] :> ((v == 1 - v) /. #) & @ makeAnotherRuleDelayed[b]

makeAnotherRuleDelayed[x_] := v : h_[x] :> foo[h, x];

a[b] /. makeARuleDelayed[a, b]
foo[a, b] == 1 - foo[a, b]
$\endgroup$
0

Your Answer

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

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