As the title asks, why does
b = a;
f[a_] := b;
f[2]
evaluate to a
rather than 2
?
The post is long.
If you just want to know how to fix the code, please search "fix the code in title in 10 ways" in the page.
If you want to read discussions about global/local variable, please search Final Remark in the page.
The a
hidden in b
isn't explicit so a_
cannot notice it.
If you can't understand this short answer, please go on reading.
I've waited for years but don't see a carnonical answer for this in this site, so decide to step up posting this as my 1000th undeleted answer :) .
The Chinese edition of this answer can be found here.
Before dealing with the question in title, let's consider another problem as a start:
Why doesn't the following sample work?
f[max_] := Plot[Sin[a x], {x, 0, max}] Manipulate[f[max], {a, 1, 5}, {max, 10, 20}]
Reading this, those familiar with document of Manipulate
would smile: "This is an example from Possible Issues section of document of Manipulate
!" Yeah, and the answer is already documented there:
Manipulate
only "notices" explicit visible parameters.
And we need to "redefine f
to include the parameter a
explicitly":
f[a_, max_] := Plot[Sin[a x], {x, 0, max}]
Manipulate[f[a, max], {a, 1, 5}, {max, 10, 20}]
"OK, so what? This is just kind of bizarreness of Manipulate
!" Well, it's not bizare. Actually the explicit existence requirement (forgive me for coining a phrase) is almost ubiquitous in Mathematica. You may seldom notice this, because you can freely use Mathematica without knowing it in most cases (mainly because of automatic evaluation, which is also ubiquitous). But once you've dived deep enough into the core language, the requirement will be nonnegligible. Let's see another example:
Why doesn't
Clear[x,y] y = x^2; Module[{x = 5}, x + y]
evaluate to
30
?
The sample is short, so Trace
is handy enough for checking the evaluation process:
Module[{x = 5}, x + y] // Trace
Remark
Still, if you feel the output of
Trace
hard to read, consider the tools in this post.
"OK, so what?"… Let's replace Module
with Block
and compare the outputs:
Block[{x = 5}, x + y] // Trace
By comparing the outputs, we can see the difference: Module
doesn't notice the x
hidden in y
. In other words, just like Manipulate
, the localization of Module
is also enslaved by the explicit existence requirement.
So does With
:
With[{x = 5}, x + y] // Trace
And once the x
inside y
becomes explicit in some way, Module
/With
will be able to notice it, for example:
With[{x = 5}, x + y // Evaluate]
(* 30 *)
In this example, Evaluate
has temporally penetrated the HoldAll
attribute of With
, so x + y
has evaluated to x + x^2
before going into With
. This fix doesn't make much sense though, because localization of Module
/With
will be broken:
x = aaa; With[{x = 5}, x + y // Evaluate]
(* aaa + aaa^2 *)
The followings are two better fixes to preserve the localization:
Hold@With[{x = 5}, x + y] /. OwnValues@y // ReleaseHold
Unevaluated@With[{x = 5}, x + y] /. OwnValues@y
Remark
With[{x = 5}, x + Evaluate@y]
won't work, becauseEvaluate
only has effect on the function next to it, see the document ofEvaluate
for more info.
With[{y = y}, With[{x = 5}, x + y]]
won't work, because the innerx
will be renamed. This is also an interesting topic but not quite related to this question so I won't talk about it here, you may start from this post to learn more.If you want to learn more about
Unevaluated
, here is a good tutorial.
Now, we see how special the function Block
is: even if the variable isn't explicitly there, the localization of Block
will succeed. This feature is convenient (Names["*Plot"]
functions are all localized with Block
internally as far as I know) and dangerous (if you don't use it properly). Since we already have this post about Module
, With
and Block
, I'd like not to talk too much about this topic in this post. Let's go on talking about explicit existence requirement.
Now let's deal with the question in title. To help readers understanding the topic better, I'll extend it to four related questions:
(1.1)
Why does
b = a; f[a_] := b; f[2]
evaluate to
a
rather than2
?(1.2)
Why does
b = a; f[a_] = b; f[2]
evaluate to
2
?(1.3)
Why does
b = a; f[a_] := Evaluate@b; f[2]
evaluate to
2
?(1.4)
Why does
b = a; f[a_] = Unevaluated@b; f[2]
evaluate to
a
rather than2
?
"How to understand these? Trace
doesn't seem to help much here. " Yeah it's not quite obvious in output of Trace
, but we know that, the code f[a_] := b
essentially stores something called downvalue in f
, and we can use the function DownValues
to display the downvalue as a Rule
:
b = a; f[a_] := b; DownValues@f
And the effect of downvalue is the same as a usual Rule
used in Replace
, ReplaceAll
, etc. In other words, ignoring the performance difference, code like f[2]
can be replaced with Replace[…]
in following manner:
f[x_] := x
f[3]
rule = DownValues@f
Clear[f]
Replace[f[3], rule]
f[x_] := x
f[3] + f[4]
rule = DownValues@f
Clear[f]
Replace[f[3], rule] + Replace[f[4], rule]
Remark
As to performance difference, you can clearly see it with
f[x_] := x f[3] // RepeatedTiming (* {2.26217*10^-7, 3} *) rule = DownValues@f; Clear[f] Replace[f[3], rule] // RepeatedTiming (* {5.72101*10^-7, 3} *)
Since it's not quite related to the topic discussed in this post, I'd like not to talk about it here.
So now the question becomes:
Why doesn't
Clear[f, a]; b = a; Replace[f[3], {HoldPattern[f[a_]] :> b}]
evaluate to
3
? How can we explain this behavior of pattern matching in a simple and clear way?
Reading all the stuff above, probably you know what I'll say: it's because pattern matching only matches expression explicitly there. Though a a
is stored in b
in HoldPattern[f[a_]] :> b
, it's not explicit. In other words, the a_
in left hand side of :>
cannot notice the a
hidden in b
, so the replacement fails.
OK, then why does b = a; f[a_] = b; f[2]
return 2
? This is where automatic evaluation comes into play. We know that, the function Set
(=
) owns attribute HoldFirst
(unlike SetDelayed
(:=
), which owns the attribute HoldAll
):
Attributes@Set
(* {HoldFirst, Protected, SequenceHold} *)
In other words, the second argument of Set
i.e. right hand side of =
will automatically evaluate before it's controlled by Set
:
b = a; Trace[f[a_] = b]
This automatic evaluation has stripped the shell (in this case, b
) outside of a
. Let's check the downvalue in this case:
b = a; f[a_] = b;
f // DownValues
(* {HoldPattern[f[a_]] :> a} *)
As we can see, the a
is explicit in right hand side of the :>
in this case, so the pattern matching will happen as expected.
The example (1.3) can be explained in a similar manner: though we've used SetDelayed
in b = a; f[a_] := Evaluate@b; f[2]
, the Evaluate
has enforced evaluation of second argument so the HoldAll
attribute doesn't have any effect on right hand side of :=
in this case, and the a
inside b
becomes explicit. This can be seen with Trace
and DownValues
:
b = a;
Trace[f[a_] := Evaluate@b]
DownValues@f
Remark
Notice that, just like the
With[…, Evaluate@…]
example above, usingSetDelayed
doesn't show any advantage compared withSet
in this case, because the localization is broken. (But still,SetDelayed
has caused a tiny difference in this case, see this post for more info. )
On the contrary, though we've used Set
in the example (1.4), the Unevaluated
has stopped the automatic evaluation once, so, unlike the example (1.2), this time the shell isn't stripped, and the a
inside b
doesn't become explicit, so f[2]
won't evaluate to 2
:
b = a;
Trace[f[a_] = Unevaluated@b]
f // DownValues
Remark
These four examples show us that, when defining a function, one should not blindly rely on
Set
(=
) orSetDelayed
(:=
) only. To choose between=
and:=
, you don't need any rule of thumb, just think about the following:
Can the right hand side of
=
/:=
be evaluated instantly?Should the right hand side of
=
/:=
be evaluated instantly?
For better illustration let's see another three examples:
(2.1)
Why doesn't
2 /. (1 -> a)
evaluate to
2 a
?(2.2)
Why does
1 + 1 /. (2 -> a)
evaluate to
a
?(2.3)
Why doesn't
Unevaluated[1 + 1] /. (2 -> a)
evaluate to
a
?
Again, this can be explained by explicit existence requirement. Being intelligent creatures, we human beings can realize 2
in example (2.1) is equal to 1 + 1
, but Mathematica isn't matching in this manner, it only checks if 1
explicitly, clearly, visably, literally exists in the left hand side of /.
. 1
won't match 2
, just as abc
won't match defg
.
Remark
If pattern matching is kind of semantic matching then it'll really be a disaster: should
2
match1 + 1
or2 × 1
or2 × 1 × 1
…?
"OK, now I understand (2.1), then why does the matching succeed in (2.2)?" Because the automatic evaluation is there. ReplaceAll
(/.
) is a function without attribute like HoldAll
, HoldFirst
, etc:
In other words, the arguments of ReplaceAll
will evaluate before going into ReplaceAll
. Let's Trace
:
1 + 1 /. (2 -> a) // Trace
As we can see, 1 + 1
has evaluated to 2
, which matches the rule 2 -> a
. But for
Unevaluated[1 + 1] /. (2 -> a)
the automatic evaluation of 1 + 1
in left hand side is stopped by Unevaluated
, so ReplaceAll
doesn't notice the 2
(in semantic sense), and the replacement doesn't happen.
Similarly,
Unevaluated[1 + 1] /. (1 + 1 -> a)
won't evaluate to a
, and
Unevaluated[1 + 1] /. (HoldPattern[1 + 1] -> a)
will evaluate to a
. The only thing that's worth noticing is, the HoldPattern
cannot be replace by Unevaluated
in this example, see this post for more info.
Remark
This isn't quite related to the topic discussed in this post, but do you know what will be the output of the following?:
a/. b_. c_ :> c^2
If you feel confused, read this.
As mentioned in the beginning of this post:
The explicit existence requirement is almost ubiquitous in Mathematica. You may seldom notice this, because you can freely use Mathematica without knowing it in most cases (mainly because of automatic evaluation, which is also ubiquitous).
To illustrate the ubiquitousnes of automatic evaluation, I'll add one more example. We know Thread
is a useful function, it can be used in the following manner:
Clear[f, a, b, c]
lst = {a, b, c}; Thread[f[lst]]
(* {f[a], f[b], f[c]} *)
But,
lst = {a, b, c};
Thread[Unevaluated@f[lst]]
(* f[{a, b, c}] *)
Why?
Because Thread
is also enslaved by the explicit existence requirement. Given that Thread
doesn't have any Hold*
attribute, its arguments always automatically evaluate unless we intentionally adjust evaluation order (as I've done with Unevaluated
above), so we almost never have a chance to notice the influence of explicit existence requirement on Thread
in our daily life.
Again, let's Trace
:
lst = {a, b, c}; Thread[f[lst]] // Trace
As we can see, f[lst]
already becomes f[{a, b, c}]
before going into Thread
, so Thread
can notice the structure therein, but:
lst = {a, b, c}; Thread[Unevaluated@f[lst]] // Trace
In this example, what's going into Thread
is just f[lst]
, the list {a, b, c}
stored in lst
is not explicit. In the eyes of Thread
, the code is just the same as
Clear@lst; Thread[f[lst]]
So the transformation won't happen.
Time to end this post. Well, though I intended to talk about explicit existence requirement, discussions about evaluation order, pattern matching, etc. have involved in, but the various topics about core language are just related to each other, so it's a bit hard for me to discuss them separately. Explicit existence requirement isn't the only possible summary for the behavior discussed in this post, and there exist at least two alternatives:
In document Blocks Compared with Modules, behavior of Module
etc. is described as lexical scoping.
In Leonid Shifrin's book Mathematica programming: an advanced introduction, the behavior of pattern matching is described as syntactic rather than semantic comparison of expressions.
These explanations are essentially equivalent to explicit existence requirement in my view. I prefer the explicit existence requirement because it's a straightforward explanation stems from the document of Manipulate
. Finally, just for fun, let me fix the code in title in 10 ways:
(* 1 *)
Clear[f]
b = a;
f[a_] = b;
f[2]
(* 2 *)
(* 2 *)
Clear[f, b]
b[a_] := a;
f[a_] := b[a];
f[2]
(* 2 *)
(* 3 *)
Clear[f]
b = a;
(f[a_] := #) &@b;
f[2]
(* 2
(* 4 *)
Clear[f]
b = a;
SetDelayed @@ {f[a_], b};
f[2] *)
(* 2 *)
(* 5 *)
Clear[f]
b = a;
f[a_] := Evaluate@b;
f[2]
(* 2 *)
(* 6 *)
Clear[f]
b = a;
Unevaluated[f[a_] := b] /. OwnValues@b;
f[2]
(* 2 *)
(* 7 *)
Clear[f]
b = a;
Block[{SetDelayed}, f[a_] := b];
f[2]
(* 2 *)
(* 8 *)
Clear[f]
b = a;
Inactivate[f[a_] := b, SetDelayed] // Activate;
f[2]
(* 2 *)
(* 9 *)
Clear[f]
b = a;
Hold[f[a_] := b] /. Language`ExtendedDefinition[b][[1, -1, 1, -1]] // ReleaseHold;
f[2]
(* 2 *)
(* 10 *)
Clear[f]
b = a;
ClearAttributes[SetDelayed, HoldAll]
f[a_] := b;
SetAttributes[SetDelayed, HoldAll]
f[2]
(* 2 *)
More than once, I see someone trying to explain the topic using the concept global variables and local variables. This is not a good idea in my view, because it's replacing the original problem with a new problem that's almost equivalently complicated: how will you explain the meaning of global/local variable? To make these concepts really clear, your explanation will finally contain something similar to explicit existence requirement, this is called xzczd's tenth rule.
With
, Module
and Block
, but indeed, I should have mentioned the phrase lexical scoping (which is another equivalent description of explicit existence requirement in my view) at least. Edited. Thx for pointing out!
$\endgroup$
I thinks it is pretty obvious why the first code returns a
and the second 2
. You can see it by comparing Definition[f]
in both cases.
Clear[a, b, f]
b = a;
f[a_] := b;
f[2]
Definition[f]
Clear[a, b, f]
a
f[a_] := b
Compared with:
Clear[a, b, f]
b = a;
f[a_] = b;
f[2]
Definition[f]
Clear[a, b, f]
2
f[a_] = a
a
in b=a
is global variable while a
in f[a_]
is local variable. So f[a_] = a
returns local value of a
, i.e. f[2]=2
. But f[a_] := b
returns global b
which was previously set to value a
which is a global variable and this global variable a
has nothing to do with local variable a
in f[a_]
.
$\endgroup$
Commented
Apr 30 at 11:07
To understand what is going on you need to look at "DownValues" to see what is actually stored:
b = a;
f[a_] := b;
DownValues[f]
{HoldPattern[f[a_]] :> b}
And;
b = a;
f[a_] = b;
DownValues[f]
HoldPattern[f[a_]] :> a}
In the first case, f[2] is evaluated to b and b is evaluated to a.
In the second case we have f[a_]-> a, there fore f[2] is evaluated to 2.
{HoldPattern[f[a_]] :> b}
, isn't the a
right there inside b
? Why can't the clever Mathematica see it?
$\endgroup$
f
is defined by SetDelayed
, which does not evaluate its arguments (neither the lhs nor the rhs ) before actually calling f
with an argument (SetDelayed
has HoldAll
attribute), hence the symbolic definition of f
can't see the global a
.
$\endgroup$
Commented
Jul 16 at 19:14
b = a; f[a_] = b; f[2]
evaluate to2
:) ? $\endgroup$a_
is after all scoped. I could see a claim that there is a bit of subtlety since there are different behaviors between use ofSetDelayed
vs.Set
. Though it still does not strike me as being all that mysterious. Then again, I'm used to it. $\endgroup$