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.

I would like to be able to define two arrays, one containing symbol names and one containing the values of those symbols, for use in constructs such as With. For example:

params = {a,b,c,d};
vals = {1,2,3,4};
With[{a=1,b=2,c=3,d=4},Print["Do something fancy"]]

I would like to construct the local variable assignment array {a=1, b=2, ....} automatically. The wishful thinking solution of With[params = values, ...] doesn't work and I've tried a random selection of Mapping, Threading and Holding, all to no avail. It looks like a similar question has been answered before however the function proposed by Leonid doesn't work in the With block. I suspect my issue focuses on an attempt to manipulate Symbols in a non-trivial manner (and by non-trivial, I simply mean anything beyond a = 1).

share|improve this question
    
did you try rule assignments, instead of an assign operator? –  Stefan Jul 15 '13 at 14:54

6 Answers 6

up vote 18 down vote accepted

Here is my version using injector pattern:

ClearAll[myWith];
SetAttributes[myWith,HoldAll];
myWith[pars_=vals_,body_]:=
    Apply[Set,Hold[Evaluate[Transpose[{pars,vals}]]],{2}]/. 
        Hold[vars_]:>With[vars,body]

This code assumes that pars evaluate to a list of symbols. For example,

myWith[params=vals,a+b+c+d]

(* 10 *)
share|improve this answer
    
I'm always confused by the Hold, Evaluate and :> etc in your answer. A neat method anyway, learn a lot from this :) –  mm.Jang Jul 16 '13 at 2:48

Borrowing from Mr. Wizard's answer to a related question, I came up with the following.

ClearAll[myWith];

