Mathematica中"="与":="定义区别!
如果你
1. 觉得 := 能把自变量全部染绿感觉很好看所以只用 :=
2. 觉得 := 写出来显得很专业所以只用 :=
3. 在某一次使用 := 或是 = 定义函数后出了问题,从此后就只用 = 或是 := 了
4. 觉得=更像传统数学表达式所以只用 =
5. 因为自带帮助教程《立即定义和延时定义》(tutorial/ImmediateAndDelayedDefinitions)里说了一句"在无法确定时用 := 总比用 = 好一些. "而开始无脑使用 := 定义函数
6. 某篇教程声称 = 是用来定义变量的而 := 是用来定义函数的
7. 因为其他的什么理由而只使用 := 或 =
那么,请来看看这篇教程。
自带帮助中的教程《立即定义和延时定义》(本教程似乎在版本12.1变成了教程《Transformation Rules and Definitions》的一节,内容无变化)对此已经有了较为清楚的讲解(要说有什么缺点,那就是举例或许太过温柔了),本文仅做少许的补充说明。请确保自己在阅读本文前已经阅读了上述教程。(不知道怎么查自带帮助的童鞋则请先看这篇教程。)
注意,自带帮助教程《立即定义和延时定义》中的论断 "在无法确定时用 := 总比用 = 好一些" 误导大于实用,请将之忽略。
顺便,老沃在他的《An Elementary Introduction to the Wolfram Language》的第39章《Immediate and Delayed Values》也谈了这个问题,不过这书的免费在线版只有英文版和世界语版,而中文版目前只出了纸质书。
其实本文想说的内容已经涵盖在了《为什么b=a; f[a_]:=b; f[2]不输出2?——说说显式存在的重要性》一文中,若是能直接读这篇那是最好。
好的,闲话少叙(你叙得够多了好吗),赶紧开始补充。首先,再度强调一遍:
难以区分该用 = 还是 := 的情形几乎是不存在的。何时使用 Set 何时使用 SetDelayed 无需死记硬背,你只需要想一想:
1. 你的函数表达式是否需要被立刻计算?
2. 你的函数表达式是否可以被立刻计算?
干巴巴地讲效果不好,我们照例看几个例子。
(1)
为什么
Clear[f]
f[x_] := D[x^2, x]
f[3]
会导致问题呢?:

警告信息的内容是" 3 不是一个有效的变量",哪里有 3 ?

显然,这个 3 指的是偏导符号里的 3 ——什么?你不知道

是啥?那么,我们把光标移到这个符号上再点 F1 打开自带帮助:

啊~这东西其实就是 D ,确切地说这是 D 的 StandardForm 。实际上,你将光标移到上面,再按 Ctrl + Shift + I ,就能使它变成我们输入时用过的 InputForm 了:

题外话
"啊————————Mathematica出错啦怎么办怎么办我不活啦!"

——以上是许多初学者看到警告信息后的第一反应,在此,我想说,诸位看到警告信息时不应惊慌,问题的答案往往就藏在警告信息里。(话说是不是该找时间写篇关于警告信息的教程……)
好的, f[3] 计算后居然变成了 D[9, 3] ,这显然不对,可是为什么呢?读了开头我要求大家必须读的教程之后,大家应该知道,我们在定义这个函数时使用的 SetDelayed ( := ),是不会立刻计算等式右侧的。(顺便,这是因为它拥有属性 HoldAll ——不明白 HoldAll 是啥的同学不妨按下 F1 查查帮助。)这意味着什么?自带帮助告诉了我们,可以用 Information( ? ) 函数来检查:
?f

当然了,我个人还是更喜欢直指问题中心的 DownValues :
DownValues@f

这里的 @ 是个简写符号, DownValues@f 等效于 DownValues[f] 。
如我们所见, D[x^2, x] 还是那个 D[x^2, x] ,并没有变成(或者说计算成) 2 x 。
"那又怎么样啊,没计算就没计算, D[x^2, x] 就是 2 x ,晚点算也是一样的嘛!"" D[x^2, x] 就是 2 x "这个说法到底对不对我们先不谈,我们先来考虑这么一个问题:
当一个函数被使用时,整个计算过程是怎样的?
"就是……执行了一下,然后就算出来了嘛!"……让我们来考虑得细一点。你说,当
f[3]
执行的时候,这个 3 ,是什么时候代入我们前面的等式右端的呢?
看了上面的输出我想大家已是心里有数了,不过我们还是例行公事地检查一下。这种短代码很适合用 Trace 来检查:
f[3] // Trace

