6

Say I have a list of Rules

rules = {a -> b, c -> d};

which I use throughout a notebook. Then, at one point, it makes sense to want the rules to apply before any other evaluations take place in an expression. Normally if you want something like this you would use

In[2]:= With[{a=b,c=d}, expr[a,b,c,d]]
Out[2]= expr[b, b, d, d]

How can I take rules and insert it into the first argument of With?


Edit

BothSome solutions fail do all that I was looking for - but I should have emphasised this point a little more. See the bold part above.

For example, let's look at

rules = {a -> {1, 2}, c -> 1};

If I use these vaules in With, I get

In[10]:= With[{a={1,2},c=1}, Head/@{a,c}]
Out[10]= {List,Integer}

Some versions of WithRules yield

In[11]:= WithRules[rules, Head/@{a,c}]
Out[11]= {Symbol, Symbol}

(Actually, I didn't notice that Andrew's answer had the Attribute HoldRest - so it works just like I wanted.)

Simon
  • 14,631
  • 4
  • 41
  • 101

3 Answers3

11

You want to use Hold to build up your With statement. Here is one way; there may be a simpler:

In[1]:= SetAttributes[WithRules, HoldRest]

In[2]:= WithRules[rules_, expr_] := 
 With @@ Append[Apply[Set, Hold@rules, {2}], Unevaluated[expr]]

Test it out:

In[3]:= f[args___] := Print[{args}]

In[4]:= rules = {a -> b, c -> d};

In[5]:= WithRules[rules, f[a, c]]

During evaluation of In[5]:= {b,d}

(I used Print so that any bug involving me accidentally evaluating expr too early would be made obvious.)

Andrew Moylan
  • 2,893
  • 18
  • 20
2

I have been using the following form of WithRules for a long time. Compared to the one posted by Andrew Moylan, it binds sequentially so that you can say e.g. WithRules[{a->b+1, b->2},expr] and get a expanded to 3:

SetAttributes[WithRules, HoldRest]
WithRules[rules_, expr_] := ReleaseHold@Module[{notSet}, Quiet[
     With[{args = Reverse[rules /. Rule[a_, b_] -> notSet[a, b]]},
       Fold[With[{#2}, #1] &, Hold@expr, args]] /. notSet -> Set, 
   With::lvw]]

This was also posted as an answer to an unrelated question, and as noted there, it has been discussed (at least) a couple of times on usenet:

HTH

EDIT: Added a ReleaseHold, Hold pair to keep expr unevaluated until the rules have been applied.

Community
  • 1
  • 1
Janus
  • 5,421
  • 2
  • 26
  • 37
  • Thanks Janus! Now that you mention it, I remember at least one of those "sequential `With`" [discussions in mathgroup](https://groups.google.com/forum/?hl=en#!topic/comp.soft-sys.math.mathematica/OlrpK9ocdRE). Your answer doesn't quite behave the way that I wanted - see the edits. – Simon Mar 04 '11 at 06:08
  • 1
    @Simon, fixed it to actually be an answer :) – Janus Mar 04 '11 at 08:34
1

One problem with Andrew's solution is that it maps the problem back to With, and that does not accept subscripted variables. So the following generates messages.

WithRules[{Subscript[x, 1] -> 2, Subscript[x, 2] -> 3}, 
 Power[Subscript[x, 1], Subscript[x, 2]]]

Given that With performs syntactic replacement on its body, we can set WithRules alternatively as follows:

ClearAll[WithRules]; SetAttributes[WithRules, HoldRest];
WithRules[r : {(_Rule | _RuleDelayed) ..}, body_] := 
 ReleaseHold[Hold[body] /. r]

Then

In[113]:= WithRules[{Subscript[x, 1] -> 2, 
  Subscript[x, 2] -> 3}, Subscript[x, 1]^Subscript[x, 2]]

Out[113]= 8

Edit: Addressing valid concerns raised by Leonid, the following version would be safe:

ClearAll[WithRules3]; SetAttributes[WithRules3, HoldRest];
WithRules3[r : {(_Rule | _RuleDelayed) ..}, body_] := 
 Developer`ReplaceAllUnheld[Unevaluated[body], r]

Then

In[194]:= WithRules3[{Subscript[x, 1] -> 2, Subscript[x, 2] -> 3}, 
 Subscript[x, 1]^Subscript[x, 2]]

Out[194]= 8

In[195]:= WithRules3[{x -> y}, f[y_] :> Function[x, x + y]]

Out[195]= f[y_] :> Function[x, x + y]

Edit 2: Even WithRules3 is not completely equivalent to Andrew's version:

In[206]:= WithRules3[{z -> 2}, f[y_] :> Function[x, x + y + z]]

Out[206]= f[y_] :> Function[x, x + y + z]

In[207]:= WithRules[{z -> 2}, f[y_] :> Function[x, x + y + z]]

Out[207]= f[y$_] :> Function[x$, x$ + y$ + 2]
Sasha
  • 5,935
  • 1
  • 25
  • 33
  • If that is all that is required, cannot one merely do `Unevaluated[Subscript[x, 1]^Subscript[x, 2]] /. {Subscript[x, 1] -> 2, Subscript[x, 2] -> 3}` ? – Mr.Wizard Mar 04 '11 at 13:56
  • @Mr.Wizard Yes, that is an alternative. – Sasha Mar 04 '11 at 14:18
  • 3
    There are subtle differences in semantics of With and rule-based substitutions, that will show up when the code in "body" has nested scoping constructs with local variable names colliding with some of the symbols in rules. Using With will favor inner constructs, using rules will favor outer rules and break the inner scoping constructs bindings. What is advantageous, depends on the code in question, but my feeling is that the "With" way is generally safer. – Leonid Shifrin Mar 04 '11 at 15:50
  • Nice one. Having a version of With that handles non-symbols, such as subscripted variables, is a really handy generalization. – Andrew Moylan Mar 04 '11 at 21:01
  • 1
    @Andrew As I already commented, this is not really a version of `With`, but rather a utility automating external rule application to an unevaluated expression. Try, for example, `WithRules[{x -> y}, f[y_] :> Function[x, x + y]]` with both yours and @Sasha's solutions, to see the difference. In the context of lexical scoping, I consider rule substitution far more dangerous. It is, in particular, responsible for the leaks in the functional abstraction (`Function`) in Mathematica, one of which the above code snippet demonstrates. – Leonid Shifrin Mar 04 '11 at 22:05