26

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).

bobthechemist
  • 19,693
  • 4
  • 52
  • 138

6 Answers6

20

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 *)
Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • I'm always confused by the Hold, Evaluate and :> etc in your answer. A neat method anyway, learn a lot from this :) – mmjang Jul 16 '13 at 02:48
9

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).

Michael E2
  • 235,386
  • 17
  • 334
  • 747
7

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}

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • 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
  • @bobthechemist Say, did you ever get back to play around with this? I still think I have the best code on the page. :^) – Mr.Wizard Jul 25 '14 at 11:09
  • Sadly no; too many projects going on and I haven't gotten back to the project that required this functionality, so at the moment I can't fairly evaluate how your solution compares to the accepted one for my application. – bobthechemist Jul 25 '14 at 14:14
  • @bobthechemist No problem, but you should expect that I'll rib you about it at least once a year. :o) – Mr.Wizard Jul 25 '14 at 14:19
  • Squeaky wheel gets the rep. (or something like that) – bobthechemist Jul 25 '14 at 15:32
5

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.

Stefan
  • 5,347
  • 25
  • 32
2

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}

bobknight
  • 2,037
  • 1
  • 13
  • 15
0

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];
Sektor
  • 3,320
  • 7
  • 27
  • 36