Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

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.

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

3 Answers 3

up vote 10 down vote accepted

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.

share|improve this answer
1  
Wouldn't you prevent the whole renaming issue by using Module instead of With? –  Heike Jun 18 '12 at 20:17
1  
@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. –  Leonid Shifrin Jun 18 '12 at 20:27
    
+1 and accepted. The linked explanation of how With renames things was very helpful for finding a generalized solution. –  Pillsy Jun 19 '12 at 15:24
    
@Pillsy Was happy to help, and thanks for the accept. –  Leonid Shifrin Jun 19 '12 at 15:40

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]
share|improve this answer
    
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. –  Leonid Shifrin Jun 19 '12 at 15:43
    
@LeonidShifrin Thanks for the link. I am, indeed, interested. :) –  Pillsy Jun 21 '12 at 13:37
    
Sure, no problem :) –  Leonid Shifrin Jun 21 '12 at 13:47

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]
share|improve this answer
    
+1. It does in a lot of situations. –  Pillsy Jun 19 '12 at 15:23

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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