第1章 介绍

来自MathCraftWiki
跳转至: 导航, 搜索

目录


Mathematica 语言是建立在为数不多的几个具有普遍性的原则基础上的。正确理解这些原则是掌握 Mathematica 编程的基本前提。目前已经有一些文献对这些原则作了深入而精辟的讨论[1,2,6-9], 这里我仅对此作一个简短的介绍。

1 第一条原则:任何元素都是一个表达式(Expression)

Mathematica 所处理的每一个对象都是一个表达式(Expression),而每一个表达式,要么是一个被称为原子(Atom)的个体,要么是一个普通的表达式(Normal Expression)。

1.1 原子和内置的 AtomQ 判定函数

原子包括数、符号和字符串,其中数又包括整数、实数、有理数和复数。其它所有对象均为复合表达式,称为普通表达式。通过应用内置断语函数 AtomQ,我们可以检验一个表达式是一个原子还是一个复合表达式。例如

In[X]:=	ClearAll["Globle`*"]
In[X]:=	{AtomQ[x], AtomQ[Sin[x]], AtomQ[1+I*2], AtomQ[2/4]}
Out[X]:=	{True, False, True, True}

1.2 Mathematica 普通(复合)表达式

Mathematica 语言中,每一个普通(复合)表达式都是根据如下的通用模式构建的:

expr[el1, ..., eln]

该模式中必须含有一个符号< expr >和一对单方括号。(符号< expr >本身也可以是一个常规表达式,不必一定要是原子)。方括号内可以没有任何元素,也可以有一个或由逗号分隔开的若干个元素<el1>,...,<eln>。这些元素本身又可以是原子或普通表达式。例如在表达式 Sin[x] 中,符号<expr> 为 Sin,它含有一个元素 <x>,是一个原子(只要 x 到目前还没有做过其他定义的话。这将涉及到一个表达式的计算运行问题,我们会在后面讨论)。由此可见在 Mathematica 语言中,任何一个表达式都具有树的结构,其中树的枝为普通(子)表达式,而其叶则都是原子。

1.3 内置函数的文字表达形式和 FullForm 命令

由于如上所述的结构,任何一个 Mathematica 内置函数/命令,都相应有一个等价的文字表述形式,使得所有表达式都能按这种结构表示为一种统一的形式。我们可以借助内置的 FullForm 函数来看清这一点。FullForm 给出一个对象/表达式的一个内部表达形式,它是真正被内核直接接受的形式。例如:

In[X]:=	{z * Sin[x +y], FullForm[z * Sin[ +y]]}
Out[X]:=	{z Sin[x +y], Times[z, Sin[Plus[x, y]]]}

这里,大括号中的第二个表达式与第一个是等价的,但是它明确地显示了前面描述的结构。 任何函数也都可以用文字形式给出。例如:

In[X]:=	{Plus[1, 2, 3, 4], Times[1, 2, 3, 4]}
Out[X]:=	{10, 24}

1.4 所有的普通表达式都是一个树(tree),TreeForm 命令

普通表达式是一个树这一事实可以通过内置的 TreeForm 命令非常清楚地看出来:

In[X]:=	TreeForm[z * Sin[x +y]]
TreeExample1.jpg

既然有树的结构,我们就可以对一个表达式的子树加注指标或进行访问。在下面的例子中,<expr> 是 Times (乘法命令):

In[X]:=	a = z * Sin[x +y];
In[X]:=	FullForm[a]
Out[X]:=	Times[z, Sin[Plus[x, y]]]

1.5 表达式的头和 Head 命令

一般来说,一个表达式在方括号前面都有一个名称,这个名称称为表达式的头。Mathematica 有一个内置函数叫 Head,它可以用来得到任何表达式的头。例如:

In[X]:=	Head[a]
Out[X]:=	Times

一个表达式的头本身可以是一个原子,也可以是一个普通表达式。例如:

In[X]:=	Clear[b, f, g, h, x];
In[X]:=	b = f[g][h][x];
In[X]:=	Head[b]
Out[X]:=	f[g][h]
In[X]:=	Head[f[g][h]]
Out[X]:=	f[g]
In[X]:=	Head[f[g]]
Out[X]:=	f

