表达式的运算
运算原则 | 循环和控制结构 |
将表达式还原为标准形式 | 在运算过程中收集表达式 |
属性 | 追踪运算 |
标准运算过程 | 运算堆栈 |
非标准运算 | 控制无限运算 |
模式、规则和定义的运算 | 中断和中止 |
迭代函数中的运算 | 编译 Wolfram 语言表达式 |
条件语句 |
例如,Wolfram 语言使用内置的整数加和过程运算表达式 6+7. 相似地,Wolfram 语言使用内置的简化过程来运算代数表达式 x-3x+1. 如果定义了 x=5,Wolfram 语言将使用此定义将 x-3x+1 简化至 -9.
表达式和运算可能是 Wolfram 语言中最重要的两个概念. "表达式" 讨论如何使用表达式将 Wolfram 语言处理的所有不同类型的对象以统一的方式表示. 本教程介绍如何以统一的方式将 Wolfram 语言可执行的所有操作视为运算示例.
Wolfram 语言遵循应用定义的原则,直到结果不再改变为止. 这意味着,如果您将 Wolfram 语言给出的最终结果作为 Wolfram 语言输入输入,您将再次获得相同的结果. (在“控制无限运算”中讨论了一些微妙的情况,这类情况不会出现.)
运算的最简单例子包括使用定义如 f[x_]=x^2 将一个表达式直接转换为另一个表达式等. 运算也是执行用 Wolfram 语言编写的程序的过程. 因此,如果您有一个由一组 Wolfram 语言表达式组成的程序,其中一些表达式可能表示条件和循环,则该程序的执行将与这些表达式的运算相对应. 有时,运算过程可能涉及多次运算一个特定的表达式,例如循环.
例如,Plus 函数的内置定义是把各项的和写成标准的去括号形式. 加法结合律意味着像 (a+b)+c、a+(b+c) 和 a+b+c 这样的表达式都是等价的. 但是出于种种目的,将所有这些形式简化为一个标准形式 a+b+c 会提供很多方便. Plus 的内置定义就是由此而设置的.
通过 Plus 的内置定义,此表达式可简化为标准的去括号形式:
像加法这样的函数不仅符合结合律,而且符合交换律,这意味着像 a+c+b 和 a+b+c 这样各项顺序不同的表达式是相等的. Wolfram 语言再次尝试将所有这样的表达式变为“标准”形式. 它选择的标准形式中所有项都采用确定顺序,大致是按字母顺序.
有人可能认为,Wolfram 语言应该以某种方式将所有数学表达式自动简化为一种单一的标准规范形式. 但是,除非是最简单的表达式类型,否则很容易明白我们并不希望将所有不同用途的表达式都使用某种相同的标准形式.
例如,多项式有两种明显的标准形式,各自适用于不同的目的. 多项式的第一种标准形式是各项的简单加和,在Wolfram 语言中通过应用函数 Expand 生成. 如果需要进行多项式的加减,则此标准形式最合适.
另一种适应于多项式的标准形式则是通过应用 Factor,将任何多项式写为不可约因子的乘积. 如果要进行除法运算,则这种规范形式非常有用.
从某种意义上说,展开形式和分解形式都是很好的多项式标准形式. 使用哪一个仅取决于使用它的目的. 因此,Wolfram 语言不会对多项式自动采纳其中任何一种形式. 相反,它为您提供了诸如 Expand 和 Factor 之类的函数,使您可以明确地将多项式转换为所需的形式.
仅需通过应用 Expand,就可以将它们都写成展开形式. 在这种形式下,多项式的等价性一目了然:
从某种意义上说,这个结论并不特别令人惊讶. 假如所有数学表达式都可以简化为一种标准形式,那么判断两个表达式是否相等就易如反掌了,而这样的话,数学上的很多难题也就不存在了,因为它们实质上都可以表述为表达式相等性的问题. 这一事实表明,这实际上是很难以实现的.
Wolfram 语言提供了一系列属性,用于指定函数的各种属性. 例如,可以使用属性 Flat 来指定某一特定函数,以便嵌套调用自动被展平,并且其行为就像符合结合律.
像 Flat 这样的属性不仅会影响运算,还会影响模式匹配等操作. 如果为函数提供定义或转换规则,则必须确保首先指定函数的属性.
Attributes[f] | 给出 f 的属性 |
Attributes[f]={attr1,attr2,…} | 设置 f 的属性 |
Attributes[f]={} | 将 f 设置为没有属性 |
SetAttributes[f,attr] | 将 attr 添加至 f 的属性 |
ClearAttributes[f,attr] | 将 attr 从 f 的属性删除 |
Orderless | 无序可交换函数(参数按标准顺序排序) |
Flat | 展平的可结合函数(参数被“扁平化”) |
OneIdentity | f[f[a]] 等,等同于用于模式匹配的 a |
Listable | f 自动线性作用于作为自变量出现的列表(例如 f[{a,b}] 变成{f[a],f[b]}) |
Constant | f 的所有导数全部为零 |
NumericFunction | f 当其自变量为数值量时,假定具有数值 |
Protected | f 的值不能更改 |
Locked | f 的属性不能更改 |
ReadProtected | f 的值不可读 |
HoldFirst | f 的第一个自变量不被运算 |
HoldRest | 除 f 第一个自变量以外的所有参数均不被运算 |
HoldAll | f 的所有自变量均不被运算 |
HoldAllComplete | f 的自变量被视为完全惰性 |
NHoldFirst | f 的第一个自变量不受 N 的影响 |
NHoldRest | f 的所有自变量除第一个之外均不受 N 的影响 |
NHoldAll | f 的所有自变量均不受 N 的影响 |
SequenceHold | 出现在 f 的自变量中的 Sequence 对象不被展平 |
Temporary | f 局部变量,当不再使用时将被删除 |
Stub | 如果明确输入 f,则自动调用 Needs |
这是内置函数 Plus 的属性:
Wolfram 语言中内置数学函数的一个重要属性是 Listable 属性. 此属性指定函数应该自动分布还是“线性”作用于作为参数的列表上. 这意味着函数会有效地单独应用于作为参数的列表中的每个元素.
可在 Wolfram 语言中指定给函数的许多属性会直接影响这些函数的运算. 但是,某些属性仅影响函数处理的其他方面. 例如,属性 OneIdentity 仅影响模式匹配,如"平面和无序函数"中所述. 同样,属性 Constant 仅与微分以及依赖于微分的运算有关.
属性 Protected 影响赋值. Wolfram 语言不允许您进行任何与带有此属性的符号关联的定义. "修改内置函数"中讨论的 Protect 和 Unprotect 函数可以用作 SetAttributes 和 ClearAttributes 的替代方法,以设置和清除此属性. 如"修改内置函数"中所述,大多数内置 Wolfram 语言对象初始都受到保护,因此您不会错误地为其定义.
通常,您可以通过键入 ?f 或使用各种内置的 Wolfram 语言函数来查看对特定符号所做的定义. 但是,如果设置了属性 ReadProtected,则 Wolfram 语言将不允许您查看特定符号的定义. 尽管如此,它将继续在执行运算时使用这些定义.
像 SetAttributes 和 ClearAttributes 这样的函数通常允许您任意修改符号的属性. 但一旦对符号设置了 Locked 属性,则 Wolfram 语言将不允许您在 Wolfram 系统会话的剩余时间修改该符号的属性. 在使用 Protected 或 ReadProtected 之外,还使用 Locked 属性,可以使用户无法修改或读取定义.
Function[vars,body,{attr1,…}] | 带有属性 attr1, …的纯函数 |
Wolfram 系统一旦运算了表达式的头部,就会查看该头部是否是具有属性的符号. 如果符号具有 Orderless、Flat 或 Listable属性,则 Wolfram 系统在运算表达式的元素之后,将立即执行与这些属性关联的转换.
如"运算原则"中所述,Wolfram系统遵循以下原则:对每个表达式进行运算,直到没有进一步的定义适用为止. 这意味着 Wolfram 系统必须继续重新运算结果,直到得到一个在运算过程中保持不变的表达式为止.
2ax+a^2+1 | 这是原始表达式 |
Plus[Times[2,a,x],Power[a,2],1] | 这是内部形式 |
Times[2,a,x] | 首先计算 |
Times[2,7,x] | a 被运算为 7 |
Times[14,x] | Times 的内置定义给出了此结果 |
Power[a,2] | 接下来运算 |
Power[7,2] | 运算 a 之后的结果 |
49 | 对 Power 的内置定义给出此结果 |
Plus[Times[14,x],49,1] | 这是对 Plus 的参数求值后的结果 |
Plus[50,Times[14,x]] | 对 Plus 的内置定义给出此结果 |
50+14x | 结果像这样打印 |
Wolfram 系统提供了“跟踪”运算过程的各种方法,如"跟踪运算"中所述. 函数 Trace[expr] 提供了一个嵌套列表,其中显示了运算过程中生成的每个子表达式. (请注意,标准运算以深度优先的方式遍历表达式树,因此表达式的最小子部分首先出现在 Trace 的结果中.)
Wolfram 系统应用各种定义的顺序很重要. Wolfram 系统会在应用内置定义之前先应用您给出的定义,这一事实意味着您可以给出覆盖内置定义的定义,如"修改内置函数"中所述.
该表达式使用 ArcSin 的内置定义求值:
您可以自行对 ArcSin 定义. 首先需要删除保护属性:
如"将定义与不同的符号相关联"中所述,您可以将定义与符号相关联(作为上值或下值). Wolfram 系统总是先尝试上值(Upvalue)定义,再尝试下值(Downvalue)定义.
这种排序遵循的是通用策略——在尝试更通用的定义之前尝试特定定义. 通过在应用与函数关联的下值之前应用与参数关联的上值,Wolfram 系统允许定义特殊参数,这些特殊参数会覆盖带有任何参数的函数的常规定义.
Wolfram 系统中的大多数内置函数(例如 Plus)都具有下值. 但是,Wolfram 系统中有些对象具有内置的上值. 例如,代表幂级数的 SeriesData 对象具有与各种数学运算相关的内置上值.
- 您所给出的与 g 相关的定义;
- 与 g 相关的内置定义;
- 您所给出的与 f 相关的定义;
- 与 f 相关的内置定义.
在许多情况下,了解上值先于下值使用这一事实很重要. 比如一个典型的情况,定义合并这样一个操作. 如果您为各种对象提供与合并有关的上值,则这些上值将在此类对象出现时使用. 但是,也可以给出一个一般的合并步骤,用于无特殊对象出现时. 可以将此过程作为合成的下值. 由于下值是在上值之后尝试,因此仅当不存在具有上值的对象时才使用常规过程.
通常,在特定表达式中可以有多个具有上值的对象。 Wolfram 系统首先查看表达式的头部,然后尝试与之关联的所有上值. 然后,它依次查看表达式的每个元素,尝试存在的任何上值. Wolfram 系统首先对明确定义的上值执行此过程,然后对内置的上值执行此过程. 该过程意味着在一系列元素中,与较早元素关联的上值优先于与较后元素关联的上值.
尽管大多数内置的 Wolfram 语言函数都遵循标准的运算过程,但也有一些重要的函数却不是这样. 例如,大多数与程序的构建和执行相关的 Wolfram 语言函数都遵循非标准的运算过程. 通常,这些函数要么从不运算它们的某些参数,要么在自己的控制下以特殊的方式进行运算.
x=y | 不运算左侧 |
If[p,a,b] | |
Do[expr,{n}] | 对 expr 运算 n 次 |
Plot[f,{x,…}] | 用 x 的一系列数值求 f |
Function[{x},body] | 在应用该函数之前不运算 |
当给出一个定义时,如 a=1,Wolfram 语言不会运算出现在左侧的 a. 可以预见,如果对 a 进行了运算,将会有麻烦. 原因是,如果先前已设置了 a=7,则在定义 a=1 中运算 a 会将定义变成无意义的形式 7=1.
在标准运算过程中,将依次运算函数的每个参数. 通过设置属性 HoldFirst、HoldRest 和 HoldAll 可以防止这种情况. 这些属性使 Wolfram 语言以未经运算的形式“保留”特定的参数.
尽管一个函数可能具有一些属性来指定某些参数不被运算,但总可以通过以 Evaluate[arg] 的形式给出参数,来明确地告诉 Wolfram 语言来运算那些参数.
f[Evaluate[arg]] | 立即运算 arg,即使 f 的属性可能指定应保留未运算的形式 |
通过保留其参数,函数可以控制何时运算这些参数. 通过使用 Evaluate,您可以强制对参数进行立即求值,而不是在函数的控制下进行求值. 此功能在许多情况下很有用.
在大多数情况下,我们都希望对提供给 Wolfram 语言的所有表达式进行求值. 但也有时可能希望阻止对某些表达式的求值. 例如,如果要只想要符号式地操作 Wolfram 语言程序的各个部分,则在操作时必须防止对其进行运算.
Hold[expr] 和 HoldForm[expr] 的不同之处在于,以标准的 Wolfram 语言输出格式,Hold 是显式打印的,而HoldForm 不是. 如要查看完整的内部 Wolfram 语言形式,则可以看到这两个函数.
Hold 使表达式保持未运算的形式:
HoldForm 也可以使表达式不被运算,但是在标准 Wolfram 语言输出格式中不可见:
HoldForm 内部仍然存在:
Hold[expr] | 保持 expr 不被运算 |
HoldComplete[expr] | 保持 expr 不被运算,并阻止使用与 expr 相关的上值 |
HoldForm[expr] | 保持 expr 不被运算,并在不使用 HoldForm 的情况下进行打印 |
ReleaseHold[expr] | |
Extract[expr,index,Hold] | 获取部分 expr,并将其用 Hold 封装以阻止被运算 |
提取一部分并立即用 Hold 封装,因此不会被运算:
f[…,Unevaluated[expr],…] | 将未运算的 expr 提供给 f 作为其参数 |
SequenceHold | 不展平显示为参数的 Sequence 对象 |
HoldAllComplete | 将所有参数视为完全惰性 |
通过设置属性 HoldAll,可以阻止 Wolfram 语言运算函数的参数. 但是即使设置了此属性,Wolfram 语言仍将对参数进行一些转换. 通过设置 SequenceHold,可以防止其展平出现在参数中的 Sequence 对象. 通过设置 HoldAllComplete,还可以禁止剔除 Unevaluated,并防止 Wolfram 语言使用它发现的与参数相关联的任何上值.
但是,在某些情况下,您可能希望不对整个模式或部分模式进行运算. 可以通过将这些部分用 HoldPattern 封装来实现此目的. 通常,每当 HoldPattern[patt] 出现在模式中时,出于模式匹配的目的,该格式被视为等同于 patt,但表达式 patt 保持不被运算的状态.
HoldPattern[patt] | 用于模式匹配时等价于 patt,但 patt 保持不被运算的状态 |
HoldPattern 的一种应用是,指定可应用于未运算表达式或以未运算形式保存的表达式的模式.
如上所示,转换规则(例如 lhs->rhs)的左侧通常会立即被运算,因为该规则通常应用于已经求值的表达式. lhs->rhs 的右侧也会立即被运算. 但是,使用延迟规则 lhs:>rhs 时,表达式 rhs 不会被运算.
尽管转换规则的左侧通常会被运算,但对于定义的左侧来说则不然. 出现这种差异的原因如下. 转换规则通常使用 /. 应用于已运算的表达式. 但是,定义是在表达式的运算过程中使用,并应用于尚未完全运算的表达式. 要使用此类表达式,定义的左侧必须保持至少部分未运算的形式.
符号的定义是最简单的情况. 如"非标准运算"中所述,定义左侧的符号不被运算,例如 x=value. 如果 x 之前已被赋值为 y,则如果 x=value 的左侧被运算,它将变成完全不相关的定义 y=value.
这就是为什么出现在定义左侧的函数参数必须通过考虑在表达式求值期间如何使用定义来求值的原因. 如"运算原理"中所述,当 Wolfram 语言运算函数时,它首先运算每个参数,然后尝试查找该函数的定义. 结果,当 Wolfram 语言应用您为函数给出的任何定义时,该函数的参数必须已经被求值. 当所讨论的函数具有指定某些参数不被运算的属性时除外.
symbol=value | 不运算 symbol;运算 value |
symbol:=value | symbol 和 value 均不被运算 |
f[args]=value | 运算 args;左侧作为整体不被运算 |
f[HoldPattern[arg]]=value |
f
[
arg
] 被赋值,而不运算
arg
|
Evaluate[lhs]=value | 左侧被完全运算 |
尽管在大多数情况下,应该运算定义左侧出现的函数参数,但在某些情况下,不希望这种情况发生. 在这种情况下,可以用 HoldPattern 封装不想被运算的部分.
当对 Table[f,{i,imax}] 这样的表达式进行计算时,如“块和局部值”中所述,第一步是使 i 的值成为本地值. 然后运算迭代规范中的极值 imax. 表达式 f 保持未运算的形式,但随着将一系列值赋给 i 而被重复求值. 完成此操作后,将恢复 i 的全局值.
函数 RandomReal[] 在这里分别被运算了四次,因此生成了四个不同的伪随机数:
在大多数情况下,比较方便的做法是,将表达式如 Table[f,{i,imax}] 中的函数 f 保持未运算形式,直到将具体值赋给 i 为止. 如果无法找到适用于任何 i 的 f 的完整符号形式,则尤其如此.
对于 诸如Table[f,{i,imax}] 之类的表达式,如果可以带有任意 i 的 f 的完整符号形式,则通常更有效的方法是先计算该形式然后输入到 Table. 可以使用 Table[Evaluate[f],{i,imax}] 进行此操作.
lhs:=rhs/;test | 仅当 test 的运算结果为 True 时才使用定义 |
If[test,then,else] | |
Which[test1,value1,test2,…] | 依次运算 testi,给出与第一个为 True 关联的值 |
Switch[expr,form1,value1,form2,…] | 将 expr 与每个 formi 进行比较,给出与其匹配的第一个格式关联的值 |
Switch[expr,form1,value1,form2,…,_,def] | 使用 def 作为默认值 |
Piecewise[{{value1,test1},…},def] | 给出与第一个生成 True 的 testi 对应的值 |
当您使用 Wolfram 语言编写程序时,通常会在以下两个选项之间做出选择:创建一个单独的定义(其右侧包含由 If 函数控制的多个分支),或者进行多个定义(每个定义由一个适当的 /; 条件控制. 通过使用多个定义,通常可以生成既更清晰又易于修改的程序.
定义一个具有三个区域的函数. 使用 True 作为第三个检验使其成为默认情况:
这使用 Which 中的第一种情况:
对于 Wolfram 语言这样的符号系统,重要的一点是,您给出的条件可能不会生成 True 或 False. 因此,例如,除非 x 和 y 具有特定值(例如数值),否则条件 x==y 不会生成 True 或 False.
If[test,then,else,unknown] | |
TrueQ[expr] | |
lhs===rhs or SameQ[lhs,rhs] | |
lhs=!=rhs or UnsameQ[lhs,rhs] | |
MatchQ[expr,form] |
lhs===rhs 与 lhs==rhs 之间的主要区别是 === 总是返回 True 或 False,而 == 可以将其输入保持为符号形式,表示符号方程式,如 "方程式" 中所述. 要检验表达式的结构时,通常应使用 ===;如果要检验数学相等性,则应使用 ==. Wolfram 语言模式匹配器有效地使用 === 来确定一个文字表达式何时与另一个文字表达式匹配.
在设置条件时,经常需要使用检验的组合,例如 test1&&test2&&…. 重要的一点是,如果任何 testi 为 False,则这些检验组合的结果将为 False. Wolfram 语言总是依次运算 testi,如果任何 testi 产生 False,则停止.
Do[expr,{i,imax}] | 重复运算 expr,i 以 1 为步长从 1 到 imax 变化 |
Do[expr,{i,imin,imax,di}] | 运算 expr,其中 i 以 di 为步长从 imin 到 imax 变化 |
Do[expr,{i,list}] | 运算 expr,其中 i 的值取自 list |
Do[expr,{n}] | 运算 expr n 次 |
可以在 Do 的内部放置一个过程:
Nest[f,expr,n] | 将 f 应用于 expr n 次 |
FixedPoint[f,expr] | 从 expr 开始,并重复应用 f 直到结果不再更改 |
NestWhile[f,expr,test] |
Do 允许您通过使用迭代变量的不同值多次运算特定表达式来重复操作. 但是,通常您可以使用"重复应用函数"中讨论的函数式编程结构来制作更优雅和更高效的程序. 例如,Nest[f,x,n] 允许您将函数重复应用于表达式.
通过嵌套一个纯函数,您可以得到与上面使用 Do 的示例相同的结果:
FixedPoint 不断应用函数,直到结果不再更改为止:
可以使用 FixedPoint 模拟 Wolfram 语言中的运算过程,或者模拟诸如 expr//.rules 之类的函数操作. FixedPoint 不断运行,直到获得的两个连续结果相同为止. NestWhile 允许您继续操作,直到任意函数不再产生 True 为止.
Throw 和 Catch 提供一种灵活的方式来控制 Wolfram 语言中的运算过程. 基本思想是,每当遇到一个 Throw 时,运算都将停止,并且 Wolfram 语言立即就近返回到适当的外层 Catch.
可以使用 Throw 和 Catch 来转移函数式编程构造的操作,例如允许仅在满足某些条件之前继续运算此类构造. 请注意,如果使用 Throw 停止运算,则所得结果的结构可能与允许运算完成所得到的结果完全不同.
由于没有遇到 Throw,这里的结果与先前相同:
这定义了一个函数,当其参数大于10时将生成 Throw:
这里不会产生 Throw:
在小型程序中,通常以最简单的形式使用 Throw[value] 和 Catch[expr] 就足够了. 但对于大型程序,特别是包含许多单独部分的大型程序,通常最好使用 Throw[value,tag] 和 Catch[expr,form]. 通过将表达式的 tag 和 form 保持在程序特定部分的本地,可以确保 Throw 和 Catch 也仅在该部分中运行.
但这里仅被外层的 Catch 捕获:
可以使用模式来指定特定 Catch 应该捕获的标签:
应该认识到,不需要将 Throw 中出现的标签设为常量; 通常,它可以是任何表达式.
当使用带有 Throw[value,tag] 的 Catch[expr,form] 时,由 Catch 返回的值仅是在 Throw 中给出的表达式value. 但如果使用 Catch[expr,form,f],则 Catch 返回的值将改为 f[value,tag].
Do、Nest 和 FixedPoint 等函数提供了在 Wolfram 语言程序中进行循环的结构式方法,而 Throw 和 Catch 提供了修改这类结构的机会. 但是,有时您可能想要创建甚至从一开始就具有较少结构的循环. 在这种情况下,函数 While 和 For 可能会更方便,它们会重复执行操作,并在指定条件不成立时停止.
循环 While 在条件不成立时停止:
Wolfram 语言中的函数 While 和 For 与 C 语言中的控制结构 while 和 for 相似. 但是请注意,两者之间仍有许多重要的区别. 例如,Wolfram 语言的 For 循环,相对于 C 语言,其逗号和分号的作用相反.
在 Wolfram 语言中,While 和 For 都始终在运算循环主体之前运算循环测试. 一旦循环测试不为 True,则 While 和 For 终止. 因此,仅在循环测试为 True 的情况下才会运算循环的主体.
在 While 或 For 循环中,或通常在任何 Wolfram 语言的过程中,所给出的 Wolfram 语言表达式都是按确定的顺序求值的. 可以认为此序列定义了 Wolfram 语言程序执行中的“控制流”.
函数式编程构造通常涉及非常简单的控制流程. While 和 For 循环总是更复杂,因为它们的设置使控制流取决于作为测试给出的表达式的值. 然而即使在这样的循环中,控制流程通常也不取决于循环主体中给出的表达式的值.
但是,在某些情况下,可能需要构造 Wolfram 语言程序,其中控制流受执行过程或循环主体生成的值的影响. 符合函数式编程思想的一种实现方法是使用 Throw 和 Catch. 但 Wolfram 语言还提供了用于修改控制流的各种函数,这些函数的工作方式与 C 语言类似.
Break[] | 退出最近的封闭循环 |
Continue[] | 转到当前循环的下一步 |
Return[expr] | 从函数返回值 expr |
Goto[name] | 转到当前过程中的元素 Label[name] |
Throw[value] | 返回 value 作为最近的封闭 Catch 的值(非本地返回) |
当参数大于 5 时,将使用该过程中的第一个 Return:
这里没有产生 Throw:
诸如 Continue[] 和 Break[] 之类的函数使您可以将控制权“转移”到 Wolfram 语言程序中循环的开始或结束. 有时,您可能需要将控制权转移到 Wolfram 语言过程中的特定元素. 如果在过程中将 Label 作为元素,则可以使用 Goto 将控制权转移到该元素.
请注意,只有当它指定的 Label 出现在同一 Wolfram 语言过程的元素中时,才能在特定 Wolfram 语言过程中使用 Goto. 通常,Goto 的使用会降低程序中易于理解的结构程度,从而使程序的操作更加难以理解.
这里定义可以进行 Sow 的函数:
Sow[val,tag] | 播撒带有标签的 val 以指示何时收获 |
Sow[val,{tag1,tag2,…}] | 对每一个 tagi 播撒 val |
Reap[expr,form] | 获取其标签匹配 form 的所有值 |
Reap[expr,{form1,form2,…}] | 为每个 formi 分别创建列表 |
Reap[expr,{form1,…},f] | 将 f 应用于每个不同的标签和值列表 |
把提供的任何表达式作为输入,完全运算该表达式,然后返回结果,这是 Wolfram 系统的标准工作方式. 但是如果想要理解 Wolfram 系统的行为,通常不仅要看运算的最终结果,还要看运算过程的中间步骤.
可以在 Trace 中指定任何模式:
但是,典型的 Wolfram 系统程序不仅包含 fac[n] 之类的“函数调用”,还包含其他元素,例如对变量的赋值、控制结构等. 所有这些元素都表示为表达式. 您可以在 Trace 中使用模式来挑选任何类型的 Wolfram 系统程序元素. 因此,举例来说,可以使用类似 k=_ 的模式来挑选对符号 k 的所有赋值.
Trace[expr,form] 可以挑选在 expr 运算过程中随时出现的表达式. 例如,表达式不必直接以您提供的 expr 形式出现. 相反,它们可能会在对函数进行运算的过程中产生,作为 expr 运算的一部分.
Trace 可以监视函数运算的中间步骤,这些函数不仅包括自定义函数,还包括 Wolfram 系统内置的某些函数. 但应该认识到,内置的 Wolfram 系统函数所遵循的中间步骤的具体顺序在细节上取决于它们在 Wolfram 系统特定版本中的实现和优化.
Trace[expr,f[___]] | 显示对函数 f 的所有调用 |
Trace[expr,i=_] | 显示对 i 的赋值 |
Trace[expr,_=_] | 显示所有赋值 |
Trace[expr,Message[___]] | 显示生成的消息 |
Trace 的一些使用方式.
函数 Trace 返回一个列表,该列表表示 Wolfram 系统计算的“历史”. 列表中的表达式按在计算过程中生成的顺序给出. 在大多数情况下,Trace 返回的列表具有嵌套结构,该嵌套结构表示计算的“结构”.
基本思想是,Trace 返回的列表中每个子列表都代表特定 Wolfram 系统表达式的“运算链”. 该链的元素对应于同一表达式的不同形式. 然而,通常对一个表达式的求值需要对许多其他表达式(通常是子表达式)求值. 每个辅助运算由 Trace 返回的结构中的子列表表示.
在 Wolfram 系统表达式的求值过程中,有两种基本方法可以要求进行辅助求值. 第一种方法是表达式可以包含子表达式,每个子表达式都必须求值. 第二种方式是,可以存在表达式的运算规则,这些规则涉及的其他表达式自身必须求值. 两种辅助运算均由 Trace 返回的结构中的子列表表示.
在运算任何 Wolfram 系统表达式的过程中,每一步都可以被视为应用特定转换规则的结果. 如"将定义与不同的符号相关联"中所述,Wolfram 系统知道的所有规则都与特定的符号或“标签”相关联. 使用 Trace[expr,f] 可以查看 expr 运算中的所有步骤,其所用的转换规则与符号 f 相关联. 在这种情况下,Trace 不仅会给出应用每个规则的表达式,而且还会给出应用规则的结果.
Trace[expr,form,TraceOn->oform] | 仅在匹配 oform 的形式内打开追踪 |
Trace[expr,form,TraceOff->oform] | 在任何匹配 oform 的形式内关闭追踪 |
通过设定选项 TraceOn->oform,可以指定仅在运算与 oform 匹配的形式时才进行追踪. 同样,通过设置 TraceOff->oform,可以指定在运算与 oform 匹配的形式期间应关闭追踪.
Trace[expr,lhs->rhs] | 查找在 expr 运算期间出现的所有与 lhs 匹配的表达式,并用 rhs 替换它们 |
Wolfram 系统 Trace 函数的强大之处在于,它返回的对象基本上是标准的 Wolfram 系统表达式,可以使用其他Wolfram 系统函数进行操作. 但要注意的重要一点是,Trace 会把使用 HoldForm 生成的列表中出现的所有表达式封装起来,以防止对其进行求值. HoldForm 不会以标准 Wolfram 系统输出格式显示,但仍存在于表达式的内部结构中.
这些表达式用 HoldForm 封装,以防止对其进行求值:
在标准 Wolfram 系统输出格式中,有时很难区分哪些列表与 Trace 返回的结构相关联,哪些是要运算的表达式:
对于复杂的运算,Trace 返回的列表结构可能非常复杂. 当您使用 Trace[expr,form] 时,Trace 仅将那些与模式 form 匹配的表达式作为元素包括在列表中. 但无论您提供哪种模式,列表的嵌套结构都保持不变.
可以设置选项 TraceDepth->n 来告诉 Trace 仅包含嵌套深度最多为 n 层的列表. 这样通常可以选择计算的“重要步骤”,而无需查看细节. 请注意,通过设置 TraceDepth 或 TraceOff ,可以避免查看计算中的许多步骤,从而显著加快了该计算的 Trace 操作.
Trace[expr,form,TraceDepth->n] | 追踪对 expr 的运算,而忽略导致列表嵌套深度在 n 层以上的步骤 |
使用 Trace[expr,form] 时,将获得与在 expr 运算过程中生成的 form 匹配的所有表达式的列表. 有时不仅需要查看这些表达式,而且需要查看它们的运算结果. 这可以通过在 Trace 中设置选项 TraceForward->True 来执行此操作.
使用Trace[expr,form] 挑出的表达式通常位于运算链的中间. 通过设置 TraceForward->True 告诉 Trace 还包括在运算链末尾获得的表达式. 如果设置 TraceForward->All,Trace 将包括在运算链上表达式匹配 form 之后出现的所有表达式.
通过设置选项 TraceForward,可以有效查看运算过程中特定形式表达式所发生的情况. 但是,有时您并不希望弄清特定表达式会发生什么,而是想知道该表达式是如何生成的. 这可以通过设置选项 TraceBackward 来实现. TraceBackward 的作用是显示出现在运算链上特定表达式形式之前的内容.
TraceForward 和 TraceBackward 允许您向前或向后查看特定运算链. 有时,您可能还需要查看特定运算链所在的运算链. 这可以使用 TraceAbove->True 实现. 如果设置选项 TraceAbove->True,则 Trace 将在所有相关运算链中包含初始和最终表达式. 通过设置 TraceAbove->All,Trace 包含所有这些运算链中的所有表达式.
Trace[expr,form,opts] | 使用指定的选项跟踪 expr 的运算 |
TraceForward->True | 将最终表达式包括在含有 form 的运算链中 |
TraceForward->All | 包括运算链中所有在 form 之后的表达式 |
TraceBackward->True | 包括运算链中含有 form 的第一个表达式 |
TraceBackward->All | 包括运算链中在 form 之前的所有表达式 |
TraceAbove->True | 包括所有运算链中含有包括 form 的链的第一个和最后一个表达式 |
TraceAbove->All | 包括所有运算链中含有包括 form 的链的所有表达式 |
Trace[expr,…] 的基本工作方式是截取对 expr 求值时遇到的每个表达式,然后使用各种标准来确定是否应记录该表达式. 通常 Trace 仅在计算函数参数之后才截取表达式. 通过设置 TraceOriginal->True,可以使 Trace 在计算函数参数之前也查看表达式.
Trace 生成的列表结构通常仅包含构成非平凡运算链中步骤的表达式. 因此通常不包括运算自身的单个符号. 但是,如果设置 TraceOriginal->True,则 Trace 会绝对查看运算过程中涉及的每个表达式,包括那些具有平凡运算链的表达式.
在这种情况下,Trace 绝对包括所有表达式,甚至包括那些具有平凡运算链的表达式:
选项名称 | 默认值 | |
TraceForward | False | 是否显示运算链中在 form 之后的表达式 |
TraceBackward | False | 是否显示运算链中在 form 之前的表达式 |
TraceAbove | False | 是否显示导致运算链包含 form 的运算链 |
TraceOriginal | False | 是否在运算表达式头部和参数之前先查看表达式 |
Trace 的其他选项.
在使用 Trace 研究程序的执行情况时,存在一个如何处理程序中局部变量的问题. 如"模块的工作原理"中所述,Wolfram 系统作用域构造(例如 Module)会使用新名称创建符号来表示局部变量. 因此,即使在程序的原始代码中调用了变量 x,在执行程序时也可以将变量重命名为 x$nnn.
Trace[expr,form] 设置为默认情况下,以 form 形式出现的符号 x 会匹配所有在执行 expr 时出现的形式名称为 x$nnn 的符号. 例如可以使用 Trace[expr,x=_] 追踪对在原始程序中被命名为 x 的所有变量(局部变量和全局变量)的赋值.
Trace[expr,form,MatchLocalNames->False] | |
包括执行与 form 匹配的 expr 的所有步骤,不允许替换局部变量名称 |
函数 Trace 执行完整的计算,然后返回代表计算历史的结构. 但是,有时在进行过程中查看计算轨迹很有用,特别是对非常长的计算而言. TracePrint 函数的工作原理与 Trace 类似,不同之处在于它会在遇到表达式时打印表达式,而不是保存所有表达式以创建列表结构.
由 TracePrint 打印的表达式序列与 Trace 返回的列表结构中给出的表达式序列相对应. TracePrint 输出中的缩进对应于 Trace 中列表结构的嵌套. 可以在 TracePrint 中使用 Trace 选项 TraceOn、TraceOff 和 TraceForward. 但是,由于 TracePrint 随过程产生输出,因此不支持选项 TraceBackward. 此外,TracePrint 的设置使得 TraceOriginal 始终为 True.
Trace[expr,…] | 追踪 expr 的运算,返回包含遇到的表达式的列表结构 |
TracePrint[expr,…] | 追踪 expr 的运算,打印遇到的表达式 |
TraceDialog[expr,…] | 追踪 expr 的运算,遇到每个指定的表达式时启动对话框 |
TraceScan[f,expr,…] |
函数 TraceDialog 有效地使您可以停在计算中间,并与当时存在的 Wolfram 系统环境进行交互. 例如,您可以在计算中找到中间变量的值,甚至重置这些值. 当然,还有许多微妙之处需要注意,主要与模式和模块变量有关.
TraceDialog 的作用是在一系列表达式上调用函数 Dialog. 函数 Dialog 在"对话框"中有详细讨论. 调用 Dialog 时,实际上是在使用自己的输入和输出行序列来启动辅助 Wolfram 系统会话.
在所有计算中,Wolfram 系统都会维护一个运算堆栈(Evaluation Stack),其中包含当前正在运算的表达式. 可以使用函数 Stack 查看堆栈. 这意味着如果 Wolfram 系统在计算过程中被中断 ,则可以使用 Stack 了解 Wolfram 系统在做什么.
Stack[] 给出与调用时正在执行的运算相关联的标签:
通常,可以把运算堆栈理解为显示哪些函数调用哪些其他函数来到 Wolfram 系统在计算中所处的位置. 表达式序列对应于 Trace 返回的连续嵌套列表中的第一个元素,且选项 TraceAbove 设置为 True.
在最简单的情况下,Wolfram 系统运算堆栈的设置是记录当前正在运算的所有表达式. 但在某些情况下,这可能会带来不便. 例如,执行 Print[Stack[]] 将始终显示一个堆栈,其中 Print 作为最后一个函数.
StackInhibit[expr] | 在不修改堆栈的情况下运算 expr |
StackBegin[expr] | 用新堆栈运算 expr |
StackComplete[expr] | 使用堆栈中包含的运算链中的中间表达式运算 expr |
通过使用 StackInhibit 和 StackBegin,可以控制运算过程的哪些部分被记录在堆栈中. StackBegin[expr] 开启一个新的堆栈来运算 expr. 这意味着在运算 expr 的过程中,堆栈不包含 StackBegin 之外的任何内容. TraceDialog[expr,…] 等函数在开始运算 expr 之前会调用 StackBegin,以使堆栈显示如何运算 expr ,而不是如何调用 TraceDialog.
StackComplete[expr] 的实际作用是将当前正在运算的每个表达式的完整运算链保持在堆栈中. 在这种情况下,堆栈对应于从 Trace 获得的表达式序列,并具有选项 TraceBackward->All 以及 TraceAbove->True.
Wolfram 语言在运算表达式时,遵循的一般原则是不断应用转换规则,直到表达式不再更改为止. 这意味着如果进行 x=x+1 之类的赋值,Wolfram 语言会陷入无限循环. 而实际上,Wolfram 语言将在确定的步骤后停止,步骤数由全局变量 $RecursionLimit 的值决定. 当然,您始终可以通过显式地提前中断 Wolfram 语言.
此赋值可能导致无限循环. Wolfram语言在达到 $RecursionLimit 确定的步骤数后停止:
当 Wolfram 语言未完成运算而停止时,将返回保留的结果. 可以通过显式调用 ReleaseHold 来继续运算:
$RecursionLimit | 运算堆栈的最大深度 |
$IterationLimit | 运算链的最大长度 |
这是一个循环定义,其运算通过 $IterationLimit 停止:
变量 $RecursionLimit 和 $IterationLimit 控制 Wolfram 语言中运算可能无限进行的两种基本方式. $RecursionLimit 限制评估堆栈的最大深度,也就是限制 Trace 生成的列表结构中将出现的最大嵌套深度. $IterationLimit 限制任何特定评估链的最大长度,也就是限制 Trace 生成的结构中任何单个列表的最大长度.
默认情况下,$RecursionLimit 和 $IterationLimit 设置的值适用于大多数计算和大多数计算机系统. 您可以将这些变量重置为任何整数(高于一个下限)或 Infinity. 请注意,如“内存管理”中所述,在大多数计算机系统上切勿设置 $RecursionLimit=Infinity.
在这种情况下,不会建立任何复杂的结构,并且计算将被 $IterationLimit 停止:
无限循环不仅会占用时间,而且还会占用计算机内存,认识到这一点很重要. 受 $IterationLimit 限制的计算通常不会产生大型的中间结构. 但是受 $RecursionLimit 限制的对象通常则会. 在许多情况下,生成结构的大小是 $RecursionLimit 值的线性函数. 但在某些情况下,使用 $RecursionLimit 会使大小呈指数倍增长,甚至更糟.
像 x=x+1 这样的赋值显然是循环的. 但在设置更复杂的递归定义时,要确保递归终止,不会无限循环下去可能会比较困难. 要检查的主要内容是变换规则的右侧是否始终与左侧不同. 这确保了运算将总是“取得进展”,并且Wolfram 语言不会只是将相同的转换规则一遍遍地重复应用于相同的表达式.
最棘手的情况出现在规则依赖于复杂的 /; 条件时(参阅“对模式施加约束”). 一种特别尴尬的情况是当条件涉及“全局变量”时. 由于表达式未更改,Wolfram 语言认为运算已完成. 但是某些其他操作可能会无意中更改了全局变量的值,从而应该产生新的运算结果. 避免出现这种情况的最好方法是不要在 /; 条件中使用全局变量. 如果所有其他方法均失败,则可以键入 Update[s] 告知 Wolfram 语言更新所有涉及 s 的表达式. Update[] 告诉 Wolfram 语言必须更新所有表达式.
Interrupt[] | 中断计算 |
Abort[] | 终止计算 |
CheckAbort[expr,failexpr] | 运算 expr 并返回结果,如果出现中止则返回 failexpr |
AbortProtect[expr] | 运算 expr,掩盖中止的影响,直到运算完成 |
如果中止发生在 Wolfram 语言表达式的运算过程中,Wolfram 语言通常会放弃对整个表达式的求值,并返回值 $Aborted.
但是,您可以使用函数 CheckAbort “捕获”中止行为. 如果在 CheckAbort[expr,failexpr] 中的 expr 运算期间发生中止,则 CheckAbort 返回 failexpr,但中止不会继续传播. Dialog 之类的函数以这种方式使用 CheckAbort 来包含中止的效果.
当使用 Wolfram 语言构造复杂的程序时,有时可能希望保证程序中的特定代码段不被中止,无论是以交互方式还是通过调用 Abort. 函数 AbortProtect 允许您运算表达式,保存所有异常中止,直到表达式运算完成.
CheckAbort 看到中止,但不进一步传播它:
即使在 AbortProtect 内部,CheckAbort 也会看到发生的任何异常中止,并将返回适当的 failexpr. 除非该 failexpr 本身包含 Abort[],否则异常中止将被 CheckAbort “吸收”.
对于定义 f[x_]:=x Sin[x],Wolfram 语言将以对任何 x 均可求值的方式对表达式 x Sin[x] 进行存储. 然后,当给出 x 的特定值时,Wolfram 语言将该值替换为 x Sin[x],并运算结果. 无论给 x 的值是数字、列表、代数对象,还是任何其他类型的表达式,Wolfram 语言用来执行此运算的内部代码都可以很好地工作.
在 Wolfram 语言中可以使用 Compile 构造编译函数. 这些函数在运算 Wolfram 语言表达式时假定出现的所有参数都是数字(或逻辑变量). Compile[{x1,x2,…},expr] 接受表达式 expr 并返回一个“编译函数”,当给定参数 时,该函数将对该表达式求值.
Compile[{x1,x2,…},expr] | 创建一个编译函数,该函数对 xi 的数值运算 expr |
创建编译函数来运算 x Sin[x]:
对于像 x Sin[x] 这样的简单表达式,普通函数和编译函数的执行速度通常几乎没有差异. 但是,随着所涉及表达式的大小增加,编译的优势也随之增强. 对于大型表达式,编译可以使执行速度提高20倍.
对于含有大量简单函数(例如算术函数)的表达式,编译的影响最大. 对于更复杂的函数(BesselK 或 Eigenvalues),大部分计算时间都花在了执行 Wolfram 语言的内部算法上,而编译对这些算法没有影响.
这将创建一个编译函数,用于查找十阶勒让德多项式的值. Evaluate 告诉 Wolfram 语言在进行编译之前显式构造多项式:
虽然可以使用编译来对所写的数值函数加速,但仍应尽可能尝试使用内置的 Wolfram 语言函数. 内置函数通常比自行创建的已编译 Wolfram 语言程序运行速度更快. 此外,内置函数通常使用更广泛的算法,并能对数值精度进行更完全的控制,等等.
应该认识到,内置的 Wolfram 语言函数本身经常会使用 Compile. 例如在默认情况下,NIntegrate 会自动对要求积分的表达式使用 Compile,类似地,Plot 和 Plot3D 等函数会自动对要求绘图的表达式使用 Compile. 使用 Compile 的内置函数通常具有选项 Compiled. 设置 Compiled->False 告诉函数不使用 Compile.
Compile[{{x1,t1},{x2,t2},…},expr] | 编译 expr,假定 xi 的类型为 ti |
Compile[{{x1,t1,n1},{x2,t2,n2},…},expr] | |
编译 expr,假定 xi 是类型 ti、秩为 ni 的对象数组 | |
Compile[vars,expr,{{p1,pt1},…}] | 编译 expr,假定与 pi 匹配的子表达式的类型为 pti |
_Integer | 机器尺寸的整数 |
_Real | 机器精度的近似实数 |
_Complex | 机器精度的近似复数 |
TrueFalse | 逻辑变量 |
Compile 通过对运算给定表达式的过程中出现的对象类型进行假设来工作. 默认假设是表达式中的所有变量均为近似实数.
Compile 也允许使用整数、复数和逻辑变量(True 或 False)以及数字数组. 可以通过提供仅匹配具有该类型的值的模式来指定特定变量类型. 例如可以使用模式 _Integer 指定整数类型. 类似地,可以使用 TrueFalse 来指定必须为 True 或 False 的逻辑变量.
Compile 处理的类型本质上对应于计算机通常在机器代码级别处理的类型. 例如,Compile 可以处理具有机器精度的近似实数,但不能处理任意精度的数. 此外,如果指定特定变量为整数,则 Compile 仅对“机器尺寸”的整数(通常在 之间)的情况生成代码.
当要求编译的表达式仅涉及标准算术和逻辑运算时,Compile 可以简单地根据输入变量的类型推导在每一步生成的对象的类型. 但是,如果调用其他函数,则 Compile 通常不知道返回的值是哪种类型. 如果没有另外指定,则Compile 假定任何其他函数都产生一个近似的实数值. 但是,也可以给出一个明确的模式列表,为匹配特定模式的表达式指定要采用的类型.
Compile 的思想是创建一个函数,来针对某些类型的参数进行优化. 尽管如此,Compile 仍应设置为使其所创建的函数适应于给定的任何参数类型. 当无法使用优化时,将对标准 Wolfram 语言表达式进行求值得到函数的值.
由 Compile 生成的编译代码不仅必须对您将提供的参数类型进行假设,还必须假设代码执行期间出现的所有对象类型. 有时,这些类型取决于所指定参数的实际值. 例如,如果 x 不为负,则 Sqrt[x] 对于实数 x 生成实数结果,但如果 x 为负,则生成复数.
Compile 始终对特定函数返回的类型做出确定的假设. 如果在特定情况下执行由 Compile 生成的代码时该假设无效,则 Wolfram 语言将放弃已编译的代码,并运算普通 Wolfram 语言表达式以得到结果.