10

I need to create a user-defined Block function where the Block variables' values are defined by code. For example, imagine I have:

SetAttributes[myBlock1,HoldAll]
myBlock1[args_]:=Block[{a=1,b=2,c=3,d=4},args]
myBlock1[{a,b,c,d}]

{1, 2, 3, 4}

Now, what I need is something like:

SetAttributes[myBlock2,HoldAll]
varList={"a","b","c","d"};

myBlock2[args_]:=Module[{varArgs},
    varArgs=MapIndexed[ToString@Row[{#1,"=",#2[[1]]}]&,varList];
    ToExpression@ToString@Row[{"Block[",varArgs,",",ToString@args,"]"}]
]

myBlock2[{a,b,c,d}]

{1, 2, 3, 4}

The above function works, but it's very clumsy (using ToExpression) and error-susceptible. I tried something like:

SetAttributes[myBlock3,HoldAll]
varList={"a","b","c","d"};

myBlock3[args_]:=ReleaseHold[Hold@Block[varDef,args]/.varDef:>MapIndexed[(Evaluate@Symbol[#1]=#2[[1]])&,varList]]
myBlock3[{a,b,c,d}]

But without success in the "variables injection." One important point is that a, b, c and d should not escape from the block scope. How can I do that?

Reb.Cabin
  • 8,661
  • 1
  • 34
  • 62
Murta
  • 26,275
  • 6
  • 76
  • 166

4 Answers4

11

The two standard methods are SlotSequence, and the "injector pattern."
Related question on StackOverflow: How to Block Symbols without evaluating them?

SlotSequence

ClearAll[myBlock]

SetAttributes[myBlock, HoldAll]

varList = {"a", "b", "c", "d"};

myBlock[args_] :=
 Function[Null, Block[{##}, args], HoldAll] @@
  (MapIndexed[Set, Join @@ MakeExpression@varList] /. {x_} :> x)

myBlock[{a, b, c, d}]
{1, 2, 3, 4}

Injector pattern

ClearAll[myBlock]

SetAttributes[myBlock, HoldAll]

varList = {"a", "b", "c", "d"};

myBlock[args_] :=
 (MapIndexed[Set, Join @@ MakeExpression@varList] /. {x_} :> x) /.
   _[sets__] :> Block[{sets}, args]

myBlock[{a, b, c, d}]
{1, 2, 3, 4}
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
7

Just an alternative, the abuse of Hold pattern

varList = {"a", "b", "c", "d"};

    SetAttributes[myBlock, HoldFirst];

myBlock[args_]:=
   Hold[Block][
      MakeExpression@varList~Hold[Set]~Range@Length@varList // Thread, 
      Hold[args]] // ReleaseHold
Rojo
  • 42,601
  • 7
  • 96
  • 188
7

I'd like to add that I personally prefer to deal with lists of symbols rather than lists of strings that are implied to convert into symbols later in functions. That way I get errors from incorrectly formated strings early rather then getting them buried deeply in an application when something runs Symbol[string] or worse ToExpression[string] and expects a single symbol. Naturally you can't just have a list of symbols, since they might evaluate, but you can just pretend HoldComplete is List for a moment, and in fact most build-in functions (Map, Sort... the list goes on), don't actually require you to pass things with the List head. Anyway to cut a long rant short, here's a function to convert a list of strings to a HoldComplete filled with symbols:

stringsToSymbols[strings_] := 
 With[{ res =       
       strings/.a_String:>ToExpression[a,InputForm,Hold]//HoldComplete@@#/.Hold[a_]:>a& },
 res/;MatchQ[res,HoldComplete[___Symbol]]]

stringsToSymbols::wrdf="Input strings did not convert nicely to symbol list `1` ";
stringlistToSymHold[s_]:=Message[stringsToSymbols::wrdf,s]

So when needing something like your block function I'll just assume the format:

varList=HoldComplete[a,b,c,d]

Now the above works nicely with the methods presented by Mr. Wizard and Rojo, but I'll show another one just to add it, which is one I've used occasionally. What I do is build the expression-structure using HoldComplete and Lists instead of for instance With Block and similar. I then substitute out all the desired heads at once at the end:

 myBlock[args_] := 
   ReplacePart[
   List[MapIndexed[Set, varList] /. {x_} :> x, 
      args], {{0} -> Block, {1, 0} -> List}]

Here I'd note that I would typically take varList as an input adding varList : HoldComplete[__Symbol] to the function definition, just to avoid reliance on globals.

jVincent
  • 14,766
  • 1
  • 42
  • 74
  • Hi @jVincent! The HoldComplete is a nice tip. About convert text into symbols, I don't like it too, but the variables names came from the names of a SQL query columns, and they are text that I needed to convert into local variables. I have no other option. Tks for your answer! – Murta Feb 19 '13 at 08:39
  • @Murta That is why I showed a function to convert from strings to a held format. The point is that sometimes you need to have strings converted into symbols, but when you do, it's better to do so once early, rather than just assume everything will always work out nicely and do the conversion in the middle of other functions. – jVincent Feb 20 '13 at 08:11
1

Here's another possibility worth mentioning that I copy from Daniel Huber, found here
http://forums.wolfram.com/mathgroup/archive/2010/Nov/msg00217.html.

It lacks the conversion from string to symbol, but it's already in other answers.

SetAttributes[CreateBlock, HoldAll];
CreateBlock[lvals_, rvals_, expr_] :=
Module[{v, myBlock, mySet, vals},
    v = Thread[mySet[lvals, rvals]];
    SetAttributes[myBlock, HoldAll];
    myBlock[vals, expr] /. vals -> v /. mySet -> Set /. myBlock -> Block
];
faysou
  • 10,999
  • 3
  • 50
  • 125