What happens and why
As Daniel Lichtblau pointed out in comments, this behavior can also be viewed as a flaw in the current behavior / design / implementation of lexical scoping in Mathematica. However, it may be useful still to understand on a deeper level what happens, since it can be explained rather easily from the core rules of how lexical scoping operates in the default mode in current (and past) versions of Mathematica.
While Mr.Wizard already discussed part of what's happening in his (now deleted) answer, I think it pays off to understand this from a somewhat different angle. Set
is a scoping construct, and as such, it protects its bindings from possible actions of the outer scoping constructs. The general mechanics of how this happens has been discussed in our answer, but let's focus on the specifics here.
One thing to understand is what is the action of the outer scoping construct (Module
) in this case. The action is variable binding, that is, renaming of variable h
to a newly produced local symbol looking like h$123
, everywhere in the code, before the body of the scoping construct (Module
) is allowed to execute. It is this action that Set
has to guard its arguments against.
The second point here is that while the protection is needed for Set
, the only function in position to enforce such protection is in fact Module
, just because it is the only one being evaluated at that stage. So, Module
's internal code has to detect Set
and protect its arguments. This is very similar to other cases, but while there things get renamed, here, on the opposite, such renaming h -> h$123
has to be prevented.
Now, here is one way to show that this is indeed what happens: use a standard idiom to fool the inner scoping construct detection mechanism:
Module[{a, h}, Set @@ Hold[a[h_], h^2]; a[4]]
(* 16 *)
Now, Module
can't detect Set
inside, and therefore h
is being successfully bound to the Module
-generated variable, and we get what we'd naively expect.
So, to summarize: we have here the same situation as with other cases described in the linked post, but the main difference is that while there outer scoping constructs were in charge of making renamings, here it is the opposite: in the case of Set
, the outer lexical scoping constructs are responsible for prevention of such renamings. In this particular case, Module
is then forced to disable its own binding mechanism.
Note that SetDelayed
is different, because while the Module
behavior would be the same, SetDelayed
makes its own bindings with it's r.h.s., which effectively localize the pattern variable correctly. Note also that this issue only holds for lexical scoping constructs, and dynamic scoping isn't affected by this.
Ways out
1. Use "StrictLexicalScoping" -> True system options setting
Thanks to Daniel's work, we have now an option available to change it. If you set
SetSystemOptions["StrictLexicalScoping" -> True];
Then the result is what it was expected to be:
Module[{a, h}, a[h_] = h^2; a[4]]
(* 16 *)
2. Use "standard" techniques to fool the inner scoping construct detection mechanism
This is as described above, but any such technique would do. See the linked post for some of the most commonly used. Admittedly, the need to use such tricks in such a case is a pain in the neck.
Summary
What happens can be viewed as a design / implementation flaw. Thanks to the work of Daniel Lichtblau, now there is an option available to switch to a mode which is free of these flaws. OTOH, the above behavior does not look like a plain bug to me, but rather a consistent behavior fully explained by the rules of how lexical scoping constructs operate in current versions of Mathematica in default mode. I have tried to explain the logic behind this behavior from the deeper principles of how scoping constructs currently behave in the default mode. It is another issue that those rules may be considered flawed, and we've seen discussions of many other instances exposing these flaws, here on SE.
h^2
anda[4]
, otherwise you'd get an error about giving 3 arguments toModule
. I edited it to fix the problem. $\endgroup$Module
is a fantastic way to have a clean program, but your problem here is using=
(Set
) instead of:=
(SetDelayed
). In my opinion, there are very few instances where it's better to useSet
. $\endgroup$