I thought this Q&A might be closed as too broad or needing input from WRI. But it's still open with no close votes. I suppose I can explain two things, which might provide the understanding why Table
and Plot
are different: (1) The purpose of HoldAll
(in these functions) and (2) why what Plot
does isn't needed in Table
(in almost all cases).
The purpose of HoldAll
The purpose of HoldAll
in Table[]
and Plot[]
is primarily to keep the symbolic expressions passed as arguments in their original forms, so that numerical definitions for the symbols in the expressions are not substituted.
For instance, without HoldAll
:
foo[x^2, {x, 0, 2}]
(* foo[x^2, {x, 0, 2}] <-- good syntax for Plot/Table *)
(* BUT: *)
x = 3.4;
foo[x^2, {x, 0, 2}]
(* foo[11.56, {3.4, 0, 2}] <-- bad syntax for Plot/Table *)
In the case where foo
is Plot
or Table
, the user does not usually want to worry about whether the variable they wish to use has been defined during their current session. While the user could be required to clear the value of x
before calling Table[]
or Plot[]
, this would be inconvenient. (Using Block[{x}, Plot[...]]
instead of Clear[x]
is still quite inconvenient.) So Table[]
and Plot[]
have been programmed to do this automatically for the user without destroying any value(s) that might be assigned to x
.
Why Plot[]
differs from Table[]
The answer centers on when to evaluate the first argument, the function(s) to be plotted or "tabled." Many things can be handled by all the functions in Mathematica that control evaluation: Evaluate
, Unevaluated
. HoldAll
, HoldAllComplete
, etc.
In the case of Table[]
, there is no common use case in which the first argument of Table[]
needs to be evaluated before Table[]
is normally ready to evaluate it. The most common case would probably :) be the use of random functions like RandomReal[]
, which is discussed in the docs (e.g Table[RandomReal[], {5}]
gives five different numbers while Table[Evaluate@RandomReal[], {5}]
gives the same number five times.
Plot[]
has evolved to handle various use-cases commonly encountered by users. It's hard to remember all the changes over the years. Now, Plot[]
needs to evaluate the first argument both symbolically and numerically. It analyzes the discontinuities of the function(s) before trying to numerically plot them. It seems to distinguish a vector-valued function from a list of functions and handle these differently. Perhaps to do this, it numerically evaluates the functions before starting to plot them. It also seems to check both the unevaluated argument and the evaluated argument to distinguish these cases. The details are hidden internally, and I cannot be precise about the decision algorithm.
These considerations point to three distinct times at which the funcs
argument of Plot[funcs, {x, 0, 1}]
might be evaluated:
- Before the call to
Plot
, e.g. Plot[Evaluate@funcs, {x, 0, 1}]
, which substitutes the value of x
if any (usually a disaster): both the argument and the symbolic analysis is made on the value of funcs
with x
not blocked.
- After the call to
Plot
but before symbolic analysis begins, e.g. Plot[funcs, {x, 0, 1}, Evaluated -> True]
: both the argument and the symbolic analysis is made on the value of funcs
but with the value of x
blocked.
- After the call and symbolic analysis, that is, what the normal call
Plot[funcs, {x, 0, 1}]
does: the argument is literally funcs
and the symbolic analysis depends both on literal form of funcs
and the value of funcs
(with the value of x
blocked).
Examples:
By "colored", I mean that Plot
styles independent functions with different colors. By "uniform," I mean each curve has the same color. If you have an old version of Mathematica, you may find that some plots that are colored in V13 are uniform in your version. (I cannot detail when the version changes occurred.)
Plot[{Sin[x], Cos[x]}, {x, 0, 10}] (* colored *)
funcs = {Sin[x], Cos[x]}; (* list of scalar function *)
Plot[funcs, {x, 0, 10}] (* colored *)
funcs2[] := {Sin[x], Cos[x]}; (* list of scalar function *)
Plot[funcs2[], {x, 0, 10}] (* colored *)
funcs3[x_] := {Sin[x], Cos[x]}; (* vector-valued function *)
Plot[funcs3[x], {x, 0, 10}] (* uniform *)
Plot[funcs3[x], {x, 0, 10}, Evaluated -> True] (* colored *)
funcs4[x_?NumericQ] := {Sin[x], Cos[x]}; (* vector-valued function *)
Plot[funcs4[x], {x, 0, 10}, Evaluated -> True] (* uniform *)
(* colored pairs of uniformly colored vector components: *)
Plot[{funcs4[x], -funcs4[x]}, {x, 0, 10}, Evaluated -> True]
The last example might need further explanation. Because of ?NumericQ
, funcs4[x]
evaluates to funcs4[x]
if x
is not numeric. It numerically evaluates to a list of two real numbers. So it is treated as a vector-valued function. The plot argument, therefore, is a list of vector-valued functions. The first function gets one color, the second gets another color. The components of each function get the same color. This is undocumented, AFAIK, and these are my inferences from observed behavior.
As for the others, apparently: funcs
and funcs2[]
are examined and seen to be lists of functions. It is assumed these are lists of independent functions. In (surprising) contrast to funcs2[]
, the argument x
in funcs3[x]
changes the analysis to decide it's a vector-valued function: the two components are colored the same because they come from the same function. If evaluated before symbolic analysis (with Evaluate
), the form Plot
sees is {Cos[x], Sin[x]}
, which looks the same as the first example and is treated the same.
HoldAll
prevents evaluation of arguments at the time of the function call. What happens after that point is up to the function that is called. It can evaluate its arguments if it wants to.Plot[]
checks some of its arguments in particular ways, which have evolved over time. So do some of the other numerical solvers. The details are complicated and probably not fully known to non-developers. $\endgroup$GeneralUtilities
PrintDefinitions@Plot` there is optionEvaluated -> Automatic
. Instead of this Mr.Wizard has recommendedEvaluated -> True
. $\endgroup$