表达式的运算

运算原则
Wolfram 语言执行的基本操作是运算. 每当您输入表达式时,Wolfram 语言都会运算该表达式,然后返回结果.
Wolfram 语言中的运算通过应用一系列定义来起作用. 这些定义可以是明确输入的定义,也可以是 Wolfram 语言中内置的定义.
例如,Wolfram 语言使用内置的整数加和过程运算表达式 6+7. 相似地,Wolfram 语言使用内置的简化过程来运算代数表达式 x-3x+1. 如果定义了 x=5,Wolfram 语言将使用此定义将 x-3x+1 简化至 -9.
表达式运算可能是 Wolfram 语言中最重要的两个概念. "表达式" 讨论如何使用表达式将 Wolfram 语言处理的所有不同类型的对象以统一的方式表示. 本教程介绍如何以统一的方式将 Wolfram 语言可执行的所有操作视为运算示例.
计算
5+611
简化
x-3x+11-2x
执行
x=55
运算的一些解释.
Wolfram 语言是一个无限运算的系统. 当输入表达式时,Wolfram 语言将继续使用它知道的定义,直到得到不适用任何定义的结果为止.
这里根据 x2 定义 x1,然后定义 x2
如果要求r x1,Wolfram 语言将使用它所知道的所有定义为您提供结果:
这是递归定义,其中阶乘函数是根据自身定义的:
如果要求 fac[10],则 Wolfram 语言将不断应用您提供的定义,直到结果不再更改为止:
Wolfram 语言在使用了它所知道的所有定义后,就会给出其所获得的表达式作为结果. 有时结果可能是诸如数字之类的对象. 但是通常结果是一个表达式,其中某些对象以符号形式表示.
Wolfram 语言使用内置定义来简化求和,但是 f[3] 的定义未知,因此保留其符号形式:
Wolfram 语言遵循应用定义的原则,直到结果不再改变为止. 这意味着,如果您将 Wolfram 语言给出的最终结果作为 Wolfram 语言输入输入,您将再次获得相同的结果. (在控制无限运算中讨论了一些微妙的情况,这类情况不会出现.)
如果输入 Wolfram 语言的结果,则会再次得到相同的表达式:
在任何给定的时刻,Wolfram 语言只能使用那一时刻所知道的定义. 但如果以后添加更多定义,Wolfram 语言也将可以使用. 在这种情况下,从 Wolfram 语言获得的结果可能会更改.
这是函数 f 的新定义:
使用新定义,得到的结果可以改变:
运算的最简单例子包括使用定义如 f[x_]=x^2 将一个表达式直接转换为另一个表达式等. 运算也是执行用 Wolfram 语言编写的程序的过程. 因此,如果您有一个由一组 Wolfram 语言表达式组成的程序,其中一些表达式可能表示条件和循环,则该程序的执行将与这些表达式的运算相对应. 有时,运算过程可能涉及多次运算一个特定的表达式,例如循环.
表达式 Print[zzzz] 在运算 Do 表达式的过程中被运算了三遍:
将表达式还原为标准形式
Wolfram 语言中的内置函数以多种方式运行. 但是有一种重要的方法是许多数学函数共享的:它们的设置是为了将数学表达式的类别简化为标准形式.
例如,Plus 函数的内置定义是把各项的和写成标准的去括号形式. 加法结合律意味着像 (a+b)+ca+(b+c)a+b+c 这样的表达式都是等价的. 但是出于种种目的,将所有这些形式简化为一个标准形式 a+b+c 会提供很多方便. Plus 的内置定义就是由此而设置的.
通过 Plus 的内置定义,此表达式可简化为标准的去括号形式:
Wolfram 语言每遇到一个函数符合结合律,就会尝试去括号(或去除该函数的嵌套调用),以使该函数成为标准的展平形式.
像加法这样的函数不仅符合结合律,而且符合交换律,这意味着像 a+c+ba+b+c 这样各项顺序不同的表达式是相等的. Wolfram 语言再次尝试将所有这样的表达式变为标准形式. 它选择的标准形式中所有项都采用确定顺序,大致是按字母顺序.
Wolfram 语言将该和式中的各项按标准顺序排序:
展平(可结合)
等价于 ,等等.
无需(可交换)
等价于 ,等等.
Wolfram 语言将某些函数简化为标准形式所用的两个重要属性.
将表达式尽量转成标准形式有多种原因. 最重要的是,如果两个表达式确实是标准形式,则它们是否相等就显而易见了.
当两个和式以标准顺序排列时,它们立即被视为相等,因此两个 f 抵消,结果为 0
可以想象,通过检验每个和的所有可能排序可以确定 a+c+b 是否等于 c+a+b. 但显然,简单地将两个和都化为标准形式是一个更有效的过程.
有人可能认为,Wolfram 语言应该以某种方式将所有数学表达式自动简化为一种单一的标准规范形式. 但是,除非是最简单的表达式类型,否则很容易明白我们并不希望将所有不同用途的表达式都使用某种相同的标准形式.
例如,多项式有两种明显的标准形式,各自适用于不同的目的. 多项式的第一种标准形式是各项的简单加和,在Wolfram 语言中通过应用函数 Expand 生成. 如果需要进行多项式的加减,则此标准形式最合适.
另一种适应于多项式的标准形式则是通过应用 Factor,将任何多项式写为不可约因子的乘积. 如果要进行除法运算,则这种规范形式非常有用.
从某种意义上说,展开形式和分解形式都是很好的多项式标准形式. 使用哪一个仅取决于使用它的目的. 因此,Wolfram 语言不会对多项式自动采纳其中任何一种形式. 相反,它为您提供了诸如 ExpandFactor 之类的函数,使您可以明确地将多项式转换为所需的形式.
这是两个数学上等价的多项式:
仅需通过应用 Expand,就可以将它们都写成展开形式. 在这种形式下,多项式的等价性一目了然:
也可以将它们转换为分解形式,看到两个多项式相等:
尽管我们并不总是希望将表达式简化为相同的标准格式,但有时可能想知道是否所有表达式至少有可能简化为某种标准形式.
已经有计算数学理论的基本结果表明,实际上并非总是如此. 我们无法保证通过任何有限的转换序列将两个任意选择的表达式转换为一种标准形式.
从某种意义上说,这个结论并不特别令人惊讶. 假如所有数学表达式都可以简化为一种标准形式,那么判断两个表达式是否相等就易如反掌了,而这样的话,数学上的很多难题也就不存在了,因为它们实质上都可以表述为表达式相等性的问题. 这一事实表明,这实际上是很难以实现的.
属性
f[x_]=x^2 之类的定义指定函数的. 但是,有时您需要指定函数的常规属性,而不必给出明确的值.
Wolfram 语言提供了一系列属性,用于指定函数的各种属性. 例如,可以使用属性 Flat 来指定某一特定函数,以便嵌套调用自动被展平,并且其行为就像符合结合律.
这里将属性 Flat 赋给函数 f
现在 f 的行为和展平或可结合函数一样,因此嵌套调用会自动展平:
Flat 这样的属性不仅会影响运算,还会影响模式匹配等操作. 如果为函数提供定义或转换规则,则必须确保首先指定函数的属性.
这是对展平函数 f 的定义:
由于 f 是展平的,因此该定义会自动应用于参数的每个子序列:
Attributes[f]
给出 f 的属性
Attributes[f]={attr1,attr2,}
设置 f 的属性
Attributes[f]={}
f 设置为没有属性
SetAttributes[f,attr]
attr 添加至 f 的属性
ClearAttributes[f,attr]
attrf 的属性删除
操纵符号的属性.
这将显示赋给 f 的属性:
这将删除赋给 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
Wolfram语言中符号属性的完整列表.
这是内置函数 Plus 的属性:
Wolfram 语言中内置数学函数的一个重要属性是 Listable 属性. 此属性指定函数应该自动分布还是线性作用于作为参数的列表上. 这意味着函数会有效地单独应用于作为参数的列表中的每个元素.
内置 Log 函数具有属性 Listable
这里定义函数 p 是可列的:
现在,p 自动线性作用于作为其自变量出现的列表上:
可在 Wolfram 语言中指定给函数的许多属性会直接影响这些函数的运算. 但是,某些属性仅影响函数处理的其他方面. 例如,属性 OneIdentity 仅影响模式匹配,如"平面和无序函数"中所述. 同样,属性 Constant 仅与微分以及依赖于微分的运算有关.
属性 Protected 影响赋值. Wolfram 语言不允许您进行任何与带有此属性的符号关联的定义. "修改内置函数"中讨论的 ProtectUnprotect 函数可以用作 SetAttributesClearAttributes 的替代方法,以设置和清除此属性. 如"修改内置函数"中所述,大多数内置 Wolfram 语言对象初始都受到保护,因此您不会错误地为其定义.
定义函数 g
g 设置 Protected 属性;
现在,您无法修改 g 的定义;
通常,您可以通过键入 ?f 或使用各种内置的 Wolfram 语言函数来查看对特定符号所做的定义. 但是,如果设置了属性 ReadProtected,则 Wolfram 语言将不允许您查看特定符号的定义. 尽管如此,它将继续在执行运算时使用这些定义.
尽管不能修改它,但仍然可以查看 g 的定义:
这将为 g 设置 ReadProtected 属性:
现在,您无法再读取 g 的定义:
SetAttributesClearAttributes 这样的函数通常允许您任意修改符号的属性. 但一旦对符号设置了 Locked 属性,则 Wolfram 语言将不允许您在 Wolfram 系统会话的剩余时间修改该符号的属性. 在使用 ProtectedReadProtected 之外,还使用 Locked 属性,可以使用户无法修改或读取定义.
Clear[f]
删除 f 的值,但不删除属性
ClearAll[f]
删除 f 的值和属性
清除值和属性.
这会清除上面赋予了属性 Listablep 的值和属性:
现在 p 不再可列:
通过定义函数的属性,可以指定 Wolfram 语言在该函数出现时应采用的属性. 但是往往这些属性只在特定实例中采用. 在这种情况下,最好不要使用属性,而是调用特定的函数来实现与属性关联的转换.
通过显式调用 Thread,可以在 p 可列时实现自动转换:
OrderlessSort[f[args]]
FlatFlatten[f[args]]
ListableThread[f[args]]
ConstantDt[expr,Constants->f]
执行与某些属性关联的转换的函数.
Wolfram 语言中的属性只能为单个符号永久定义. 然而,Wolfram 语言还允许设置纯函数,其行为就像自带属性一样.
Function[vars,body,{attr1,}]
带有属性 attr1, 的纯函数
带有属性的纯函数.
此纯函数将 p 应用于整个列表:
通过添加属性 Listable,该函数将在应用 p 之前分布在列表的元素上:
标准运算过程
这里介绍了 Wolfram 系统用来运算表达式的标准过程. 此过程为大多数表达式所遵循. 但是有些表达式,例如用于表示 Wolfram 系统程序和控制结构的表达式,是以非标准的方式进行运算的.
在标准运算过程中,Wolfram 系统首先运算表达式的头部,然后运算表达式的每个元素. 这些元素通常本身就是表达式,递归地应用相同的运算过程.
这三个 Print 函数依次求值,每次打印其参数,然后返回值 Null
将符号 ps 赋给 Plus
头部 ps 首先被运算,因此该表达式的行为和求各项总和一样:
Wolfram 系统一旦运算了表达式的头部,就会查看该头部是否是具有属性的符号. 如果符号具有 OrderlessFlatListable属性,则 Wolfram 系统在运算表达式的元素之后,将立即执行与这些属性关联的转换.
标准运算过程的下一步是将 Wolfram 系统所知道的定义用于正在运算的表达式. Wolfram 系统首先尝试使用您创建的定义,如果没有适用的定义,再尝试使用内置定义.
如果 Wolfram 系统找到适用的定义,它将对表达式执行相应的转换. 结果是另一个表达式,必须根据标准运算过程对其进行运算.
运算表达式的头部.
依次运算每个元素.
应用与属性 OrderlessListableFlat 关联的转换.
应用您给出的定义.
应用内置定义.
运算结果.
标准运算过程.
"运算原则"中所述,Wolfram系统遵循以下原则:对每个表达式进行运算,直到没有进一步的定义适用为止. 这意味着 Wolfram 系统必须继续重新运算结果,直到得到一个在运算过程中保持不变的表达式为止.
这是一个示例,显示了标准运算过程如何作用于一个简单的表达式. 假设 a=7.
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 系统中一个简单的运算示例.
Wolfram 系统提供了跟踪运算过程的各种方法,如"跟踪运算"中所述. 函数 Trace[expr] 提供了一个嵌套列表,其中显示了运算过程中生成的每个子表达式. (请注意,标准运算以深度优先的方式遍历表达式树,因此表达式的最小子部分首先出现在 Trace 的结果中.)
首先设置 a7
这给出了在表达式求值期间生成的所有子表达式的嵌套列表:
Wolfram 系统应用各种定义的顺序很重要. Wolfram 系统会在应用内置定义之前先应用您给出的定义,这一事实意味着您可以给出覆盖内置定义的定义,如"修改内置函数"中所述.
该表达式使用 ArcSin 的内置定义求值:
您可以自行对 ArcSin 定义. 首先需要删除保护属性:
您的定义在内置定义之前使用:
"将定义与不同的符号相关联"中所述,您可以将定义与符号相关联(作为上值或下值). Wolfram 系统总是先尝试上值(Upvalue)定义,再尝试下值(Downvalue)定义.
如果是一个像 f[g[x]] 这样的表达式,则通常可以应用两组定义:与 f 相关联的下值和与 g 相关联的上值。 Wolfram 系统先尝试与 g 关联的定义,再尝试与 f 关联的定义.
这种排序遵循的是通用策略在尝试更通用的定义之前尝试特定定义. 通过在应用与函数关联的下值之前应用与参数关联的上值,Wolfram 系统允许定义特殊参数,这些特殊参数会覆盖带有任何参数的函数的常规定义.
f[g[x_]] 定义与 f 相关联的规则:
f[g[x_]] 定义与 g 相关联的规则:
在尝试与 f 关联的规则之前,先尝试与 g 相关的规则:
如果删除了与 g 关联的规则,则使用与 f 关联的规则:
在表达式 f[g[x]] 中,与 g 相关联的定义先于与 f 相关联的定义应用.
定义的应用顺序.
Wolfram 系统中的大多数内置函数(例如 Plus)都具有下值. 但是,Wolfram 系统中有些对象具有内置的上值. 例如,代表幂级数的 SeriesData 对象具有与各种数学运算相关的内置上值.
对于像 f[g[x]] 这样的表达式,在标准运算过程中尝试的完整定义序列为:
在许多情况下,了解上值先于下值使用这一事实很重要. 比如一个典型的情况,定义合并这样一个操作. 如果您为各种对象提供与合并有关的上值,则这些上值将在此类对象出现时使用. 但是,也可以给出一个一般的合并步骤,用于无特殊对象出现时. 可以将此过程作为合成的下值. 由于下值是在上值之后尝试,因此仅当不存在具有上值的对象时才使用常规过程.
这是与 q 相关联的定义,用于组成q 个对象
这是与 comp 相关联的一般合并规则:
如果合并两个 q 对象,使用与 q 相关的规则:
如果合并 r 个对象,则使用与 comp 相关联的一般规则:
通常,在特定表达式中可以有多个具有上值的对象。 Wolfram 系统首先查看表达式的头部,然后尝试与之关联的所有上值. 然后,它依次查看表达式的每个元素,尝试存在的任何上值. Wolfram 系统首先对明确定义的上值执行此过程,然后对内置的上值执行此过程. 该过程意味着在一系列元素中,与较早元素关联的上值优先于与较后元素关联的上值.
定义 p 相对于 c 的上值:
定义 q 的上值:
使用哪个上值取决于哪个先出现在 c 的参数序列中:
非标准运算
尽管大多数内置的 Wolfram 语言函数都遵循标准的运算过程,但也有一些重要的函数却不是这样. 例如,大多数与程序的构建和执行相关的 Wolfram 语言函数都遵循非标准的运算过程. 通常,这些函数要么从不运算它们的某些参数,要么在自己的控制下以特殊的方式进行运算.
x=y
不运算左侧
If[p,a,b]
如果 pTrue,则运算 a,如果为 False 则运算 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.
在标准运算过程中,将依次运算函数的每个参数. 通过设置属性 HoldFirstHoldRestHoldAll 可以防止这种情况. 这些属性使 Wolfram 语言以未经运算的形式保留特定的参数.
HoldFirst
不运算第一个参数
HoldRest
仅运算第一个参数
HoldAll
不运算任何参数
用于以未计算形式保存函数参数的属性.
通过标准运算过程,将运算函数的所有参数:
将属性 HoldFirst 赋给 h
现在,h 的第一个参数保持不被运算的形式:
当这样使用 h 的第一个参数时,它将被求值:
Set 这样的内置函数带有诸如 HoldFirst 之类的属性:
尽管一个函数可能具有一些属性来指定某些参数不被运算,但总可以通过以 Evaluate[arg] 的形式给出参数,来明确地告诉 Wolfram 语言来运算那些参数.
Evaluate 有效地覆盖 HoldFirst 属性,并导致对第一个参数求值:
f[Evaluate[arg]]
立即运算 arg,即使 f 的属性可能指定应保留未运算的形式
强制运算函数参数.
通过保留其参数,函数可以控制何时运算这些参数. 通过使用 Evaluate,您可以强制对参数进行立即求值,而不是在函数的控制下进行求值. 此功能在许多情况下很有用.
Wolfram 语言 Set 函数保留其第一个参数不被运算,因此在这种情况下不对符号 a 求值:
可以使用 Evaluate 使 Set 运算其第一个参数. 在这种情况下,结果是一个对象,它是 a 的值,即 b 被设置为 6
b 现在已设置为 6
在大多数情况下,我们都希望对提供给 Wolfram 语言的所有表达式进行求值. 但也有时可能希望阻止对某些表达式的求值. 例如,如果要只想要符号式地操作 Wolfram 语言程序的各个部分,则在操作时必须防止对其进行运算.
可以使用函数 HoldHoldForm 使表达式保持不被运算. 这些函数仅通过携带属性 HoldAll 即可,该属性可防止运算其参数.
Hold[expr]HoldForm[expr] 的不同之处在于,以标准的 Wolfram 语言输出格式,Hold 是显式打印的,而HoldForm 不是. 如要查看完整的内部 Wolfram 语言形式,则可以看到这两个函数.
Hold 使表达式保持未运算的形式:
HoldForm 也可以使表达式不被运算,但是在标准 Wolfram 语言输出格式中不可见:
HoldForm 内部仍然存在:
函数 ReleaseHold 删除 HoldHoldForm,因此对它们包含的表达式进行求值:
Hold[expr]
保持 expr 不被运算
HoldComplete[expr]
保持 expr 不被运算,并阻止使用与 expr 相关的上值
HoldForm[expr]
保持 expr 不被运算,并在不使用 HoldForm 的情况下进行打印
ReleaseHold[expr]
删除 expr 中的 HoldHoldForm
Extract[expr,index,Hold]
获取部分 expr,并将其用 Hold 封装以阻止被运算
处理未求值表达式的函数.
提取部分表达式时,通常会对其进行评估:
提取一部分并立即用 Hold 封装,因此不会被运算:
f[,Unevaluated[expr],]
将未运算的 expr 提供给 f 作为其参数
临时防止参数被运算.
1+1 计算结果为 2,并且 Length[2] 给出 0
1+1 以未运算的形式提供给 Length 作为参数:
Unevaluated[expr] 实质上是通过临时为函数提供诸如 HoldFirst 之类的属性,然后将 expr 作为参数提供给函数来工作.
SequenceHold
不展平显示为参数的 Sequence 对象
HoldAllComplete
将所有参数视为完全惰性
防止运算其他方面的属性.
通过设置属性 HoldAll,可以阻止 Wolfram 语言运算函数的参数. 但是即使设置了此属性,Wolfram 语言仍将对参数进行一些转换. 通过设置 SequenceHold,可以防止其展平出现在参数中的 Sequence 对象. 通过设置 HoldAllComplete,还可以禁止剔除 Unevaluated,并防止 Wolfram 语言使用它发现的与参数相关联的任何上值.
模式、规则和定义的运算
Wolfram 语言在运算和模式匹配之间存在许多重要的相互作用. 第一个观察结果是,模式匹配通常发生在至少已经部分求值的表达式上. 因此,通常应该对与这些表达式匹配的模式本身进行运算.
模式被运算意味着它与给定的表达式匹配:
/; 右端的条件在用于模式匹配之前,不被运算:
但是,在某些情况下,您可能希望不对整个模式或部分模式进行运算. 可以通过将这些部分用 HoldPattern 封装来实现此目的. 通常,每当 HoldPattern[patt] 出现在模式中时,出于模式匹配的目的,该格式被视为等同于 patt,但表达式 patt 保持不被运算的状态.
HoldPattern[patt]
用于模式匹配时等价于 patt,但 patt 保持不被运算的状态
防止模式被运算.
HoldPattern 的一种应用是,指定可应用于未运算表达式或以未运算形式保存的表达式的模式.
HoldPattern 使 1+1 保持不被运算,并允许其与 /. 运算符左侧的 1+1 匹配:
请注意,尽管诸如 Hold 之类的函数会阻止对表达式的求值,但它们不会影响使用 /. 及其它运算符操纵这些表达式的某些部分.
每当 r 的参数不是原子对象时,为其定义值:
根据定义,像 r[3] 这样的表达式保持不变:
但模式 r[x_] 根据 r 的定义进行转换:
您需要将 HoldPattern 包装在 r[x_] 周围,以防止对其进行求值:
如上所示,转换规则(例如 lhs->rhs)的左侧通常会立即被运算,因为该规则通常应用于已经求值的表达式. lhs->rhs 的右侧也会立即被运算. 但是,使用延迟规则 lhs:>rhs 时,表达式 rhs 不会被运算.
-> 的右侧被立即运算,但 :> 不会:
这是应用规则的结果. :> 规则的右侧被插入到 Hold 中,而没有被运算:
lhs->rhs
同时运算 lhsrhs
lhs:>rhs
运算 lhs,但不运算 rhs
转换规则的运算.
尽管转换规则的左侧通常会被运算,但对于定义的左侧来说则不然. 出现这种差异的原因如下. 转换规则通常使用 /. 应用于已运算的表达式. 但是,定义是在表达式的运算过程中使用,并应用于尚未完全运算的表达式. 要使用此类表达式,定义的左侧必须保持至少部分未运算的形式.
符号的定义是最简单的情况. 如"非标准运算"中所述,定义左侧的符号不被运算,例如 x=value. 如果 x 之前已被赋值为 y,则如果 x=value 的左侧被运算,它将变成完全不相关的定义 y=value.
这是一个定义. 不运算左侧的符号:
这里重新定义该符号:
如果运算左侧,则定义的不是符号 k,而是符号 k w[4]
现在 w[4] 具有值 w[5]
尽管定义左侧出现的各个符号未被运算,但更复杂的表达式得以部分运算. 在定义左侧的表达式例如 f[args] 中,对 args 求值.
运算 1+1,这样就为 g[2] 定义了一个值:
显示为 g 定义的值:
这就是为什么出现在定义左侧的函数参数必须通过考虑在表达式求值期间如何使用定义来求值的原因. 如"运算原理"中所述,当 Wolfram 语言运算函数时,它首先运算每个参数,然后尝试查找该函数的定义. 结果,当 Wolfram 语言应用您为函数给出的任何定义时,该函数的参数必须已经被求值. 当所讨论的函数具有指定某些参数不被运算的属性时除外.
symbol=value
不运算 symbol;运算 value
symbol:=value
symbolvalue 均不被运算
f[args]=value
运算 args;左侧作为整体不被运算
f[HoldPattern[arg]]=value
f [ arg ] 被赋值,而不运算 arg
Evaluate[lhs]=value
左侧被完全运算
定义中的运算.
尽管在大多数情况下,应该运算定义左侧出现的函数参数,但在某些情况下,不希望这种情况发生. 在这种情况下,可以用 HoldPattern 封装不想被运算的部分.
迭代函数中的运算
Wolfram 语言的内置迭代函数(如 TableSum)运算其参数的方法略有特殊.
当对 Table[f,{i,imax}] 这样的表达式进行计算时,如块和局部值中所述,第一步是使 i 的值成为本地值. 然后运算迭代规范中的极值 imax. 表达式 f 保持未运算的形式,但随着将一系列值赋给 i 而被重复求值. 完成此操作后,将恢复 i 的全局值.
函数 RandomReal[] 在这里分别被运算了四次,因此生成了四个不同的伪随机数:
这会先运算 RandomReal[],再将它输入到 Table. 结果是四个相同数字的列表:
在大多数情况下,比较方便的做法是,将表达式如 Table[f,{i,imax}] 中的函数 f 保持未运算形式,直到将具体值赋给 i 为止. 如果无法找到适用于任何 if 的完整符号形式,则尤其如此.
fac 定义为在具有整数参数时给出阶乘,而在其他情况下给出 NaN (表示非数字):
在这种形式下,直到赋给 i 明确的整数值时,才对 fac[i] 求值:
使用 Evaluate 强制运算 fac[i],将 i 保留为符号对象:
对于 诸如Table[f,{i,imax}] 之类的表达式,如果可以带有任意 if 的完整符号形式,则通常更有效的方法是先计算该形式然后输入到 Table. 可以使用 Table[Evaluate[f],{i,imax}] 进行此操作.
在这种情况下,将针对 i 的每个值分别求和,即运算 Sum
但是,有可能获得一个和的符号公式,该公式对于 i 的任何值均有效:
通过插入 Evaluate,首先告诉 Wolfram 语言以符号方式求和,然后对 i 进行迭代:
Table[f,{i,imax}]
保持 f 不被运算,直到将具体值赋给 i 为止
Table[Evaluate[f],{i,imax}]
首先运算 f,保留 i 的符号形式
迭代函数中的运算.
条件语句
Wolfram 语言提供了各种设置条件语句的方式,用于指定只有在满足特定条件的情况下才应运算特定表达式.
lhs:=rhs/;test
仅当 test 的运算结果为 True 时才使用定义
If[test,then,else]
如果 testTrue,则运算 then,如果为 False 则运算 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]
给出与第一个生成 Truetesti 对应的值
条件语句结构.
检验给出 False,因此返回"else"表达式 y
在这种情况下,仅运算 "else" 表达式:
当您使用 Wolfram 语言编写程序时,通常会在以下两个选项之间做出选择:创建一个单独的定义(其右侧包含由 If 函数控制的多个分支),或者进行多个定义(每个定义由一个适当的 /; 条件控制. 通过使用多个定义,通常可以生成既更清晰又易于修改的程序.
这定义了一个阶跃函数,对于 x>0,其值为 1,否则为 -1
这里使用 /; 条件定义了步进函数的正数部分:
这是步进函数的负数部分:
这里使用 /; 条件显示完整定义:
函数 If 提供了在两种选择之间进行选择的方法. 但是,选择往往会有两种以上. 一种解决方法是使用一组嵌套的 If 函数. 而通常最好使用诸如 WhichSwitch之类的函数.
定义一个具有三个区域的函数. 使用 True 作为第三个检验使其成为默认情况:
这使用 Which 中的第一种情况:
这使用第三种情况:
这定义了一个函数,该函数取决于其参数模 3 的值:
Mod[7,3]1,因此这里使用 Switch 中的第二种情况:
1701 均不匹配,但匹配 _
对于 Wolfram 语言这样的符号系统,重要的一点是,您给出的条件可能不会生成 TrueFalse. 因此,例如,除非 xy 具有特定值(例如数值),否则条件 x==y 不会生成 TrueFalse.
在这种情况下,检验不会给出 TrueFalse,因此 If 中的两个分支均未运算:
可以在 If 中添加一个特殊的第四个参数,如果检验未得出 TrueFalse,则使用该参数:
If[test,then,else,unknown]
If 的一种形式,其中包含 test 既非 True 也非 False 时要使用的表达式
TrueQ[expr]
如果 exprTrue,则为 True,否则为 False
lhs===rhs
or
SameQ[lhs,rhs]
如果 lhsrhs 相同,则为 True,否则为 False
lhs=!=rhs
or
UnsameQ[lhs,rhs]
如果 lhsrhs 不相同,则为 True,否则为 False
MatchQ[expr,form]
如果模式 formexpr 相匹配,则为 True,否则为 False
处理符号式条件的函数.
Wolfram 语言将其作为符号方程式:
除非 expr 明显为 True,否则 TrueQ[expr] 有效地假定 exprFalse
== 不同,=== 检验两个表达式是否显然相同. 在这种情况下,它们不同:
lhs===rhslhs==rhs 之间的主要区别是 === 总是返回 TrueFalse,而 == 可以将其输入保持为符号形式,表示符号方程式,如 "方程式" 中所述. 要检验表达式的结构时,通常应使用 ===;如果要检验数学相等性,则应使用 ==. Wolfram 语言模式匹配器有效地使用 === 来确定一个文字表达式何时与另一个文字表达式匹配.
可以使用 === 检验表达式的结构:
运算符 == 给出的结果不太有用:
在设置条件时,经常需要使用检验的组合,例如 test1&&test2&&. 重要的一点是,如果任何 testiFalse,则这些检验组合的结果将为 False. Wolfram 语言总是依次运算 testi,如果任何 testi 产生 False,则停止.
expr1&&expr2&&expr3
直到发现其中一个 expriFalse 时停止运算
expr1||expr2||expr3
直到发现其中一个 expriTrue 时停止运算
逻辑表达式的运算.
此函数涉及两个检验的组合:
这里两个检验都经过运算:
在这里,第一个检验的结果为 False,因此不尝试第二个检验. 第二个检验将涉及 1/0,会产生错误:
Wolfram 语言运算逻辑表达式的方式使您可以组合检验序列,其中只有通过了较早的检验,后面的检验才有意义. 该行为与 C 语言中的行为类似,可方便地构造多种 Wolfram 语言程序.
循环和控制结构
Wolfram 语言程序的执行涉及对 Wolfram 语言表达式序列的运算. 在简单的程序中,要计算的表达式可以用分号分隔,然后依次求值. 但往往很多时候需要以某种循环方式多次计算表达式.
Do[expr,{i,imax}]
重复运算 expri1 为步长从 1imax 变化
Do[expr,{i,imin,imax,di}]
运算 expr,其中 idi 为步长从 iminimax 变化
Do[expr,{i,list}]
运算 expr,其中 i 的值取自 list
Do[expr,{n}]
运算 expr n
简单的循环结构.
运算 Print[i^2],其中 i14 变化:
通过循环执行对 t 的赋值,其中 k262 为步长变化;
Do 中指定迭代的方式与 TableSum 等函数中的方式完全相同. 和在这些函数中一样,可以通过为 Do 提供一系列迭代规范来设置多个嵌套循环.
这里 i 的值从 14 循环,对于 i 的每个值,j 的值从 1i-1 循环:
有时,您可能希望将特定的操作重复执行一定次数,而不更改迭代变量的值. 这种重复可以像在 Table 和其他迭代函数中一样在 Do 中指定.
这里将赋值 t=1/(1+t) 重复三次:
可以在 Do 的内部放置一个过程:
Nest[f,expr,n]
f 应用于 expr n
FixedPoint[f,expr]
expr 开始,并重复应用 f 直到结果不再更改
NestWhile[f,expr,test]
expr 开始,并重复应用 f,直到对结果进行 test 不再产生 True 为止
重复应用函数.
Do 允许您通过使用迭代变量的不同值多次运算特定表达式来重复操作. 但是,通常您可以使用"重复应用函数"中讨论的函数式编程结构来制作更优雅和更高效的程序. 例如,Nest[f,x,n] 允许您将函数重复应用于表达式.
这里将 f 嵌套三次:
通过嵌套一个纯函数,您可以得到与上面使用 Do 的示例相同的结果:
Nest 允许您将函数应用指定的次数. 但是,有时您可能只是想继续应用一个函数,直到结果不再改变为止. 这可以使用 FixedPoint[f,x] 实现此目的.
FixedPoint 不断应用函数,直到结果不再更改为止:
可以使用 FixedPoint 模拟 Wolfram 语言中的运算过程,或者模拟诸如 expr//.rules 之类的函数操作. FixedPoint 不断运行,直到获得的两个连续结果相同为止. NestWhile 允许您继续操作,直到任意函数不再产生 True 为止.
Catch[expr]
运算 expr,直到遇到 Throw[value],然后返回 value
Catch[expr,form]
运算 expr,直到遇到 Throw[value,tag],其中 formtag 匹配
Catch[expr,form,f]
返回 f[value,tag] 而不是 value
运算的非本地控制.
遇到 Throw 时求值停止,并且将 i 的当前值作为外层的 Catch 的值返回:
ThrowCatch 提供一种灵活的方式来控制 Wolfram 语言中的运算过程. 基本思想是,每当遇到一个 Throw 时,运算都将停止,并且 Wolfram 语言立即就近返回到适当的外层 Catch.
Scan 将函数 Print 应用于列表中的每个连续元素,最后只返回 Null
Scan 的运算一旦遇到 Throw 即停止,并且外层的 Catch 返回 Throw 的参数作为其值:
通过 Map 可以获得相同的结果,即便 Map 的运算如果不因遇到 Throw 而停止的话将返回一个列表:
可以使用 ThrowCatch 来转移函数式编程构造的操作,例如允许仅在满足某些条件之前继续运算此类构造. 请注意,如果使用 Throw 停止运算,则所得结果的结构可能与允许运算完成所得到的结果完全不同.
这是通过重复应用函数生成的列表:
由于没有遇到 Throw,这里的结果与先前相同:
现在对 NestList 的运算被转移,返回的是作为 Throw 参数给出的单个数值:
ThrowCatch 以完全全局的方式操作:无论如何或在何处生成 Throw,总是停止运算并返回到外层的 Catch.
Throw 停止运算 f,使得 Catch 只返回 a,不留 f 的痕迹:
这定义了一个函数,当其参数大于10时将生成 Throw
这里不会产生 Throw
但这里在 g 的运算内部生成的 Throw 将返回到外层的 Catch
在小型程序中,通常以最简单的形式使用 Throw[value]Catch[expr] 就足够了. 但对于大型程序,特别是包含许多单独部分的大型程序,通常最好使用 Throw[value,tag]Catch[expr,form]. 通过将表达式的 tagform 保持在程序特定部分的本地,可以确保 ThrowCatch 也仅在该部分中运行.
在这里 Throw 被内层的 Catch 捕获:
但这里仅被外层的 Catch 捕获:
可以使用模式来指定特定 Catch 应该捕获的标签:
这样可以使标签 a 保持完全本地化:
应该认识到,不需要将 Throw 中出现的标签设为常量; 通常,它可以是任何表达式.
在这里,内层的 Catch 捕获了所有标记小于4的 Throw,并继续执行 Do. 但是,只要标签到达 4,就需要外层的 Catch
当使用带有 Throw[value,tag]Catch[expr,form] 时,由 Catch 返回的值仅是在 Throw 中给出的表达式value. 但如果使用 Catch[expr,form,f],则 Catch 返回的值将改为 f[value,tag].
这里,f 被应用于 Throw 中的值和标记:
如果没有 Throw,绝不会使用 f
While[test,body]
只要 testTrue,便重复运算 body
For[start,test,incr,body]
运算 start,然后重复运算 bodyincr,直到 test 失败
一般的循环构造.
DoNestFixedPoint 等函数提供了在 Wolfram 语言程序中进行循环的结构式方法,而 ThrowCatch 提供了修改这类结构的机会. 但是,有时您可能想要创建甚至从一开始就具有较少结构的循环. 在这种情况下,函数 WhileFor 可能会更方便,它们会重复执行操作,并在指定条件不成立时停止.
循环 While 在条件不成立时停止:
Wolfram 语言中的函数 WhileFor 与 C 语言中的控制结构 whilefor 相似. 但是请注意,两者之间仍有许多重要的区别. 例如,Wolfram 语言的 For 循环,相对于 C 语言,其逗号和分号的作用相反.
这是 For 循环的一个非常常见的形式. i++ 使 i 的值递增:
这是一个更为复杂的 For 循环. 请注意,一旦测试 i^2<10 失败,循环就会停止:
在 Wolfram 语言中,WhileFor 都始终在运算循环主体之前运算循环测试. 一旦循环测试不为 True,则 WhileFor 终止. 因此,仅在循环测试为 True 的情况下才会运算循环的主体.
循环测试立即失败,因此永远不会运算循环的主体:
WhileFor 循环中,或通常在任何 Wolfram 语言的过程中,所给出的 Wolfram 语言表达式都是按确定的顺序求值的. 可以认为此序列定义了 Wolfram 语言程序执行中的控制流.
在大多数情况下,应该尽量使 Wolfram 语言程序中的控制流尽可能简单. 例如,控制流越依赖于程序执行过程中生成的特定值,您通常会越难以理解控制程序的结构和操作.
函数式编程构造通常涉及非常简单的控制流程. WhileFor 循环总是更复杂,因为它们的设置使控制流取决于作为测试给出的表达式的值. 然而即使在这样的循环中,控制流程通常也不取决于循环主体中给出的表达式的值.
但是,在某些情况下,可能需要构造 Wolfram 语言程序,其中控制流受执行过程或循环主体生成的值的影响. 符合函数式编程思想的一种实现方法是使用 ThrowCatch. 但 Wolfram 语言还提供了用于修改控制流的各种函数,这些函数的工作方式与 C 语言类似.
Break[]
退出最近的封闭循环
Continue[]
转到当前循环的下一步
Return[expr]
从函数返回值 expr
Goto[name]
转到当前过程中的元素 Label[name]
Throw[value]
返回 value 作为最近的封闭 Catch 的值(非本地返回)
控制流函数.
Break[] 使循环在 t 超过19 时终止:
k<3 时,Continue[] 使循环继续执行,而不执行 t+=2
Return[expr] 允许您退出特定的函数,返回一个值. 可以将 Throw 视为一种非本地返回,它使您可以退出整个嵌套函数序列. 这种行为可以方便地处理某些出错情况.
这是 Return 的用法示例. 此特定过程无需使用 Return 同样可以编写:
当参数大于 5 时,将使用该过程中的第一个 Return
如果该函数的参数为负,则会error
这里没有产生 Throw
但这种情况下会产生 Throw,整个 Catch 返回值 error
诸如 Continue[]Break[] 之类的函数使您可以将控制权转移到 Wolfram 语言程序中循环的开始或结束. 有时,您可能需要将控制权转移到 Wolfram 语言过程中的特定元素. 如果在过程中将 Label 作为元素,则可以使用 Goto 将控制权转移到该元素.
不断循环,直到 q 超过 6 为止:
请注意,只有当它指定的 Label 出现在同一 Wolfram 语言过程的元素中时,才能在特定 Wolfram 语言过程中使用 Goto. 通常,Goto 的使用会降低程序中易于理解的结构程度,从而使程序的操作更加难以理解.
在运算过程中收集表达式
在许多计算中,我们只关心表达式作为输入的最终运算结果. 但是有时也会希望收集在运算过程中生成的表达式. 这可以使用 SowReap 实现.
Sow[val]
对最接近的外层 Reap 播撒值 val
Reap[expr]
运算 expr,同时返回 Sow 所播撒的值的列表
使用 SowReap.
这里的输出仅包含最终结果:
这里还给出了两个中间结果:
计算总和,并收集所有偶数项:
ThrowCatch 相似,SowReap 可以在计算中的任何地方使用.
这里定义可以进行 Sow 的函数:
这将嵌套该函数,并收获所有低于1/2的情况:
Sow[val,tag]
播撒带有标签的 val 以指示何时收获
Sow[val,{tag1,tag2,}]
对每一个 tagi 播撒 val
Reap[expr,form]
获取其标签匹配 form 的所有值
Reap[expr,{form1,form2,}]
为每个 formi 分别创建列表
Reap[expr,{form1,},f]
f 应用于每个不同的标签和值列表
带有标签的播撒和收获.
这里只收获标签 x 播撒的值:
这里 1 通过标签 x 被播撒了两次:
使用不同标签播撒的值始终出现在不同的子列表中:
为要获取的每种形式的标签创建一个子列表:
f 应用于每个不同的标记和值列表:
标签可以是计算的一部分:
追踪运算
把提供的任何表达式作为输入,完全运算该表达式,然后返回结果,这是 Wolfram 系统的标准工作方式. 但是如果想要理解 Wolfram 系统的行为,通常不仅要看运算的最终结果,还要看运算过程的中间步骤.
Trace[expr]
生成运算 expr 使用的所有表达式的列表
Trace[expr,form]
仅包含与模式 form 匹配的表达式
追踪表达式的运算.
将表达式 1+1 立即运算为 2
在进行加法之前先运算 2^3
每个子表达式的运算显示在单独的子列表中:
Trace[expr] 给出一个列表,其中包括运算 expr 涉及的所有中间表达式. 然而,除了极简单的情况外,以这种方式生成的中间表达式的数量通常非常庞大,并且 Trace 所返回的列表很难理解.
Trace[expr,form] 允许您过滤 Trace 记录的表达式,仅保留那些与模式 form 匹配的表达式.
这是阶乘函数的递归定义:
给出运算 fac[3] 时生成的所有中间表达式. 结果相当复杂:
仅显示形式为 fac[n_] 的中间表达式:
可以在 Trace 中指定任何模式:
Trace[expr,form] 通过截取在 expr 运算期间将要运算的每个表达式,并挑选出与模式 form 匹配的表达式,有效地工作.
如果要追踪对诸如 fac 之类的函数的调用,只需告诉 Trace 选出 fac[n_] 形式的表达式即可. 还可以使用 f[n_,2] 之类的模式来挑选具有特定参数结构的调用.
但是,典型的 Wolfram 系统程序不仅包含 fac[n] 之类的函数调用,还包含其他元素,例如对变量的赋值、控制结构等. 所有这些元素都表示为表达式. 您可以在 Trace 中使用模式来挑选任何类型的 Wolfram 系统程序元素. 因此,举例来说,可以使用类似 k=_ 的模式来挑选对符号 k 的所有赋值.
这显示了对 k 的赋值序列:
Trace[expr,form] 可以挑选在 expr 运算过程中随时出现的表达式. 例如,表达式不必直接以您提供的 expr 形式出现. 相反,它们可能会在对函数进行运算的过程中产生,作为 expr 运算的一部分.
这是一个函数定义:
可以查看在运算 h 期间生成的表达式:
Trace 可以监视函数运算的中间步骤,这些函数不仅包括自定义函数,还包括 Wolfram 系统内置的某些函数. 但应该认识到,内置的 Wolfram 系统函数所遵循的中间步骤的具体顺序在细节上取决于它们在 Wolfram 系统特定版本中的实现和优化.
Trace[expr,f[___]]
显示对函数 f 的所有调用
Trace[expr,i=_]
显示对 i 的赋值
Trace[expr,_=_]
显示所有赋值
Trace[expr,Message[___]]
显示生成的消息
Trace 的一些使用方式.
函数 Trace 返回一个列表,该列表表示 Wolfram 系统计算的历史. 列表中的表达式按在计算过程中生成的顺序给出. 在大多数情况下,Trace 返回的列表具有嵌套结构,该嵌套结构表示计算的结构.
基本思想是,Trace 返回的列表中每个子列表都代表特定 Wolfram 系统表达式的运算链. 该链的元素对应于同一表达式的不同形式. 然而,通常对一个表达式的求值需要对许多其他表达式(通常是子表达式)求值. 每个辅助运算由 Trace 返回的结构中的子列表表示.
这是一个赋值序列:
生成一条运算链,该链反映了所用 a[i] 的转换顺序:
化简 y+x+y 生成的连续形式在其运算链中显示为连续元素:
函数 f 的每个参数都有一个单独的运算链,在子列表中给出:
每个子表达式的运算链在单独的子列表中给出:
追踪嵌套表达式的运算将产生一个嵌套列表:
在 Wolfram 系统表达式的求值过程中,有两种基本方法可以要求进行辅助求值. 第一种方法是表达式可以包含子表达式,每个子表达式都必须求值. 第二种方式是,可以存在表达式的运算规则,这些规则涉及的其他表达式自身必须求值. 两种辅助运算均由 Trace 返回的结构中的子列表表示.
这里的辅助运算来自对 fg 参数的运算:
这是附带条件的函数:
fe[6] 的运算涉及与该条件关联的辅助运算:
如果函数是由其自身其他实例递归定义的,我们对函数运算的追踪通常会得到嵌套列表. 这通常是因为函数的每个新实例在通过运算函数的先前实例而获得的表达式中都显示为子表达式.
例如,通过定义 fac[n_]:=n fac[n-1]fac[6] 的运算生成表达式 6 fac[5],它包含 fac[5] 作为子表达式.
生成的 fac 的连续实例出现在连续嵌套的子列表中:
使用此定义,fp[n-1] 直接作为 fp[n] 的值得到:
fp[n] 永远不会出现在子表达式中,因此不会生成任何子列表:
这是斐波那契数的递归定义:
这是递归的结束条件:
这显示了 fib[5] 递归运算的所有步骤:
在运算任何 Wolfram 系统表达式的过程中,每一步都可以被视为应用特定转换规则的结果. 如"将定义与不同的符号相关联"中所述,Wolfram 系统知道的所有规则都与特定的符号或标签相关联. 使用 Trace[expr,f] 可以查看 expr 运算中的所有步骤,其所用的转换规则与符号 f 相关联. 在这种情况下,Trace 不仅会给出应用每个规则的表达式,而且还会给出应用规则的结果.
一般地,Trace[expr,form] 会选出在 expr 运算过程中,其 form 要么与待运算的表达式匹配,要么与所用规则关联的标记匹配的所有步骤.
Trace[expr,f]
显示所有使用与符号 f 相关的转换规则的运算
Trace[expr,f|g]
显示与 fg 相关的所有运算
追踪与特定标签关联的运算.
这里仅显示匹配 fac[_] 的中间步骤:
这里显示所有使用与符号 fac 关联的转换规则的运算:
这里是 log 函数的规则:
这将追踪 log[abcd] 的运算,显示与 log 相关的所有转换:
Trace[expr,form,TraceOn->oform]
仅在匹配 oform 的形式内打开追踪
Trace[expr,form,TraceOff->oform]
在任何匹配 oform 的形式内关闭追踪
在某些形式内关闭追踪.
Trace[expr,form] 允许追踪在 expr 运算的任何时候生成的与 form 匹配的表达式. 有时,您可能只想追踪在运算 expr 某些部分时生成的表达式.
通过设定选项 TraceOn->oform,可以指定仅在运算与 oform 匹配的形式时才进行追踪. 同样,通过设置 TraceOff->oform,可以指定在运算与 oform 匹配的形式期间应关闭追踪.
这里显示运算的所有步骤:
这里仅显示在运算 fac 的过程中产生的步骤:
这里仅显示不在运算 fac 的过程中产生的步骤:
Trace[expr,lhs->rhs]
查找在 expr 运算期间出现的所有与 lhs 匹配的表达式,并用 rhs 替换它们
将规则应用于运算过程中遇到的表达式.
这里告诉 Trace 仅返回在 fib[5] 运算过程中使用的 fib 的参数:
Wolfram 系统 Trace 函数的强大之处在于,它返回的对象基本上是标准的 Wolfram 系统表达式,可以使用其他Wolfram 系统函数进行操作. 但要注意的重要一点是,Trace 会把使用 HoldForm 生成的列表中出现的所有表达式封装起来,以防止对其进行求值. HoldForm 不会以标准 Wolfram 系统输出格式显示,但仍存在于表达式的内部结构中.
显示在运算过程的中间阶段生成的表达式:
这些表达式用 HoldForm 封装,以防止对其进行求值:
在标准 Wolfram 系统输出格式中,有时很难区分哪些列表与 Trace 返回的结构相关联,哪些是要运算的表达式:
查看输入格式可以解决歧义问题:
Trace 中使用转换规则时,将对结果进行运算,然后将其封装到 HoldForm 中:
对于复杂的运算,Trace 返回的列表结构可能非常复杂. 当您使用 Trace[expr,form] 时,Trace 仅将那些与模式 form 匹配的表达式作为元素包括在列表中. 但无论您提供哪种模式,列表的嵌套结构都保持不变.
显示在 fib[3] 运算过程中所有出现的 fib[_]
仅显示 fib[1] 的出现,但列表的嵌套与 fib[_] 相同:
可以设置选项 TraceDepth->n 来告诉 Trace 仅包含嵌套深度最多为 n 层的列表. 这样通常可以选择计算的重要步骤,而无需查看细节. 请注意,通过设置 TraceDepthTraceOff ,可以避免查看计算中的许多步骤,从而显著加快了该计算的 Trace 操作.
仅显示出现在最多两层嵌套的列表中的步骤:
Trace[expr,form,TraceDepth->n]
追踪对 expr 的运算,而忽略导致列表嵌套深度在 n 层以上的步骤
限制追踪深度.
使用 Trace[expr,form] 时,将获得与在 expr 运算过程中生成的 form 匹配的所有表达式的列表. 有时不仅需要查看这些表达式,而且需要查看它们的运算结果. 这可以通过在 Trace 中设置选项 TraceForward->True 来执行此操作.
这不仅显示与 fac[_] 匹配的表达式,而且还显示这些表达式的运算结果:
使用Trace[expr,form] 挑出的表达式通常位于运算链的中间. 通过设置 TraceForward->True 告诉 Trace 还包括在运算链末尾获得的表达式. 如果设置 TraceForward->AllTrace 将包括在运算链上表达式匹配 form 之后出现的所有表达式.
通过设置 TraceForward->All,运算链上与 fac[_] 匹配的元素之后的所有元素均包括在内:
通过设置选项 TraceForward,可以有效查看运算过程中特定形式表达式所发生的情况. 但是,有时您并不希望弄清特定表达式会发生什么,而是想知道该表达式是如何生成的. 这可以通过设置选项 TraceBackward 来实现. TraceBackward 的作用是显示出现在运算链上特定表达式形式之前的内容.
这表明数字 120 来自 fac[10] 运算过程中对 fac[5] 的运算:
以下是与数字 120 的生成相关的整个运算链:
TraceForwardTraceBackward 允许您向前或向后查看特定运算链. 有时,您可能还需要查看特定运算链所在的运算链. 这可以使用 TraceAbove->True 实现. 如果设置选项 TraceAbove->True,则 Trace 将在所有相关运算链中包含初始和最终表达式. 通过设置 TraceAbove->AllTrace 包含所有这些运算链中的所有表达式.
这包括所有运算链中的初始和最终表达式,这些运算链包括含有 120 的链:
显示在运算 fib[5] 的过程中生成 fib[2] 的所有方式:
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 在计算函数参数之前也查看表达式.
这包括在参数求值前后都与 fac[_] 匹配的表达式:
Trace 生成的列表结构通常仅包含构成非平凡运算链中步骤的表达式. 因此通常不包括运算自身的单个符号. 但是,如果设置 TraceOriginal->True,则 Trace 会绝对查看运算过程中涉及的每个表达式,包括那些具有平凡运算链的表达式.
在这种情况下,Trace 绝对包括所有表达式,甚至包括那些具有平凡运算链的表达式:
选项名称
默认值
TraceForwardFalse
是否显示运算链中在 form 之后的表达式
TraceBackwardFalse
是否显示运算链中在 form 之前的表达式
TraceAboveFalse
是否显示导致运算链包含 form 的运算链
TraceOriginalFalse
是否在运算表达式头部和参数之前先查看表达式
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 的所有步骤,不允许替换局部变量名称
防止局部变量的匹配.
在某些情况下,您可能只想追踪全局变量 x,而不要跟踪最初命名为 x 的任何局部变量. 这可以通过设置选项MatchLocalNames->False 来实现.
这将追踪对名称形式为 x$nnn 的所有变量的赋值:
这里仅追踪对特定全局变量 x 的赋值:
函数 Trace 执行完整的计算,然后返回代表计算历史的结构. 但是,有时在进行过程中查看计算轨迹很有用,特别是对非常长的计算而言. TracePrint 函数的工作原理与 Trace 类似,不同之处在于它会在遇到表达式时打印表达式,而不是保存所有表达式以创建列表结构.
这将打印在运算 fib[3] 过程中出现的表达式:
TracePrint 打印的表达式序列与 Trace 返回的列表结构中给出的表达式序列相对应. TracePrint 输出中的缩进对应于 Trace 中列表结构的嵌套. 可以在 TracePrint 中使用 Trace 选项 TraceOnTraceOffTraceForward. 但是,由于 TracePrint 随过程产生输出,因此不支持选项 TraceBackward. 此外,TracePrint 的设置使得 TraceOriginal 始终为 True.
Trace[expr,]
追踪 expr 的运算,返回包含遇到的表达式的列表结构
TracePrint[expr,]
追踪 expr 的运算,打印遇到的表达式
TraceDialog[expr,]
追踪 expr 的运算,遇到每个指定的表达式时启动对话框
TraceScan[f,expr,]
追踪 expr 的运算,将 f 应用于遇到的每个表达式的 HoldForm
追踪运算的函数.
在运算 fac[10] 的过程中遇到 fac[5] 时,将进入一个对话框:
在对话框中,可以通过查看堆栈来确定您的位置:
这将从对话框返回,并给出 fac[10] 的最终运算结果:
函数 TraceDialog 有效地使您可以停在计算中间,并与当时存在的 Wolfram 系统环境进行交互. 例如,您可以在计算中找到中间变量的值,甚至重置这些值. 当然,还有许多微妙之处需要注意,主要与模式和模块变量有关.
TraceDialog 的作用是在一系列表达式上调用函数 Dialog. 函数 Dialog"对话框"中有详细讨论. 调用 Dialog 时,实际上是在使用自己的输入和输出行序列来启动辅助 Wolfram 系统会话.
通常,可能需要在追踪运算时将任意函数应用于所获得的表达式. TraceScan[f,expr,]f 应用于出现的每个表达式. 表达式使用 HoldForm 进行封装,以防止对其求值.
TraceScan[f,expr,] 中,函数 f 在运算表达式之前应用于表达式. TraceScan[f,expr,patt,fp]f 应用于运算之前,将 fp 应用于运算之后.
运算堆栈
在所有计算中,Wolfram 系统都会维护一个运算堆栈(Evaluation Stack),其中包含当前正在运算的表达式. 可以使用函数 Stack 查看堆栈. 这意味着如果 Wolfram 系统在计算过程中被中断 ,则可以使用 Stack 了解 Wolfram 系统在做什么.
Wolfram 系统最新开始运算的表达式总是作为运算堆栈的最后一个元素显示. 堆栈中的先前元素是当前正在进行运算的其他表达式.
因此,当运算 x 时,以表达式 f[g[x]] 为例,与运算相关联的堆栈将具有 {f[g[x]],g[x],x} 的形式.
Stack[_] 给出在调用时正在运算的表达式,在这种情况下,包括 Print 函数:
Stack[] 给出与调用时正在执行的运算相关联的标签:
通常,可以把运算堆栈理解为显示哪些函数调用哪些其他函数来到 Wolfram 系统在计算中所处的位置. 表达式序列对应于 Trace 返回的连续嵌套列表中的第一个元素,且选项 TraceAbove 设置为 True.
Stack[]
给出与当前正在执行的运算相关的标签列表
Stack[_]
给出当前正在运算的所有表达式的列表
Stack[form]
仅包括匹配 form 的表达式
查看运算堆栈.
在 Wolfram 系统的主体会话中直接调用 Stack 的情况很少见. 更常见的是在计算过程中调用 Stack. 通常,可以在对话框或辅助会话中执行此操作,如对话框中所述.
这是阶乘函数的标准递归定义:
运算 fac[10],当遇到 fac[4] 时启动一个对话框:
显示启动对话框时正在运算的对象:
结束对话框:
在最简单的情况下,Wolfram 系统运算堆栈的设置是记录当前正在运算的所有表达式. 但在某些情况下,这可能会带来不便. 例如,执行 Print[Stack[]] 将始终显示一个堆栈,其中 Print 作为最后一个函数.
通过函数 StackInhibit 可以避免这类问题. StackInhibit[expr] 在不修改堆栈的情况下运算 expr.
StackInhibit 防止将 Print 包含在堆栈中:
诸如 TraceDialog 之类的函数在每次启动对话框时都会自动调用 StackInhibit. 这意味着 Stack 不显示对话框内调用的函数,仅显示外部的函数.
StackInhibit[expr]
在不修改堆栈的情况下运算 expr
StackBegin[expr]
用新堆栈运算 expr
StackComplete[expr]
使用堆栈中包含的运算链中的中间表达式运算 expr
控制运算堆栈.
通过使用 StackInhibitStackBegin,可以控制运算过程的哪些部分被记录在堆栈中. StackBegin[expr] 开启一个新的堆栈来运算 expr. 这意味着在运算 expr 的过程中,堆栈不包含 StackBegin 之外的任何内容. TraceDialog[expr,] 等函数在开始运算 expr 之前会调用 StackBegin,以使堆栈显示如何运算 expr ,而不是如何调用 TraceDialog.
StackBegin[expr] 在运算 expr 的过程中使用新堆栈:
Stack 通常只显示当前正在计算的那些表达式. 因此,它仅包含每个表达式的最新形式. 但有时可能会需要查看表达式的早期形式. 这可以使用 StackComplete 实现.
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.
$RecursionLimit$IterationLimit 重置为 20
现在,这样的无限定义在运行仅20步后就停止:
没有结束条件,此递归定义将导致无限计算:
在停止计算之前会建立一个相当大的结构:
这是另一个递归定义:
在这种情况下,不会建立任何复杂的结构,并且计算将被 $IterationLimit 停止:
无限循环不仅会占用时间,而且还会占用计算机内存,认识到这一点很重要. 受 $IterationLimit 限制的计算通常不会产生大型的中间结构. 但是受 $RecursionLimit 限制的对象通常则会. 在许多情况下,生成结构的大小是 $RecursionLimit 值的线性函数. 但在某些情况下,使用 $RecursionLimit 会使大小呈指数倍增长,甚至更糟.
x=x+1 这样的赋值显然是循环的. 但在设置更复杂的递归定义时,要确保递归终止,不会无限循环下去可能会比较困难. 要检查的主要内容是变换规则的右侧是否始终与左侧不同. 这确保了运算将总是取得进展,并且Wolfram 语言不会只是将相同的转换规则一遍遍地重复应用于相同的表达式.
最棘手的情况出现在规则依赖于复杂的 /; 条件时(参阅对模式施加约束). 一种特别尴尬的情况是当条件涉及全局变量时. 由于表达式未更改,Wolfram 语言认为运算已完成. 但是某些其他操作可能会无意中更改了全局变量的值,从而应该产生新的运算结果. 避免出现这种情况的最好方法是不要在 /; 条件中使用全局变量. 如果所有其他方法均失败,则可以键入 Update[s] 告知 Wolfram 语言更新所有涉及 s 的表达式. Update[] 告诉 Wolfram 语言必须更新所有表达式.
中断和中止
中断计算介绍了如何通过键盘上的相应键来中断 Wolfram语言计算.
在某些情况下,您可能想在 Wolfram 语言程序内部模拟此类中断. 通常,执行 Interrupt[] 与按下中断键具有相同的效果. 一般系统上会显示选项菜单,如中断计算中所述.
Interrupt[]
中断计算
Abort[]
终止计算
CheckAbort[expr,failexpr]
运算 expr 并返回结果,如果出现中止则返回 failexpr
AbortProtect[expr]
运算 expr,掩盖中止的影响,直到运算完成
中断和中止.
函数 Abort[] 与中断计算并在中断菜单中选择 abort 选项具有相同的效果.
可以使用 Abort[] 在程序中实现紧急停止. 但在几乎所有情况下,都应尽量使用诸如 ReturnThrow之类的函数,这些函数使行为受到更多控制.
Abort 终止计算,因此仅执行第一个 Print
如果中止发生在 Wolfram 语言表达式的运算过程中,Wolfram 语言通常会放弃对整个表达式的求值,并返回值 $Aborted.
但是,您可以使用函数 CheckAbort 捕获中止行为. 如果在 CheckAbort[expr,failexpr] 中的 expr 运算期间发生中止,则 CheckAbort 返回 failexpr,但中止不会继续传播. Dialog 之类的函数以这种方式使用 CheckAbort 来包含中止的效果.
CheckAbort 捕获中止,打印 c,并返回值 aborted
Abort 的效果被 CheckAbort 包含,因此 b 被打印出来:
当使用 Wolfram 语言构造复杂的程序时,有时可能希望保证程序中的特定代码段不被中止,无论是以交互方式还是通过调用 Abort. 函数 AbortProtect 允许您运算表达式,保存所有异常中止,直到表达式运算完成.
保存 Abort 直到完成 AbortProtect
CheckAbort 看到中止,但不进一步传播它:
即使在 AbortProtect 内部,CheckAbort 也会看到发生的任何异常中止,并将返回适当的 failexpr. 除非该 failexpr 本身包含 Abort[],否则异常中止将被 CheckAbort 吸收.
编译 Wolfram 语言表达式
对于定义 f[x_]:=x Sin[x],Wolfram 语言将以对任何 x 均可求值的方式对表达式 x Sin[x] 进行存储. 然后,当给出 x 的特定值时,Wolfram 语言将该值替换为 x Sin[x],并运算结果. 无论给 x 的值是数字、列表、代数对象,还是任何其他类型的表达式,Wolfram 语言用来执行此运算的内部代码都可以很好地工作.
对所有这些可能性的考虑,不可避免地会使运算过程变慢. 但如果 Wolfram 语言可以认定 x 是机器数,则可以避免很多步骤,并且可能更快地运算像 x Sin[x] 这样的表达式.
在 Wolfram 语言中可以使用 Compile 构造编译函数. 这些函数在运算 Wolfram 语言表达式时假定出现的所有参数都是数字(或逻辑变量). Compile[{x1,x2,},expr] 接受表达式 expr 并返回一个编译函数,当给定参数 时,该函数将对该表达式求值.
通常,Compile 创建一个 CompiledFunction 对象,该对象包含用于运算已编译函数的一系列简单指令. 选择的指令与典型计算机的机器代码中的指令接近,因此可以快速执行.
Compile[{x1,x2,},expr]
创建一个编译函数,该函数对 xi 的数值运算 expr
创建编译函数.
定义 f 为纯函数,对任何 x 运算 x Sin[x]
创建编译函数来运算 x Sin[x]
ffc 生成的结果相同,但当您提供的参数为数值时,fc 的运行速度更快:
在必须多次运算特定数值或逻辑表达式的情况下,Compile 很有用. 通过花时间调用 Compile,可以得到一个比普通Wolfram 语言函数执行得更快的已编译函数.
对于像 x Sin[x] 这样的简单表达式,普通函数和编译函数的执行速度通常几乎没有差异. 但是,随着所涉及表达式的大小增加,编译的优势也随之增强. 对于大型表达式,编译可以使执行速度提高20倍.
对于含有大量简单函数(例如算术函数)的表达式,编译的影响最大. 对于更复杂的函数(BesselKEigenvalues),大部分计算时间都花在了执行 Wolfram 语言的内部算法上,而编译对这些算法没有影响.
这将创建一个编译函数,用于查找十阶勒让德多项式的值. Evaluate 告诉 Wolfram 语言在进行编译之前显式构造多项式:
求参数为 0.4 的十阶勒让德多项式的值:
使用内置的数值代码:
虽然可以使用编译来对所写的数值函数加速,但仍应尽可能尝试使用内置的 Wolfram 语言函数. 内置函数通常比自行创建的已编译 Wolfram 语言程序运行速度更快. 此外,内置函数通常使用更广泛的算法,并能对数值精度进行更完全的控制,等等.
应该认识到,内置的 Wolfram 语言函数本身经常会使用 Compile. 例如在默认情况下,NIntegrate 会自动对要求积分的表达式使用 Compile,类似地,PlotPlot3D 等函数会自动对要求绘图的表达式使用 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
机器精度的近似复数
True|False
逻辑变量
指定编译类型.
Compile 通过对运算给定表达式的过程中出现的对象类型进行假设来工作. 默认假设是表达式中的所有变量均为近似实数.
Compile 也允许使用整数、复数和逻辑变量(TrueFalse)以及数字数组. 可以通过提供仅匹配具有该类型的值的模式来指定特定变量类型. 例如可以使用模式 _Integer 指定整数类型. 类似地,可以使用 True|False 来指定必须为 TrueFalse 的逻辑变量.
假定 ij 为整数,编译表达式 5i+j
这将产生一个整数结果:
这将编译一个对整数矩阵执行运算的表达式:
现在,列表操作以编译的方式执行,结果为整数:
Compile 处理的类型本质上对应于计算机通常在机器代码级别处理的类型. 例如,Compile 可以处理具有机器精度的近似实数,但不能处理任意精度的数. 此外,如果指定特定变量为整数,则 Compile 仅对机器尺寸的整数(通常在 之间)的情况生成代码.
当要求编译的表达式仅涉及标准算术和逻辑运算时,Compile 可以简单地根据输入变量的类型推导在每一步生成的对象的类型. 但是,如果调用其他函数,则 Compile 通常不知道返回的值是哪种类型. 如果没有另外指定,则Compile 假定任何其他函数都产生一个近似的实数值. 但是,也可以给出一个明确的模式列表,为匹配特定模式的表达式指定要采用的类型.
定义一个函数,当给定整数参数时会产生整数结果:
编译 x^com[i],假定 com[_] 恒为整数:
这将运算已编译的函数:
Compile 的思想是创建一个函数,来针对某些类型的参数进行优化. 尽管如此,Compile 仍应设置为使其所创建的函数适应于给定的任何参数类型. 当无法使用优化时,将对标准 Wolfram 语言表达式进行求值得到函数的值.
这是一个用于得到变量平方根的编译函数:
如果提供实数参数,则使用优化的代码:
无法使用已编译的代码,因此 Wolfram 语言会显示警告,然后仅运算原始符号表达式:
Compile 生成的编译代码不仅必须对您将提供的参数类型进行假设,还必须假设代码执行期间出现的所有对象类型. 有时,这些类型取决于所指定参数的实际值. 例如,如果 x 不为负,则 Sqrt[x] 对于实数 x 生成实数结果,但如果 x 为负,则生成复数.
Compile 始终对特定函数返回的类型做出确定的假设. 如果在特定情况下执行由 Compile 生成的代码时该假设无效,则 Wolfram 语言将放弃已编译的代码,并运算普通 Wolfram 语言表达式以得到结果.
编译后的代码不期望使用复数,因此 Wolfram 语言必须转为显式评估原始符号表达式:
Compile 的一个重要特征是,它不仅可以处理数学表达式,而且还可以处理各种简单的 Wolfram 语言程序. 例如,Compile 可以处理条件并控制流结构.
在所有情况下,Compile[vars,expr] 均保持其参数不被运算. 这意味着您可以显式给出程序作为要编译的表达式.
这将创建 Wolfram 语言程序的编译版本,该程序实现对平方根的牛顿逼近:
这将执行编译后的代码: