Set(=)和SetDelayed(:=)有什么区别?

12

这个讨论在之前的问题中出现过,我很想知道这两者之间的区别。最好用一个例子进行说明。

3个回答

16

基本示例

这是来自Leonid Shifrin的书Mathematica编程:高级介绍的一个示例。

这是解决此类问题的绝佳资源。请参见:(1) (2)

ClearAll[a, b]

a = RandomInteger[{1, 10}];

b := RandomInteger[{1, 10}]

Table[a, {5}]
{4, 4, 4, 4, 4}
Table[b, {5}]
  {10, 5, 2, 1, 3}

复杂示例

上面的示例可能会让人觉得,使用Set创建符号的定义后,其值就是固定的,不会发生变化。但实际上并非如此。

f = ...将表达式分配给f,并在赋值时进行评估。如果符号仍然存在于已评估的表达式中,且稍后它们的值发生更改,则f的显示值也会随之改变。

ClearAll[f, x]

f = 2 x;
f
  2 x
x = 7;
f
14
x = 3;
f
  6

记住规则在内部的存储方式是有用的。对于分配了一个值的符号 symbol = expression,规则存储在OwnValues中。通常情况下(但不总是),OwnValues只包含一条规则。在这种特殊情况下,

In[84]:= OwnValues[f]

Out[84]= {HoldPattern[f] :> 2 x}

我们现在关注的重点是右侧,其中包含符号x。对于评估来说,真正重要的是这种形式——规则在内部存储的方式。只要x在赋值时没有任何值,无论是Set还是SetDelayed都会在全局规则库中产生(创建)相同的规则,这就是所有需要关注的。因此,在这个上下文中,它们是等效的。
最终结果是一个类似函数的行为的符号f,因为它的计算值取决于当前x的值。然而,这不是一个真正的函数,因为它没有任何参数,并且只触发符号x的更改。通常情况下,应该避免使用这样的结构,因为隐式依赖于全局符号(变量)在Mathematica中和其他语言一样糟糕——它们使代码更难理解,而且漏洞更加微妙,更容易被忽视。相关讨论可以在这里找到。

用于函数的集合

Set 可以用于函数,有时候需要使用它。让我给你举个例子。这里Mathematica符号性地解决了和式,然后将其分配给aF(x),然后用于绘图。

ClearAll[aF, x]

aF[x_] = Sum[x^n Fibonacci[n], {n, 1, \[Infinity]}];

DiscretePlot[aF[x], {x, 1, 50}]

enter image description here

如果您尝试使用SetDelayed,则需要将要绘制的每个值传递给Sum函数。这不仅会慢得多,而且至少在Mathematica 7上会完全失败。
ClearAll[aF, x]

aF[x_] := Sum[x^n Fibonacci[n], {n, 1, \[Infinity]}];

DiscretePlot[aF[x], {x, 1, 50}]

如果想确保在定义新函数的过程中,形式参数(此处为x)的可能全局值不会干扰和被忽略,那么除了使用Clear之外,另一种选择是在定义周围使用Block
ClearAll[aF, x];
x = 1;
Block[{x}, aF[x_] = Sum[x^n Fibonacci[n], {n, 1, \[Infinity]}]];

查看函数的定义,可以确认我们得到了想要的结果:

?aF
Global`aF
aF[x_]=-(x/(-1+x+x^2))

它在 M8 中也失败了。使用 Evaluate 有帮助:DiscretePlot[aF[x] // Evaluate, {x, 1, 50}] - Sjoerd C. de Vries
1
抱歉,我不理解你的斐波那契数列示例。当x >= 1/GoldenRatio时,求和不会收敛,所以SetDelayed是正确的选择。那么为什么在这里使用Set更好呢? - Jo Mo
@JoMo 看起来这只是一个愚蠢的错误。奇怪的是,十多年来没有人指出它。谢谢。 - Mr.Wizard

9
In[1]:= Attributes[Set]

Out[1]= {HoldFirst, Protected, SequenceHold}

In[2]:= Attributes[SetDelayed]

Out[2]= {HoldAll, Protected, SequenceHold}

正如您从它们的属性中所看到的,这两个函数都保存了它们的第一个参数(您要分配的符号),但它们的区别在于SetDelayed也保存了它的第二个参数,而Set则不保存。这意味着Set将在分配时计算等号右侧的表达式。SetDelayed直到变量实际使用时才计算:=右侧的表达式。
如果赋值的右侧具有副作用(例如Print()),那么正在发生的情况会更加清晰:
In[3]:= x = (Print["right hand side of Set"]; 3)
x
x
x

During evaluation of In[3]:= right hand side of Set

Out[3]= 3

Out[4]= 3

Out[5]= 3

Out[6]= 3

In[7]:= x := (Print["right hand side of SetDelayed"]; 3)
x
x
x

During evaluation of In[7]:= right hand side of SetDelayed

Out[8]= 3

During evaluation of In[7]:= right hand side of SetDelayed

Out[9]= 3

During evaluation of In[7]:= right hand side of SetDelayed

Out[10]= 3

1
对于 x := (Print["right hand side of SetDelayed"]; 3) 这种构造方式,我给予加一的评价。这种技巧在调试时非常有用,可以看到规则何时被触发。 - Simon

3

:=用于定义函数,=用于设置值。

例如,:=在读取时会被计算,=在设置时会被计算。

可以这样理解:

x = 2
y = x
z := x
x = 4

现在,如果在y仍为2的情况下进行评估,z将为4。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接