Solution using Block
The easiest option is probably to use Block
, and wrap your result in Hold
or HoldForm
. So,
a = 1; b = 2; c = 3;
result =
Block[{a, b, c},
Hold[Evaluate[Expand[(a + b + c)^3]]]
]
(*
Hold[a^3+3 a^2 b+3 a b^2+b^3+3 a^2 c+6 a b c+3 b^2 c+3 a c^2+3 b c^2+c^3]
*)
The values will be automatically substituted once you call ReleaseHold
:
ReleaseHold[result]
(* 216 *)
Use metaprogramming to automate things
Here I will show how you can automate this step with some meta-programming. The following function returns all symbols used in building a given expression, and belonging to some pre-defined set of contexts (which I default to {"Global`"}
:
ClearAll[getHeldSymbols];
SetAttributes[getHeldSymbols, HoldFirst];
getHeldSymbols[expr_, contexts_: {"Global`"}] :=
Cases[
Unevaluated[expr],
s_Symbol /; MemberQ[contexts, Context[s]] :> HoldComplete[s],
{0, Infinity},
Heads -> True
]
For example:
getHeldSymbols[Expand[(a + b + c)^3]]
(* {HoldComplete[a], HoldComplete[b], HoldComplete[c]} *)
Now, the following code defines a dynamic environment where the values of defined symbols participating in building your expression have been "frozen":
ClearAll[evalWithFrozenSyms]
SetAttributes[evalWithFrozenSyms, HoldAll];
evalWithFrozenSyms[expr_, unfrozen : {___Symbol} : {},
wrapper_: Hold, getsymbolsF_: getHeldSymbols
] :=
With[{frozen =
Thread[
Complement[
getsymbolsF[expr],
Thread[HoldComplete[unfrozen]]
],
HoldComplete
]
},
If[frozen === {}, expr,
frozen /. HoldComplete[{syms___}] :>
Block[{syms}, wrapper[##] &@expr]
]
]
Now we can use it:
evalWithFrozenSyms[Expand[(a+b+c)^3]]
(* Hold[a^3+3 a^2 b+3 a b^2+b^3+3 a^2 c+6 a b c+3 b^2 c+3 a c^2+3 b c^2+c^3] *)
if you want to release come symbols, list them in the second argument of evalWithFrozenSyms
:
evalWithFrozenSyms[Expand[(a+b+c)^3],{a,c}]
(* Hold[64+48 b+12 b^2+b^3] *)
Finally, per additional request of the OP - here is what you can do if you want to evaluate those symbols which may expand into other symbols, but not the "final" ones. We will have to use some heavy artillery now. Load the code from this answer on dependency graphs, and then here is another function:
ClearAll[symbolHasValue];
symbolHasValue[held : HoldComplete[s_]] := depends[s] =!= {held}
and finally:
ClearAll[evalWithSemiFrozenSyms]
SetAttributes[evalWithSemiFrozenSyms, HoldAll];
evalWithSemiFrozenSyms[expr_, wrapper_: Hold] :=
Thread[Select[depends[expr], symbolHasValue], HoldComplete] /.
HoldComplete[{syms___Symbol}] :>
evalWithFrozenSyms[expr, {syms}, wrapper, depends];
Here is an example of use:
ClearAll[a, b, c, d, e, f];
a = b + c; d = e + f;
b = 1; c = 2; e = 3; f = 4;
Now, you can see that symbols a
and d
can be expanded, while symbols b,c,e,f
should be kept frozen. Here is an example then:
evalWithSemiFrozenSyms[Expand[(a+d)^2]]
(* Hold[b^2 + 2 b c + c^2+ 2 b e +2 c e + e^2 + 2 b f + 2 c f + 2 e f + f^2] *)