In general, it is good practice to include i
among the local variables. Table
does not localize its variable (or, as some say, it only localizes the value but not the variable).
It is relatively safe to leave i
unlocalized when variables only have numeric values, like in fun
. But the same is not true when variables can have symbolic values, e.g.:
fun2[x_] := Table[x + i, {i, 3}]
At first it seems to work:
ClearAll[a, b, i]
fun2[a]
(* {1 + a, 2 + a, 3 + a} *)
fun2[b]
(* {1 + b, 2 + b, 3 + b} *)
... but unexpected results are obtained if the caller just happens to use the same symbol as the iteration variable:
fun2[i]
(* {2, 4, 6} *)
As observed in the question, the reason for this behaviour is that i
is not being localized by Table
. Table
notionally uses Block
to temporarily clear the value of i
in the outer scope, assign it successively to each iteration value, and then finally restore the original outer value.
Sum
can produce similar surprises:
sum[x_] := Sum[x + i, {i, 3}]
sum /@ {a, b, i}
(* {6 + 3 a, 6 + 3 b, 12} *)
As can Do
. Such lack of localization can indeed cause unexpected variable value changes ("flickering") in the context of Dynamic
, Monitor
, etc.
If we localize i
, then we get no surprises:
fun3[x_] := Module[{i}, Table[x + i, {i, 3}]]
fun3[a]
(* {1+a, 2+a, 3+a} *)
fun3[b]
(* {1+b, 2+b, 3+b} *)
fun3[i]
(* {1+i, 2+i, 3+i} *)
sum3[x_] := Module[{i}, Sum[x + i, {i, 3}]]
sum3 /@ {a, b, i}
(* {6 + 3 a, 6 + 3 b, 6 + 3 i} *)
Table
and Sum
are still re-using i
from the outer scope, but now the outer scope is the one introduced by Module
. i
in the global scope is now left untouched and we see unsurprising results.
In simple examples like these, it is usually easy to see by inspection whether the lack of localization by Table
will be problematic. But in more complex functions, that analysis can be more difficult. Therefore, I would suggest that it is good defensive programming practice to routinely localize iteration variables. Especially when writing library code.
For a detailed analysis of the subtle differences between Module
and Block
(and With
), see What are the use cases for different scoping constructs?.
Why do iterators work like this?
The scoping behaviour that we see here is a common idiom across all Mathematica functions that use iterators (e.g. Table
, Sum
, Plot
, Plot3D
, Do
, For
, Integrate
, RecurrenceTable
and many more). Why does it behave this way? Would it not be safer if all of these constructs used Module
for localization instead of Block
?
The answer is that the present behaviour is more convenient for interactive operation, particularly when one is engaged in the kinds of symbolic manipulations that motivated the creation of Mathematica in the first place. The Mathematica tutorials are full of examples like this:
expr = i^2;
Table[expr, {i, 1, 10}]
(* {1, 4, 9, 16, 25, 36, 49, 64, 81, 100} *)
If the iterator variable i
were localized as if by Module
, then the result would be:
(* {i^2, i^2, i^2, i^2, i^2, i^2, i^2, i^2, i^2, i^2} *)
Less than useful for an ad hoc interactive session.
The design decision could have gone either way, but I have found that in such cases the decision made often favours interactive use over library code implementation. This general principle does mean that we must take greater care when writing re-usable ("library") code.