Great question. My first reaction is that this shouldn't happen. h
appears explicitly inside the Module
and it should be substituted with the localized equivalent, e.g. h$123
, even if it is held:
Module[{h}, HoldComplete[h^2]]
HoldComplete[h$6992^2]
However named patterns in constructs such as Set
and Rule
are exempt from this substitution:
Module[{h}, Hold[h_ -> h^2]]
Hold[h_ -> h^2]
Module[{h}, Hold[f[h_] = h^2]]
Hold[f[h_] = h^2]
This is related to:
Until now I did not realize that this affected constructs without a Hold attribute. That is I knew that Module
handles Function
, SetDelayed
, and RuleDelayed
differently:
Module[{x}, Function[x, x^2]]
Function[x, x^2]
Module[{x}, f[x_] := x^2]
?f
f[x_] := x^2
Module[{x}, x_ :> x^2]
x_ :> x^2
This actually makes sense as the x
is already being localized by these constructs and additional substitution by Module
would only result in e.g.:
Function[x$123, x$123^2]
However in the case of Set
or Rule
, which do not have hold attributes, this is problematic, as these are not localizing constructs as correctly recognized by the syntax highlighter. Daniel Lichtblau wrote plainly:
No need to dance around this: it's a flaw in the implementation of the scoping mechanism. Set
SetSystemOptions["StrictLexicalScoping" -> True];
and it gives 16.
So it seems that my first reaction was correct, and this shouldn't happen but does.
Further thoughts and exploration
Leonid comments below that Set
and Rule
are still scoping constructs. I do not contest this. As noted the difference is in the evaluation behavior controlled by hold attributes.
Let us ask the question:
- How should the right-hand-side Symbols in
Set
andRule
be handled withinModule
?
The existing default behavior leaves both the LHS and RHS x
unchanged in this case which results in the RHS instance evaluating to the global value of x
if it exists:
Module[{x}, x_ -> x]
x_ -> x
However a slight change results in different behavior:
Module[{x, y}, x_ -> x y]
x$_ -> x$ y$748
Here the automatic renaming mechanism kicks in and changes x
to x$
on both sides of the Rule
.
If as in SetDelayed
and RuleDelayed
the RHS is not expected to immediately evaluate this difference in renaming often will not matter. However with Set
and Rule
where the RHS is expected to evaluate this inconsistency is troubling. One cannot generally rely on the behavior illustrated in the original question as it will break if this renaming is activated:
h = 5; i = 7;
Module[{h, i}, h_ -> h^2]
Module[{h, i}, h_ -> i*h]
h_ -> 25 h$_ -> h$ i$787
From a user's perspective the simple introduction of i
in the RHS should not result in an entirely different evaluation characteristic, yet it does. (This affects Set
as well.) It would be desirable to have consistency in this behavior, and it seems that is exactly what Daniel's System Option provides:
SetSystemOptions["StrictLexicalScoping" -> True];
h = 5; i = 7;
Module[{h, i}, h_ -> h^2]
Module[{h, i}, h_ -> i*h]
h$_ -> h$^2 h$_ -> h$ i$1334
Now the h
is renamed to h$
on both sides of both rules.