How to set Block local variables by code?

9

5

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?

Murta

Posted 2013-02-18T02:00:12.057

Reputation: 23 859

The third argument of ToExpression is your saviour – Rojo – 2013-02-18T02:14:25.423

But the idea is to get rid of ToExpression. :) – Murta – 2013-02-18T02:17:13.340

And what's the practical objective of that idea? – Rojo – 2013-02-18T02:19:00.137

Idea to get rid of ToExpression? A simple variation like myBlock2[r={1,2,3};r[[b]]] do not work with this solution, but is ok with the first one. – Murta – 2013-02-18T02:23:07.697

I might understand you want to get rid of the clumsiness of writing code in strings, but you need ToExpression at least for the variables, the moment you decide to store your variables as strings. – Rojo – 2013-02-18T02:25:04.953

Why not Evaluate@Symbol["x"]? I just don't know how to insert it on Block as Block[{Evaluate@Symbol["x"]=1},args] – Murta – 2013-02-18T02:27:15.903

1If you evaluate Symbol["x"] and x has a value, it will evaluate to the value and not x – Rojo – 2013-02-18T02:27:39.257

let us continue this discussion in chat

– Murta – 2013-02-18T02:28:06.777

Answers

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

Posted 2013-02-18T02:00:12.057

Reputation: 259 163

Nicer than what I drafted in chat, go get the 50k – Rojo – 2013-02-18T02:44:53.783

@Mr.Wizard You and yours Mathemagics!.. Now I need to study it. Tks! +1 – Murta – 2013-02-18T02:45:50.993

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

Posted 2013-02-18T02:00:12.057

Reputation: 40 993

Why not Range@Length@varList? tks +1 – Murta – 2013-02-18T03:09:30.180

Sure, I didn't know if that was the general intention. Editing – Rojo – 2013-02-18T03:10:48.677

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

Posted 2013-02-18T02:00:12.057

Reputation: 14 423

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 – 2013-02-19T08:39:30.370

@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 – 2013-02-20T08:11:24.360

0

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

Posted 2013-02-18T02:00:12.057

Reputation: 10 549