2
$\begingroup$

I want to define a function for doing multiple definitions, where I apply the same function f to all values, and definitions are done not at the same time but after each other.

A simple example: the function I want is multiDef, where

multiDef[{{a, 1}, {b, 2}, {c, a}}, f]

should be identical to the sequence of assignments

a = f[1];
b = f[2];
c = f[a];

A simple attempt, with slightly different syntax, is

multiDef[list1_List, list2_List, fct_] /; Length[list1] == Length[list2] := 
  list1 = fct /@ list2

Aside from the different syntax the problem here is that it will not set c to f[f[1]], but to f[a] (or to f[5] if we had a = 5 before running this).

$\endgroup$

4 Answers 4

3
$\begingroup$

The first proposed syntax of multiDef can be implemented like this:

SetAttributes[multiDef, HoldAll]
multiDef[{b:{_Symbol, _}...}, f_] :=
  (CompoundExpression @@ Replace[Hold[b], {s_, v_} :> (s = f@v), {1}];)

The key is make sure that the symbols in the argument list are not evaluated until after the appropriate Set expressions have been assembled. This is necessary to prevent the symbols from being replaced by any existing values that they may already have.

The HoldAll attribute prevents such unwelcome evaluation of the argument list. Inside the function, we temporarily wrap the symbol/value pairs in Hold to prevent further evaluation of the pairs as we operate upon them. Replace is used to transform each pair into an assignment. Finally, we convert the Hold expression into a CompoundExpression, adding an extra semicolon after the last assignment.

Here is multiDef in action:

multiDef[{{a, 1}, {b, 2}, {c, 3 + 4}}, f]

{a, b, c}
(* {f[1], f[2], f[7]} *)

Note that the definition ensures that the supplied variable values are not evaluated prior to passing them to the supplied function -- in case that function wants to hold them itself:

multiDef[{{a, 1}, {b, 2}, {c, 3 + 4}}, Hold]

{a, b, c}
(* {Hold[1], Hold[2], Hold[3 + 4]} *)

We can use TracePrint to peek under the covers and see the result of the Replace transformation:

TracePrint[multiDef[{{a, 1}, {b, 2}, {c, 3 + 4}}, f], _CompoundExpression]
(*
   CompoundExpression @@ Replace[Hold[{a,1},{b,2},{c,3+4}],{s$_,v$_}:>(s$=f[v$]),{1}];
   a = f[1]; b = f[2]; c = f[3+4]
*)

When defining macros like multiDef, it is a good idea to signal an error if the expression does not conform to the expected syntax:

m:multiDef[___] := (Message[multiDef::malformed, HoldForm[m]]; Abort[])
multiDef::malformed = "Malformed arguments in ``";

This extra definition will catch simple syntax errors as they happen rather than blindly carrying on with the evaluation:

multiDef[{a, b, c}, f]
(*
   multiDef::malformed: Malformed arguments in multiDef[{a,b,c},f]
   $Aborted
*)
$\endgroup$
1
  • $\begingroup$ Thanks, this is what I was looking for. The compound expression guarantees evaluation in the order of input. $\endgroup$
    – Jansen
    Jun 21, 2014 at 8:47
3
$\begingroup$

If a, b and c do not already have values, you can simply use

In[1]:= (#1 = f[#2]) & @@@ {{a, 1}, {b, 2}, {c, a}}
Out[1]= {f[1], f[2], f[f[1]]}

In[2]:= a
Out[2]= f[1]

In[3]:= b
Out[3]= f[2]

In[4]:= c
Out[4]= f[f[1]]

If they do have values then I do not recommend storing the input in a simple list such as {{a,1}, ...}. It is necessary to Hold the result in some way, e.g. Hold[{a,1}, ...]. Then you can use the same solution from above and use ReleaseHoldat the end.

$\endgroup$
2
$\begingroup$

You can also use this construct if your left- and right-hand sides are in different lists:

MapThread[Set, {{a, b, c}, f /@ {1, 2, a}}]
{a, b, c}

{f[1], f[2], f[f[1]]}

@Szabolcs's remarks are equally applicable here.

$\endgroup$
0
$\begingroup$

Try this also:

m = {{a, 1}, {b, 2}, {c, a}};
(Evaluate@m[[;; , 1]]) = f /@ m[[;; , 2]]

(*{f[1], f[2], f[f[1]]}*)

One thing I want to add, when you run this, a,b, and c will not be empty symbols. So I would suggest you add clear command if you want to always run this.

Clear[a, b, c]
$\endgroup$

Your Answer

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

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