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