26
$\begingroup$

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

$\endgroup$
1
  • $\begingroup$ did you try rule assignments, instead of an assign operator? $\endgroup$
    – Stefan
    Jul 15, 2013 at 14:54

6 Answers 6

20
$\begingroup$

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 *)
$\endgroup$
1
  • $\begingroup$ I'm always confused by the Hold, Evaluate and :> etc in your answer. A neat method anyway, learn a lot from this :) $\endgroup$
    – mmjang
    Jul 16, 2013 at 2:48
9
$\begingroup$

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

$\endgroup$
1
  • 1
    $\begingroup$ This works well, and I'm not the least bit surprised I couldn't come up with the answer on my own. $\endgroup$ Jul 15, 2013 at 16:13
7
$\begingroup$

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}
$\endgroup$
9
  • $\begingroup$ 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. $\endgroup$ Aug 18, 2013 at 13:45
  • $\begingroup$ @bobthechemist It's nice, isn't it? And to think some people believe an acceptable SE question should have only one answer. :^) $\endgroup$
    – Mr.Wizard
    Aug 18, 2013 at 14:05
  • $\begingroup$ @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? $\endgroup$
    – Mr.Wizard
    Aug 21, 2013 at 17:46
  • $\begingroup$ 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. $\endgroup$ Aug 21, 2013 at 19:02
  • $\begingroup$ @bobthechemist Thanks, that's all I could ask. $\endgroup$
    – Mr.Wizard
    Aug 21, 2013 at 19:05
5
$\begingroup$

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.

$\endgroup$
9
  • $\begingroup$ I think the OP is trying to write a "macro" that expand at the first element of With. It's rather hard for me . $\endgroup$
    – mmjang
    Jul 15, 2013 at 15:04
  • 2
    $\begingroup$ @mm.Jang checked it and it works with 9... $\endgroup$
    – Stefan
    Jul 15, 2013 at 15:36
  • 1
    $\begingroup$ @Stefan I don't really know what happened, can't reproduce this anymore. I believe I copied the code in a right way. $\endgroup$
    – mmjang
    Jul 15, 2013 at 15:37
  • 1
    $\begingroup$ @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. $\endgroup$
    – Stefan
    Jul 15, 2013 at 15:53
  • 1
    $\begingroup$ Or, perhaps a bit simpler, With[{list = Thread[params -> vals]},(*fancy operation*){a, b, c, d} /. list] $\endgroup$
    – user1066
    Jul 15, 2013 at 23:33
2
$\begingroup$

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}

$\endgroup$
0
$\begingroup$

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];
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.