SetAttributes[myWith, HoldAll];
myWith[vars_ = init_, expr_] := 
 Function[Null, With[{##}, expr], HoldAll] @@
  (Thread[Hold[Set][Hold[vars], Hold[init]] /. {x__} :> x, Hold] /. Hold[Set] -> Set)

Examples

Clear[a, b, c, d];
a = 10;
myWith[{a, b, c, d} = {1, 3, 5, 7}, Range[4].{a, b, c, d}]
(* 50 *)

myWith[{a, b, c, d} = {10, 3, 5, 7}, Range[4].{a, b, c, d}]
(* 59 *)

It works with only one assignment (Set) in the first argument, as in the example in OP. One might wish to extend it to arbitrarily many (left as an exercise for the reader).

share|improve this answer
    
This works well, and I'm not the least bit surprised I couldn't come up with the answer on my own. –  bobthechemist Jul 15 '13 at 16:13

If we say that = is the same as a ->, we can do the following:

With[{list={#1->#2}&@@@Thread[{params,vals}]//Flatten},
    (* fancy operation*)
    {a,b,c,d}/.list
]

==> {1, 2, 3, 4}

Maybe a bit cumbersome, but it does the job. (Please have a look down to @TomD's comment which makes this specific approach even much cleaner and simpler)

You could do this as well:

With[{tmp$=Quiet@Set[#1,#2]&@@@Thread[{params,vals}]},
    (* fancy operation *)
    Range[4] . {a,b,c,d}
]

==> 30

Since we're using With we've to assign to something. That's why there is tmp$. We don't need to use replacement with this anymore.

This seems a little hacky and Mathematica complains (sometimes) about assigning to raw objects etc. That's why there is a Quiet...

With this approach you can go up with arbitrary arguments. Like so:

params = ToExpression@CharacterRange["a", "z"];
vals = Range[26];

With[{tmp$ = Quiet@Set[#1, #2] & @@@ Thread[params -> vals]},
    #*2 & /@ ToExpression@CharacterRange["a", "z"]] // Total

==> 702

Having showed that, I don't think that there isn't any need for a macro or overloading of standard built-in functionality.

Hope that helps.

share|improve this answer
    
I think the OP is trying to write a "macro" that expand at the first element of With. It's rather hard for me . –  mm.Jang Jul 15 '13 at 15:04
2  
@mm.Jang checked it and it works with 9... –  Stefan Jul 15 '13 at 15:36
1  
@Stefan I don't really know what happened, can't reproduce this anymore. I believe I copied the code in a right way. –  mm.Jang Jul 15 '13 at 15:37
1  
@mm.Jang i know exactly what you mean...fiddling around with replacements it is sometimes best to start with a fresh kernel...or do the replacements inside a With. –  Stefan Jul 15 '13 at 15:53
1  
Or, perhaps a bit simpler, With[{list = Thread[params -> vals]},(*fancy operation*){a, b, c, d} /. list] –  TomD Jul 15 '13 at 23:33

As has been stated in other answers the two basic methods of insertion are described in:
How to set Block local variables by code?

I'll pick the "injector pattern" as did Leonid, but with apologies to him that code seems much too complicated for such a simple operation. I would use this:

SetAttributes[listWith, HoldAll];

listWith[(set : Set | SetDelayed)[L_, R_], body_] :=
  Inner[set, L, R, Hold] /. _[x__] :> With[{x}, body]

Additionally this code has been designed to support the (undocumented) SetDelayed form of With and works with held Symbols and values.


Edit:
Sadly not as clean but more robust, handling e.g. held variables and non-held values or vice versa:

SetAttributes[listWith, HoldAll];

listWith[(set : Set | SetDelayed)[L_, R_], body_] :=
  set @@@ Thread[Hold @@@ {L, R}, Hold] /. _[x__] :> With[{x}, body]

Basic example:

params = {a, b, c, d};
vals   = {1, 2, 3, 4};

listWith[params = vals, a + b + c + d]

params
10

{a, b, c, d}

Advanced example:

{a, b, c, d} = {0, 0, 0, 0};                       (* other existing assignments *)
params = Hold[a, b, c, d];                         (* held Symbols *)
vals   = Hold[2 + 2, 8/4, Sin[Pi], 1/0];           (* held values *)

listWith[params := vals, HoldForm[a + b + c + d]]  (* note := *)

{a, b, c, d}
(2+2) + 8/4 + Sin[π] + 1/0

{0, 0, 0, 0}
share|improve this answer
    
Definitely one of the things I like most about M.SE is the wealth of knowledge users are willing to contribute; having a software package that can do so many things in so many ways helps as well. –  bobthechemist Aug 18 '13 at 13:45
    
@bobthechemist It's nice, isn't it? And to think some people believe an acceptable SE question should have only one answer. :^) –  Mr.Wizard Aug 18 '13 at 14:05
    
@bobthechemist I was hoping that you might Accept this answer. The presently selected answer fails if any of the symbols you use (a,b,c,d) have global values for example. This also allow you to conveniently use the := method in With. Are these not valuable to you? –  Mr.Wizard Aug 21 '13 at 17:46
    
I will take this under consideration. This project took a back burner role now that school is starting so I haven't had a chance to play around with your answer. –  bobthechemist Aug 21 '13 at 19:02
    
@bobthechemist Thanks, that's all I could ask. –  Mr.Wizard Aug 21 '13 at 19:05

If I correctly interpreted the question, you want to define a list of variables and related values externally to a With construct, and then inside the With you want to automatically assign those values to variables, but without to influence the values of such variables outside the With itself. In other words, exactly what With does, but using parallel assignments in automatic. Because parallel assignment is not allowed inside scoping constructs like With (and Module for instance) the effort is to find a way to overcome such limit. Some solutions offered in previous answers are not fully working, despite the apparent correct behavior. See for instance the following

Clear[a,b,c,d];

params = {a, b, c, d};
vals = {1, 2, 3, 4};
With[{list = {#1 -> #2} & @@@ Thread[{params, vals}] // 
    Flatten},(*fancy operation*){a, b, c, d} /. list]

{1, 2, 3, 4}

it seems it works, but looking at other operations

params = {a, b, c, d};
vals = {1, 2, 3, 4};
With[{list = {#1 -> #2} & @@@ Thread[{params, vals}] // Flatten},
 (*fancy operation*)
 e = a + b + c + d;
 Print[{a, b, c, d} /. list];
 Print[e]]

{1,2,3,4}

a+b+c+d

event the other solution seems to have a side effect that at the end vanishes the use of With.

Clear[a,b,c,d];

params = ToExpression@CharacterRange["a", "z"];
vals = Range[26];
With[{tmp$ = Quiet@Set[#1, #2] & @@@ Thread[params -> vals]}, #*2 & /@ 
   ToExpression@CharacterRange["a", "z"]] // Total

702

the result is correct but the variables inside params are now defined outside the With statement, and this makes unuseless the With itself

params

{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}

vals

{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}

so starting from the solution proposed by Michael E2, then I found an easier solution.

Clear[a,b,c,d];

myWith[vars_List, values_List, expr_] := Module[vars, MapThread[Set, {vars, values}]; expr]

myWith[{a, b, c, d}, {1, 3, 5, 7}, Range[4].{a, b, c, d}]
(* 50 *)
myWith[{a, b, c, d}, {10, 3, 5, 7}, Range[4].{a, b, c, d}]
(* 59 *)

{a, b, c, d}

{a, b, c, d}

myWith[ToExpression@CharacterRange["a", "z"], 
  Range[26], #*2 & /@ ToExpression@CharacterRange["a", "z"]] // Total

702

and still the variables are not defined outside the With

ToExpression@CharacterRange["a", "z"]

{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z}

share|improve this answer

The solution:

myWith[vars_ = init_, expr_] := 
              Function[Null, With[{##}, expr], HoldAll]@@(Thread[Hold[Set][Hold[vars],
              Hold[init]]/.{x__} :>x, Hold] /. Hold[Set] -> Set)

seems to have problems with

myWith[a = {1, 2}, a - b]

It is only when the variables are a list that the {x___}->x is appropriate.

Perhaps it is more transparent to use a Replace rather than Function as per below:

ClearAll[myWith];
SetAttributes[myWith, HoldAll];
myWith[{vars__} = {inits__}, expr_] := 
     Replace[(Thread[Hold[Set][Hold[vars], Hold[inits]], Hold] /. 
     Hold[Set] -> Set),
     Hold[assignments___] :> With[{assignments}, expr]];
myWith[var_ = init_, expr_] := With @@ Hold[{var = init}, expr];
share|improve this answer

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.