每一个表达式都有一个头,即使是原子也一样。原子的头有 String,Symbol,Integer,Real,Rational 以及 Complex。例如:

In[X]:=	{Head[f], Head[2], Head[Pi],Head[3.14], Head["abc"], Head[2 / 3], Head[1 +I]}
Out[X]:=	{Symbol, Integer, Symbol, Real, String, Rational, Complex}

Ÿ

1.6 通过指标来访问表达式的各个部分

通过应用指标({Part 命令}),我们也可以访问一个表达式内部的组成部分(即方括号里面的元素)。这一点可以从下面这个例子中清楚地看到:

In[X]:=	{a0, a1, a2, a2, 0, a2, 1, a2, 1, 0, a2, 1, 1, a2, 1, 2}
Out[X]:=	{Times, z, Sin[x +y], Sin, x +y, Plus, x, y}

(双重方括号是Part 命令的简写形式:

In[X]:=	a2, 1
Out[X]:=	x +y

In[X]:=	Part[a,{2, 1}]
Out[X]:=	x +y


在这个例子中,我们实际上是把一个表达式解构成它的各个组成部分。对照前面由 TreeForm 得到的数图,可以看出我们的步骤是从树干出发,沿着树枝,最后到达叶子。我们也看到所有最后一个元素为零的地址(即指标序列)所对应的都是子表达式的 Heads,这是一个约定。原则上讲,任何一个表达式都可以按此方法进行解构,更重要的是,我们可以对其子表达式进行变更。

1.7 表达式的层级和 Level 命令

利用内置函数 Level 命令,我们还可以进入离树干某一距离上的所有枝(或子表达式)。考虑下面的例子:

In[X]:=	Clear@aD;
In[X]:=	a = z * Sin[x +y] +z1 * Cos[x1 +y1]
Out[X]:=	z1 Cos[x1 +y1] +z Sin[x +y]

它的完全形式是:

In[X]:=	FullForm[a]
Out[X]:=	Plus[Times[z1, Cos[Plus[x1, y1]]], Times[z, Sin[Plus[x, y]]]]

它的树图是:

In[X]:=	TreeForm[a]

那么该树的层级依次为:

In[X]:=	Level[a, {0}]
In[X]:=	Level[a, {1}]
In[X]:=	Level[a, {2}]
In[X]:=	Level[a, {3}]
In[X]:=	Level[a, {4}]
Out[X]:=	{z1 Cos[x1 +y1] +z Sin[x +y]}
Out[X]:=	{z1 Cos[x1 +y1], z Sin[x +y]}
Out[X]:=	{z1, Cos[x1 +y1] z, Sin[x +y]}
Out[X]:=	{x1 +y1, x +y}
Out[X]:=	{x1, y1, x, y}

Level[a, {n}]给出了 a 离树干相距 n 层的所有枝或叶。如果想要得到具有 n 层亚枝或叶的所有的枝,我们则要在层级命令中设定负数层级:Level[a, {-n}]。 如:

In[X]:=	Level[a, {-1}]
In[X]:=	Level[a, {-2}]
In[X]:=	Level[a, {-3}]
In[X]:=	Level[a, {-4}]
Out[X]:=	{z1, x1, y1, z, x, y}
Out[X]:=	{x1 +y1, x +y}
Out[X]:=	{Cos[x1 +y1], Sin[x +y]}
Out[X]:=	{z1 Cos[x1 +y1], z Sin[x +y]}

注意,一般情况下,负数层级是不可能用正数层级来得到的。它们给出的是不同类型的信息。我们这里所描述的,在 Mathematica 中称为标准层级设定(Standard Level Specification)。有许多内置函数将标准层级设定作为一个(往往是可选的)变量。

2 第二条原则:模式匹配和规则替代

另一条基本原则是所谓模式匹配。它是将规则与表达式进行匹配的一个系统。离开这个系统,Mathematica 将无法知道什么时候使用哪一条规则。给系统是按照语法而非语义在表达式间做比较。我们这里的话题主要是和规则与模式相关的。

2.1 重写规则

一个典型的规则具有如下的形式:

a -> b

其中,a 和 b 为 Mathematica 表达式。规则的作用是,一旦遇到 a ,就将其用 b 来替代。 例如:

In[X]:=	{a, c, d, c}/. a -> b
Out[X]:=	{b, c, d, c}

(这里,</.> 是规则替代命令,将在下面的章节中介绍)。

模式可以是任何一个某一部分被"blank"(Blank[])所取代的表达式,这里Blank[]代表的是任意表达式的占位,也就是说这一部分可以被任何东西所取代(这多少有点过度简化)。与Blank[]等价的是单下划线("_")符号。比如,f[x_]意思是f[任何东西]。

2.2 一个简单的模式定义的函数的例子

In[X]:=	Clear[f]
In[X]:=	f[x_]:=x^2;
In[X]:=	{f[2],f["wood"],f[Newton]}
Out[X]:=	{4,word^2,Newton^2}

在这个例子中,产生的结果遵循了函数f的定义,即f[任何东西]被替换成(任何东西)^2。

2.3 函数就是规则:DownValues命令

要看清这个规则的内部形式——即它是如何存储到规则库里的——我们可以借助内置的DownValue命令。在它的帮助下我们可以看到:

In[X]:=	DownValue[f]
Out[X]:=	{HoldPattern[f[x_]]:>x^2}

HoldPattern函数的作用我们会在后面提到。这里的模式x_是最简单的一种模式。模式可以变得很复杂,不仅仅是语法层面上,模式还可以附加条件,这样就可以使得在条件得到满足的前提下模式才能得到匹配,这种模式又称条件模式。这一点我们会在之后的章节里详细提到。

2.4 一个基于限制模式的函数的例子

现在来看一个例子:我们限制函数f只能作用在整数上:

In[X]:=	Clear[f];
In[X]:=	f[x_Integer]:=x^2;
In[X]:=	{f[2],f[Pi],f["word"],f[Newton]}
Out[X]:=	{4,f[Pi],f[word],f[Newton]}

在这个例子中,我们引入了一个稍复杂的模式x_Integer。

2.5 关于求值

在这个例子中,我们可以看到如果规则中的模式部分(规则等号左边的那部分)未能匹配到任何表达式的话,Mathematica 就会原封不动地返回这个表达式。这牵涉到它的求值方法的一个核心部分:对于任何输入的表达式而言,在当前时刻全局规则库中的所有规则会反复作用到表达式上。当其中某条规则作用到表达式上后,表达式会被重写,之后整个过程再次重复。到某个时候,当没有一条规则能够再作用到表达式上后,此时的表达式就是最终结果。由于规则库同时包括系统内置规则和用户自定义规则(后者有更高的优先级),因而我们在操纵表达式上更加得心应手。

2.6 通过模式给同一个函数添加多个定义

现在我们来看另一个简单的例子,我们定义一个函数f,它对于偶数是线性的,对于奇数是二次的,对于剩下的其他数字则表示为正弦函数:

In[X]:=	Clear[f];
In[X]:=	f[x_?EvenQ]:=x;
In[X]:=	f[x_?OddQ]:=x^2;
In[X]:=	f[x_]:=Sin[x];


下面是这个函数在不同输入下的输出结果:

In[X]:=	{f[1],f[2],f[3],f[4],f[3/2],f[Newton],f[Pi]}
Out[X]:=	{1,2,9,4,Sin[3/2],Sin[Newton],0}

在这里,内置函数OddQEvenQ是一个判定函数,前者对输入是奇数的情况返回True,对输入是偶数返回False:

In[X]:=	{EvenQ[2],EvenQ[3],OddQ[2],OddQ[3]}
Out[X]:=	{True, False, False, True}

如果输入是一个不明确的东西,返回值将会是False:

In[X]:=	EvenQ[Newton],OddQ[Newton]
Out[X]:=	{False, False}

2.7 规则替换的不可交换性

现在我们来看一下上面这个例子中函数f三个定义中的最后一个。它的意思是所有的输入都会变成它的正弦值。起初我们会以为我们会得到所有输入的正弦值作为输出,但这种情况并没有发生。这是因为顺序式的规则应用是不具有交换性的:首先,在每一次规则应用的过程中,一旦找到第一条规则可以匹配的部分后,只有这一条规则会被应用,而其余的规则即使可以匹配也不会被应用到(子)表达式上。其次,如果有很多规则都能匹配同一个表达式,那么只有排在第一位的规则会改写表达式,这样的结果是其他(一些)表达式不再能匹配这一被改写后的表达式。因此我们可以看出输出结果取决于规则被应用的先后顺序。Mathematica 会依次应用规则。由于正弦的定义排在最后,这意味着它仅仅能够作用于那些不满足前两条规则的输入。

2.8 规则自动重排

如果我们把上一个例子中的规则定义的顺序换一下,得到的仍然是相同的结果:

In[X]:=	Clear[f];
In[X]:=	f[x_]:=Sin[x];
In[X]:=	f[x_?EvenQ]:=x;
In[X]:=	f[x_?OddQ]:=x^2;
In[X]:=	{f[1],f[2],f[3],f[4],f[3/2],f[Newton]}
Out[X]:=	{1, 2, 9, 4, Sin[3/2], Sin[Newton]}

为了了解规则储存的顺序,我们再次用到DownValues函数:

In[X]:=	DownValues[f]
Out[X]:=	{HoldPattern[f[x_?EvenQ]]:>x, HoldPattern[f[x_?OddQ]]:>x^2, HoldPattern[f[x_]]:>Sin[x]}

我们发现尽管我们第一个定义正弦规则,但它仍然排在最后。这是因为Mathematica 规则替换引擎用内置的规则分析器来给规则排序,更一般的规则被排在更细致的规则之后,前提是分析器要能够判断哪个规则更一般。但这一般来说不可能自动完成,更别说要消除歧义,因而编程者应当注意这一点。但在实际应用中,很少需要手动给规则排序。

3 第三条原则:表达式求值

上一个例子使我们来到第三条原则:表达式求值以及改写规则(全局规则库)原则。它其实是这样的:当Mathematica 遇到某一个表达式时,它首先会在全局规则库里寻找匹配某一段表达式的规则。一个典型的改写规则看上去是这样的:对象1->对象2。如果能找到这样一个规则,对于这条表达式或它的子表达式(其实是先从子表达式开始),(子)表达式会被重写,然后这一过程再次重复。这一过程会一直进行下去,直到全局规则库中没有一条规则能够匹配这条表达式或它的任何一个部分。当表达式无法再发生变化时,它就被当作结果返回给用户。请记住这一流程其实已经被极度简化,真正的求值过程要更加细致琐碎,但主要的思路就是这样的。 全局规则库中同时包含内嵌进核的规则以及用户自定义的规则。用户定义的规则优先级比系统自带规则更高,这使得在必要情况下我们可以重新定义几乎所有的内置函数。事实上,所有对变量的赋值以及对函数的定义都被以某种全局规则的形式储存起来。从这种意义上来说,函数和变量之间其实没有本质上的区别(虽然在作用上还是有区别的)。
这样的结果就是,我们会得到如下的结果:

In[X]:=	FullForm[Sin[Pi+Pi]]
Out[X]:=	0

产生这个结果的原因在于在核内部有像Plus[x,x]->2x,Sin[2*Pi]->0这样的规则,而且由于求值过程默认是从最内部(叶)开始的,因而在FullForm函数有机会“看到“表达式之前0已经产生了。内部的求值过程可以用Trace命令查看:

In[X]:=	Trace[FullForm[Sin[Pi+Pi]]]
Out[X]:=	{{{Pi+Pi, 2Pi}, Sin[2*Pi], 0}, 0}

4 小结

在这一章中,我们简要介绍了Mathematica 的主要运行原理,以及给出了以下内置函数的一些例子:AtomQHeadFullFormTreeFormLevelPlusTimesTraceDownValuesOddQEvenQ

个人工具
名字空间

变种
操作
导航
FAQ
项目
工具