8

How can I create a function which works like Block but takes a list of rules as its first argument, rather than a list of assignments?

The function needs to be able to work with rules supplied either as an explicit list or via a symbol, and it should work when some of the symbols to be blocked have global values.

Test code:

a = 100;
code := a + b;
rules := {a -> 1, b -> 2};

(* using an explicit list of rules *)
ruleBlock[{a -> 1, b -> 2}, code]
(* 3 *)

(* using a symbol *)
ruleBlock[rules, code]
(* 3 *)

(* global values are unaffected *)
{a, b}
(* {100, b} *)

I have a working solution (below) but I have had to write separate functions for List and Symbol patterns, and I find the code a bit cumbersome. I would like to know if there is a simpler, cleaner way to write ruleBlock.

SetAttributes[ruleBlock, HoldAll]

ruleBlock[rules_List, code_] := 
 Replace[Apply[Set, Hold[rules], {2}], Hold[x_] :> Block[x, code]]

ruleBlock[rules_Symbol, code_] := 
 Replace[Apply[Set, OwnValues[rules] /. HoldPattern[{_ :> r_}] :> Hold[r], {2}], 
  Hold[x_] :> Block[x, code]]
Simon Woods
  • 84,945
  • 8
  • 175
  • 324
  • You've got a bit of a problem in the case rules assigned to a symbol if the LHS of some of those rules already has a global value, as they will evaluate. How do you want to handle that? – Mr.Wizard Sep 05 '13 at 13:07
  • Oh, I see you're digging out OwnValues. I forget who I'm talking to as most people wouldn't think to use := to hold a list of values like this, but I would. :-) – Mr.Wizard Sep 05 '13 at 13:10
  • 3
    @Mr.Wizard, the rule list might also be defined with = before the global values are assigned. Ideally there wouldn't be any global values for the LHS of the rules, but I am working with a large body of code written by an incompetent fool (i.e. myself, several years ago...) – Simon Woods Sep 05 '13 at 13:24
  • You made my profile quotes. :-) – Mr.Wizard Sep 05 '13 at 13:26

1 Answers1

6

The only alternative that comes to mind is to use my step function to extract a definition rather than digging around in *Values lists; this has the advantage of being more robust as it will work with other kinds of definitions as well.

SetAttributes[ruleBlock, HoldAll]

ruleBlock[L_List, body_] := Block @@ Join[Apply[Set, Hold[L], {2}], Hold[body]]

ruleBlock[obj_, body_] := step[obj] /. _[x_] :> ruleBlock[x, body]

An extension you may wish to consider is handling for the undocumented := syntax of Block which holds the RHS of assignments. This adds considerable code length but also flexibility. As above but with:

ruleBlock[L_List, body_] := Block @@ Join[
    Replace[Hold[L], {(a_ -> b_) :> (a = b), (a_ :> b_) :> (a := b)}, {2}],
    Hold[body]
  ]
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • This is great, thanks. Much more readable than mine, and as you say, more robust. I love the Block @@ Join[Hold[x], Hold[y]] construction - I always forget that Join works with any head. – Simon Woods Sep 05 '13 at 14:24
  • @Simon You're welcome. I wasn't sure if it would be useful to you. I think I finally figured out what I was trying to do re: :=. One remaining issue: ruleBlock["null", code] returns "null" because "null" is inert. If you want the original expression returned you'll have to add a Condition. – Mr.Wizard Sep 05 '13 at 14:37
  • Oh it's definitely useful. Another nice feature, I've just realised, is that if I have a chain of definitions like r1:={a->1}; r2:=r1; r3:=r2 I can use r3 and your code will recursively call ruleBlock until the symbol resolves to a list. – Simon Woods Sep 05 '13 at 15:39
  • @Simon Yes, I should have explicitly stated that. I used it for the that reason in (19900). – Mr.Wizard Sep 05 '13 at 16:02