这里的 // 又是个简写,f[3] // Trace 等效于 Trace[f[3]] 。
如我们所见,函数计算时,是先代入自变量,再计算表达式的。而这在 D 上出了问题,因为 D 不能先代入数字。
题外话
很多人,包括许多使用了 Mathematica 相当长时间的人,对"计算次序"的理解其实都是一团浆糊,当然了,即便你对计算次序只有非常基本的了解,也能活得很好, Mathematica 就是如此设计的,但是,当你的编程深入到了一定程度,你将无可避免地涉及计算次序问题。这一部分请参考开头的《显式存在》一文,这里不再多说。
那么这个问题该怎么解决?最简单的方法,当然是用立即赋值咯:
f[x_] = D[x^2, x]
不放心?那我们照例检查一下:
Trace[f[x_] = D[x^2, x]]
DownValues@f


如我们所见, D[x^2, x] 在变为函数之前就已经计算了,存入 f 的是 2 x 。这个函数定义自然不会导致前面的问题。
(2)
感觉有上面这个例子也差不多了吧下面再怎么谈也都是一个套路啊我们再来看一个例子。这次整个积分吧:
f[k_] := Integrate[Sin[k x y], {x, 0, 1}, {y, 0, x}]
这个函数倒也能用,不会像(1)那么夸张:
f[0.5]

我们来画个图看看:
Plot[f[k], {k, -5, 5}]

那么,为什么会这么慢?我们已经知道,由 := 定义的函数,并不会立刻计算等式的右边,也就是说, Integrate 就在我们的函数定义里:

而 Plot 实际上就是个数值计算器,它会取许多不同的 k 值代入这个函数进行计算,也就是说这个积分会在 Plot 里被反复计算,自然很慢。
对于这种即使 k 未知也能算出来的积分,当然是应该用立即赋值立刻计算:
f[k_] = Integrate[Sin[k x y], {x, 0, 1}, {y, 0, x}]
Plot[f[k], {k, -5, 5}]

题外话
虽然在这个例子中并不明显,但是,使用
Plot[f[k] // Evaluate, {k, -5, 5}]
还能把画图速度提高一点,因为我们这个函数定义使用了模式匹配,而模式匹配是无法被自动编译的。这部分算是高级内容,有兴趣的可以参看《怎样编译(Compile)/编译的通用规则/学会这6条,你也会编译》一文,看不懂也不必气馁。
(3)
也差不多该举点 Set ( = )的例子了。好的,推理时间。下面这个现象是怎么回事?:

可能的真相不止一种,不过此处我为大家准备的真相是: x 里面预先存了东西。

"对嘛! = 就是这点不好,自变量里面一旦存了东西就全完了,根本防不胜防嘛!所以我才只用 := 的!"不,并不是防不胜防,实际上,你在写下代码的那一刻,本就应该注意到 x 已被污染。好的,下面是大家来找茬时间。请问下面两张图上的代码有什么不同?


没错,是颜色。里面没东西的 x 是蓝的,而存了东西的 x 是黑的,注意到了这点就足以避免这所谓"防不胜防"的错误了。关于这个问题已单开教程《有的字母蓝,有的字母黑,你知道为什么吗?你对语法着色有认识吗?》,这里不再多说。
题外话
如果你对上面的截图中,x = "你的函数算不出来啦哇哈哈" // Sqrt 写在了f[x_] = x^2;之下感到惊讶,那么,你对Mathematica的笔记本(Notebook)的工作原理存在重大误解。简单地说,代码的"先后"仅取决于它们的执行次序,而与它们书写的位置无关(你单开个笔记本另写都行)。至于执行次序,只要看看行号就能明白:下面是343,而上面是344和345。
(4)
我们已经知道, Set ( = )在定义函数时会立刻计算等式的右端,非要粗略地说的话——注意,本人依旧反对基于经验规则来判断 := 和 = 的选择!——大部分的非算术函数使用 = 来定义都会出问题,比如
Clear[expr]
plot[expr_] = Plot[expr, {x, 0, 3}]
expr 里面还没有含 x 的表达式,显然立刻计算行不通。但是,并非总是如此,即便定义函数时产生了警告。比如:
take[a_, y_] = a[[y]]

我们可以看到,尽管 Mathematica 跳了警告,但最后存进 take 的依旧是 a[[y]] 。这个函数当然能用:

这一特性有时可以被用来简化计算次序调节,具体的例子嘛,我现在累了所以不写了涉及的又是高级内容这里姑且就不谈了。
。