模块化和事物的命名

模块和局部变量
Wolfram 语言一般假设变量是全局变量. 即每次使用 x 等名字时,Wolfram 语言总认为在调用同一对象.
然而在编程时,不需要将所有变量都作为全局变量. 例如,在两个不同的程序中,x 可用来指代两个不同的变量. 此时,每个程序中的 x 都必须作为局部变量.
在 Wolfram 语言中可以用 modules 定义局部变量. 在每个模块中,可以给出模块中涉及的局部变量列表.
Module[{x,y,},body]
具有局部变量 xy 的模块
在 Wolfram 语言中产生模块.
定义全局变量 t,其值为 17:
t 在模块内,所以其处理与全局变量 t 无关:
全局变量 t 的值还是17:
模块的最常用方法是在自定义函数中建立临时或中间变量. 一定要保证这些变量只是局部的,否则当这些变量名与其他变量名重合成就会引起麻烦.
中间变量 t 是模块中的局部变量:
运行函数 f
全局变量 t 的值还是17:
在模块中可以像处理其他符号一样处理局部变量. 例如可以作为局部函数的名字来用,可以对其赋于属性等.
构造了一个建立局部函数 f 的模块:
局部函数 f 是一般的阶乘:
f 是一个推广的阶乘:
在一个模块中定义局部变量时,Wolfram 语言开始并不对其赋值. 即使在模块外定义了该变量的全局值,都能以纯符号的方式使用该变量.
使用 t 在前面定义的全局值,结果是一个数:
Length 得到变量的个数:
由于局部变量 t 没有赋值,因此被当作符号处理,Expand 产生一个预期的代数结果:
Module[{x=x0,y=y0,},body]
局部变量有初始值的模块
对局部变量赋初值
局部变量 t 的初始值为 u
使用 g 的定义:
对模块中任何一个局部变量都可以定义初始值. 这些初始值总是在模块执行之前进行计算. 结果,即使定义了 x 是模块的局部变量,在赋初始值时可以用全局变量 x 的值.
u 的初始值为全局变量 t 的值:
lhs:=Module[vars,rhs/;cond]
rhscond 中共用局部变量
在条件定义中使用局部变量
在定义 /; 时经常需要引入临时变量,并且定义的右端也需要使用这些临时变量. Wolfram 语言允许将定义的右端和条件包含在模块之中.
定义具有条件的函数:
Wolfram 语言在条件和右端项中共用局部变量 t 的值:
局部常量
With[{x=x0,y=y0,},body]
定义局部常量 x, y,
定义局部常量.
Module 中可以定义局部变量,这样我们可以对其赋值并且改变其值. 然而,通常我们所需要的是局部常量,对其我们仅需要赋值一次. Wolfram 语言中的 With 结构可以建立局部常量.
定义 t 的全局值:
定义把 t 作为局部常量的函数:
使用定义 w:
t 仍然具有其全局值:
与模块 Module 中的情况相似,用 With 定义的初始值在 With 执行之前进行计算.
给局部常量 t 赋值的表达式 t+1t 的全局值进行计算:
With[{x=x0,},body] 的工作方式是取 body,并用 x0 代替 x,等等. 可以将 With 理解为 /. 运算的推广,可适用于 Wolfram 语言的代码.
使用 a 替换 x
替代以后,With 的内容是 a=5,故 a 得到了全局值 5
清除 a 的值:
在某种意义上,With 类似于局部变量仅赋值一次的 Module 的特殊形式.
With 而不用 Module 的主要原因之一是使 Wolfram 语言程序容易理解. 在模块中的某一处遇到局部变量 x 时很可能需要跟踪整个模块的代码来得到 x 在该处的值. 而在 With 结构中,只需要观察初始值列表就能得到局部常量的值.
有几个 With 结构时,总是某一个变量最里面的一个起作用. 可以将 ModuleWith 混用. 一般原则是某一变量最里面的一个起作用.
在嵌套 With 结构中,总是最里面的一个起作用:
可以将 ModuleWith 结构混用:
当名字不重合时,结构内的局部变量不屏蔽结构外的变量:
除了 xbody 在何时计算外,With[{x=x0,},body]body/.x->x0 的做法基本类似. 然而,当表达式 body 内部都包含 WithModule 结构时, With 显示特殊的状态. 主要的问题是防止不同 With 结构中的局部常量相互冲突,或与全局目标冲突. 有关细节将在 "模块工作方式" 节讨论.
With 内部的 y 被重新命名以防止其与全局变量 y 冲突:
模块工作方式
Wolfram 语言中模块的基本工作方式非常简单. 任何模块每一次使用时,就产生一个新符号去代表每一个局部变量. 新符号的名字被唯一地给定,不能跟任何其其名字冲突. 命名的方法是在给定的局部变量后加 $,并给出唯一的序号.
从全局变量 $ModuleNumber 的值可以找到序列号. 该变量计算 Module 的任何形式所使用的总次数.
Module 中产生形如 x$nnn 的符号去代表每个局部变量.
Wolfram 语言中模块的基本原理.
说明模块内所产生 t 的符号:
任何模块每一次运行时产生的符号不同:
在绝大部分情况下,不需要直接涉及模块内产生的实际符号. 但在一个模块的执行过程中打开对话时就会看到这些符号. 同样,用函数如 Trace 等也能观察模块的计算.
Trace 观察模块内部产生的符号:
在模块中打开对话:
在对话内看到为 t 等局部变量产生的符号:
像其他符号一样来处理这些符号:
从对话返回:
在某些情况下,明确返回在模块中产生的符号是很方便的.
可以明确地返回在模块中产生的符号:
把这些符号像其他符号一样处理:
Unique[x]
产生形如 x$nnn 唯一名称的新符号
Unique[{x,y,}]
产生一个新符号表
通过唯一的名称产生新符号.
函数 UniqueModule 一样产生新符号. 每次调用 Unique 时,$ModuleNumber 增加,故可保证新符号的名称不重复.
产生唯一的名称以 x 开头的新符号:
每调用一次 Unique 就得到一个序列号更大的符号:
对一个集合调用 Unique 时,得到每个符号有相同的序列号:
可以用标准 Wolfram 语言?name 机制得到在模块中或用函数 Unique 产生符号的信息.
执行这一模块产生符号 q$nnn
看到所产生的符号:
模块 Module 所产生的符号与计算中的符号性能相同. 但这些符号具有 Temporary 属性,当不再使用时会被系统删除,所以在模块内产生的符号当模块执行完时就被删除. 那些明确返回的符号才能继续存在.
显示在模块内产生的新变量 q
新变量在模块执行结束时被删除,所以这里不再出现:
应该意识到对所产生的符号用 x$nnn 等符号名完全是一种约定. 一般地,可以对任何符号用这类符号,但这样做时可能会与模式 Module 所产生的符号重合.
重要的一点是由模式 Module 所产生的符号的唯一性仅仅在 Wolfram 语言的一个进程中是唯一的. 决定符合序列数的变量 $ModuleNumber 在每个进程的开始时总是重新设置.
这意味着将含有所产生符号的表达式存在一个文件中,然后在另一个进程中打开该文件时无法保证不冲突. 在实践中,这是不太可能发生的,因为生成的符号通常是临时的. 处理冲突的一种可能方法是,在读入带有临时符号的文件会话开始时,手动将 $ModuleNumber 设置为一个较大的值,例如 2^($SystemWordLength-8).
在产生了决定局部变量的符号后,Module[vars,body] 就用这些符号计算 body. 首先取模块中出现的 body 的实际表达式,用 With 将每个局部变量的名称用所产生的符号代替,然后模块 Module 来计算结果中表达式的值.
重要的是 Module[vars,body] 仅将所产生的符号代入 body 的实际表达式中. 但不把这些符号代入 body 所调用的但不直接出现在 body 中的代码中.
"块和局部值" 节将讨论如何用 Block 去建立以不同方式工作的局部值.
由于 x 没有直接出现在模块的body中,故没有局部值:
大部分情况下,通过 Module[vars,body] 的直接输入来在 Wolfram 语言中建立模块. 由于 Module 函数具有属性HoldAll,所以在模块执行之前 body 将维持不计算的状态.
在 Wolfram 语言中还可以建立动态模块. 新符号的产生以及向 body 中的代入总是在模块执行时进行,而不是在模块作为 Wolfram 语言输入建立时进行.
立即计算模块的内部,直接给出 x 的值:
纯函数和规则中的变量
ModuleWith 可以给出作为局部处理的符号名序列. 但有时需要直接将某些变量名进行局部处理.
例如,在使用 Function[{x},x+a] 等纯函数时,x 是一个形式参数,这个名称是局部的. 在规则 f[x_]->x^2 或定义 f[x_]:=x^2 中出现的 x 也是这样.
Wolfram 语言用统一的方法去保证在纯函数和规则中出现的形式参数是局部的,且不与全局变量混淆. 其基本的思想是必要时用形如 x$ 的符号去代替形式参数. 作为一个约定,x$ 从不用作全局变量名.
一个纯函数的嵌套:
Wolfram 语言重新命名函数内的形式参数 y 以避免与全局对象 y 冲突:
得到的纯函数与所期望的一样:
一般来说,在对象如 Function[vars,body] 中的形式参数当另一个纯函数修改了 body 时 Wolfram 语言就要重新命名.
由于纯函数内发生了变化,形式参数 y 就被重新命名:
当函数内部没有变化时,形式参数就不重新命名:
Wolfram 语言在对纯函数中的形式参数重命名时比较自由. 原则上,函数中的形式参数与代换到纯函数中表达式的项不冲突时可以不用重新命名. 但为了一致起见,在这种情况下,Wolfram 语言还是对形式参数重新命名.
这时在函数内的形式参数 x 屏蔽了函数体,故不需要进行重命名:
三重函数嵌套:
这种情况下两个内层函数都重新命名:
正如 "纯函数" 所述,Wolfram 语言中纯函数类似于形式逻辑中的 表达式. 对形式参数重新命名使 Wolfram 语言纯函数再次产生标准 表达式的所有语法.
Function[{x,},body]
局部参数
lhs->rhs
and
lhs:>rhs
局部模式名
lhs=rhs
and
lhs:=rhs
局部模式名
With[{x=x0,},body]
局部常数
Module[{x,},body]
局部变量
Wolfram 语言中的定界结构.
Wolfram 语言中有一些 定界结构,其中某些名称作为局部变量处理,当这些结构混合时,Wolfram 语言进行适当的重命名以避免冲突.
Wolfram 语言重新命名纯函数中的形式参数以避免冲突:
With 内的局部参数被重新命名以避免冲突:
变量名之间没有冲突,故没有进行重新命名:
在模块中局部变量 y 重新命名以避免冲突:
执行模块时,局部变量又一次重新进行命名,以使得名称唯一:
Wolfram 语言将变换规则当定界结构处理,其中模式的名称是局部的. 可以用 x_x__ 等或 x:patt 建立命名的模式.
h 中的 xx_ 相同,在规则中作为局部量:
f[x_]->x+y 等规则右端出现的 x 与名为 x_ 的模式匹配. 于是,x 被当作规则的局部量处理,不能用其他定界结构去修改.
另一方面,y 在规则中不是局部量,可以用其他定界结构去修改. 当这种情况发生时,Wolfram 语言重新对规则中的模式命名以防止冲突.
Wolfram 语言对规则中的 x 重新命名以防止冲突:
在一个定界结构中使用 With 时,Wolfram 语言就自动地进行适当的重新命名. 但有时需要在定界结构中进行代换以免重命名. 这可以用 /. 运算实现.
当用 With 代替 y 时,纯函数中的 x 被重新命名以防止冲突:
当使用 /. 而不是 With 时,没有进行这类重新命名:
当使用规则如 f[x_]->rhs 或定义如 f[x_]:=rhs 时,Wolfram 语言必须间接地替换出现在表达式 rhs 中的 x,用 /. 运算有效地完成此项工作. 于是,这些替换不遵循定界结构. 然而,当定界结构的内部由替换修改后时,在定界结构中的其他变量被重新命名.
定义一个产生纯函数的函数:
xx^2 通过使用 /. 运算被简洁地插入纯函数之中:
定义产生一对嵌套函数的函数:
纯函数外的 x 被重新命名:
数学中的哑元
当建立数学公式时,需要引入各种局部的对象或哑元. 可以用模块或其他 Wolfram 语言定界结构处理这样的哑元.
积分变量是数学中哑元的常用例子. 当给出一个形式的积分时,约定的记号需要引入一个具有确定名的积分变量. 这个变量对积分而言是局部的,其任何名称不能与数学表达式中的其他名称冲突.
定义计算积分的一个函数:
s 与积分变量冲突:
一个定义,其中积分变量是模块中的局部变量:
由于使用了模块,Wolfram 语言自动重新给积分变量命名以避免冲突:
在许多情况下,最重要的是将哑元作为局部变量,并不干扰表达式中的其他变量. 有时,同一哑元的不同方式的使用也不应该冲突.
重复的哑元经常出现在向量和张量积之中. 根据 加法约定,恰好出现两次的向量或张量的下标应该将所有可能的值相加. 重复的下标的实际名称不起作用,但当有两个相分离的重复下标时,必须保证其名称不冲突.
将重复下标 j 作为哑元:
模块对不同位置出现的哑元赋以不同的名称:
数学中许多情况下均需要变量有唯一的名称. 例如方程的解. 方程如 有无穷多解 ,其中 是任何整数的哑元.
当 Wolfram 语言求解以下方程时,创建一个哑元:
使哑元唯一的方法:
另一个需要目标具有唯一性的地方是 积分常数 的表示. 积分时是解一个微分方程. 一般地,这有无限个解,每个仅差一个 积分常数. Wolfram 系统的标准函数 Integrate 总是得到一个没有积分常数的解. 但是,要引入积分常数时,必须用模块保证积分常数总是唯一的.
块和局部值
Wolfram 语言中的模块使变量名具有局部性,但有时需要名是全局的,值是局部的,这可以用 Wolfram 语言中的 Block 来实现.
Block[{x,y,},body]
x,y, 的局部值计算 body
Block[{x=x0,y=y0,},body]
x,y, 赋初值
设置局部值.
涉及 x 的一个表达式:
x 的局部值计算以前的表达式:
x 没有全局值:
如前一节 "模块和局部变量" 所述,在模块如 Module[{x},body] 中的变量 x 总是有一个唯一符号,模块每次调用这符号不同,且与全局符号 x 也有区别. Block[{x},body] 中的 x 是一个全局的符号 x. 这个块的作用是让 x 有局部值. 进入这个块时,x 的值在退出该块时恢复. 而在块执行时,x 可以取任意值.
给符号 t 设置值 17
模块中的变量有唯一的局部名:
在块中,变量有全局名,但有局部值:
t 在块中给出了一个局部值:
当块执行结束时,t 恢复以前的值:
Wolfram 语言中的块设置了可以暂时改变值的一种 环境. 在块执行过程中,用在块中所定义的变量的当前值计算表达式,不论表达式是该块的一部分或是在计算中某一处产生的情况都是如此.
定义符号 u 的一个延时值:
在块之外计算 u 时,t 的全局值被使用:
可以指定 t 的临时值在块中使用:
Wolfram 语言中的 Block 间接使用在 DoSumTable 等的递推结构中. 在所有这些结构中,Wolfram 语言利用 Block 建立了迭代递推变量的局部值.
Sum 自动使递推变量 t 是局部值:
在递推结构中的局部变量比在 Block 中的更一般一些. 他们处理 a[1]、纯符号等变量:
在 Wolfram 语言中定义函数时,用不直接给出变量,但能影响函数的全局变量是方便的. 例如,Wolfram 语言有一个全局变量 $RecursionLimit,其影响所有函数的计算但又不直接是函数的变量.
Wolfram 语言通常将所定义的全局变量的值保持到明显的改变为止,但也需要仅在一个计算过程中或某一项的计算中有效的值,这可以通过将其设置为 Wolfram 语言块的局部值来实现.
定义依赖于 全局变量 t 的函数:
使用 t 的全局值:
在块内,可以设置 t 的局部值:
全局变量不仅可以用来设置函数的参数,还可以积累从函数得到的结果. 在块中设置这样的局部变量,可以积累在这个块执行过程中从所调用函数得到的结果.
此函数增加了全局变量 t,返回的是其当前值:
如果不用块,计算 h[a] 时改变全局变量 t 的值:
使用了块后,仅局部变量 t 被影响:
全局变量 t 没有改变:
当输入块如 Block[{x},body] 时,x 的所有值被删除,这意味着在这个块内,可以把 x符号变量 来处理. 然而,从块明确地返回了 x 以后就被在块外计算时产生的值所代替.
当输入块时,t 的值被删除:
当返回含有 t 的表达式时,就用 t 的全局值进行计算:
块与模块的比较
当进行 Wolfram 语言编程时,应当尽量使其项相互独立,这样程序就容易理解、维护和扩充.
保证程序中不同相相互不影响的一个重要途径是给其变量一定的 范围. Wolfram 语言用模块和块这两种机制来限制变量的范围.
在实际编程时,模块远远比块常用,而在相互作用的计算中需要确定范围时,往往是块比较方便.
Module[vars,body]
词汇(lexical)定界
Block[vars,body]
动态定界
Wolfram 语言变量的定界机理.
大部分计算机语言使用与 Wolfram 语言模块类似的 词汇定界 机理. 一些像 LISP 等符号计算语言与 Wolfram 语言块类似的 动态定界 机理.
在使用词汇定界时,变量在一个程序中的一个代码段被作为局部变量. 在动态定界时,在程序执行历史的一部分被作为局部值.
在 C 和 Java 等编译语言中,其变量在使用之前就要声明类型,因此在编译前就已经确定了变量的类型;代码执行历史 之间的区分非常明显. 而 Wolfram 语言属于动态类型语言,其符号特性使这个区别不明显,其原因是代码在程序的执行过程中可以动态地生成.
Module[vars,body] 的作用是在模块作为 Wolfram 语言的代码被执行时处理表达式 body 的形式,并检查表达式,当任何 vars 明显地出现在代码中时,就被当作局部变量. 然后继续正常计算.
Block[vars,body] 不注意表达式 body 的形式. 而是,记录 vars 的当前值. 在 body 的全局计算过程中使用 vars 的局部值. 并且当计算 body 完成时,存储他们的初始值.
通过 i 来定义 m
在块内 i+m 的计算过程中,i 用了局部值:
明显出现在 i+m 中的 i 被当作局部变量处理:
上下文
总是给变量或者定义选用尽可能清楚的名称是一个好思想. 但这样做有时会导致变量名很长.
在 Wolfram 语言中,可以用 上下文 来组织符合名. 在引入与其他符号不冲突的变量名的 Wolfram 语言程序包中上下文特别有用. 在编写或者调用 Wolfram 语言程序包时,就需要了解上下文.
其基本的思想是任何符号的全名为两部分:上下文短名. 全名被写为 context`short,其中 ` 是倒引号或重音符字符( ASCII 二进制代码 96),在 Wolfram 语言中称为 上下文标记.
具有短名 x 和上下文 aaaa 的符号:
可以像其他符号一样使用这一符号:
可以定义这个符号的值:
Wolfram 语言将 a`xb`x 当作两个完全不同的符号:
典型的情况是让与一个特殊的主题相关的符号有相同的上下文. 例如,表示物理单位的符号具有上下文PhysicalUnits`. 这类符号的全名可能是 PhysicalUnits`Joule 或者 PhysicalUnits`Mole.
尽管总可以用全名来代表一个符号,但是用短名常常很方便.
在 Wolfram 语言进程中的任何点,总有一个当前上下文 $Context. 可以用短名简单地指代这个上下文中的符号,除非该符号被 $ContextPath 中具有相同短名的符号屏蔽. 如果具有给定短名的符号在上下文路径中存在,其将被使用,而不是当前上下文中的符号被使用.
Wolfram 语言进程的默认上下文是 Global`
$ContextPath 中不存在名为 x 的符号,因此在当前上下文中用短名指代符号是足够的:
上下文在 Wolfram 语言中的作用在某种程度上类似于许多操作系统的文件目录,可以通过路径和全名指定一个文件. 但在任何点,总有一个当前目录,这类似于 Wolfram 语言的当前上下文. 在当前目录下的文件就可以仅用其短名指定.
与许多操作系统中的目录一样,Wolfram 语言中的上下文具有启发式(分层)的特性. 例如,符号的全名可以涉及到一系列形如 c1`c2`c3`name 的上下文名.
context `name
or
c1`c2
`name
在明确指定的上下文中的符号
`name
当前上下文中的符号
`context`name
or
`c1`c2
`
`name
与当前上下文相关的在一个指定上下文中的符号
name
在当前上下文或者在上下文搜索路径中的符号
在不同上下文中指定符号.
在上下文 a`b` 中的符号:
开始了一个 Wolfram 语言进程后,默认当前上下文是 Global`. 引入的符号通常就在这个上下文中. 但是,内部符号Pi 等在上下文 System` 中.
为了方便地处理 Global`System` 中的符号,Wolfram 语言支持上下文搜索路径. 在 Wolfram 语言进程中的任一点,有当前上下文 $Context 和当前上下文搜索路径 $ContextPath. 搜索路径的意思是在输入一个符号的短名后,Wolfram 语言在一系列上下文中搜索去找到有这个短名的符号.
Wolfram 语言中的上下文搜索路径与操作系统提供的程序文件的 搜索路径 相似. 由于 $Context 是在$ContextPath 之后搜索的,我们可以认为其在文件搜索路径后添加 ..
默认的上下文路径包括系统定义符号的上下文:
当输入 Pi 时,Wolfram 语言将其翻译为具有全名 System`Pi 的符号:
Context[s]
一个符号的上下文
$Context
在 Wolfram 语言进程中的当前上下文
$ContextPath
当前上下文搜索路径
$ContextAliases
上下文的别名列表
Contexts[]
所有上下文组成的集合
找出上下文和上下文搜索路径.
在 Wolfram 语言中使用上下文时,在两个不同的上下文中两个符号可以有相同的短名. 例如,在上下文 PhysicalUnits`BiologicalOrganisms` 中都可以使用短名 Mole 的符号.
于是,在输入短名 Mole 后,就产生了用户实际上调用了哪一个符号的问题. 解决这个问题时要弄清楚在上下文搜索路径中哪一个上下文先出现.
引入两个具有 Mole 短名的符号:
$ContextPath 添加了两个上下文. 通常,Wolfram 语言在 $ContextPath 开头添加新的上下文:
输入 Mole,得到在上下文 PhysicalUnits` 中的符号:
一般地,当输入一个符号的短名后,Wolfram 语言认为用户需要在上下文搜索路径中最早出现的上下文中的符号. 结果,上下文出现晚的符号或者在当前上下文中具有相同短名的符号将被屏蔽,为了调用这些符号必须使用全名.
在引入的新符号 屏蔽 了当前 $ContextPath 中已经存在的符号时,Wolfram 语言就会发出警告. 另外,如果在笔记本前端,Wolfram 语言就提示用户对屏蔽的符号使用红色.
Global` 上下文中引入了有短名 Mole 的符号,Wolfram 语言警告新符号屏蔽了已经存在的短名为 Mole 的符号:
如果输入 Mole,就会得到上下文路径 PhysicalUnits` 的符号:
如果引入的符号屏蔽了已经存在的符号,则需要重新安排 $ContextPath,或者直接删去这个符号. 用户应该意识到,仅清除这个符号的值是不够的,必须从 Wolfram 语言中全部删除这个符号. 这可以用函数 Remove[s] 来实现.
Clear[s]
清除一个符号的值
Remove[s]
从系统中完全删除一个符号
清除或删除 Wolfram 语言中的符号.
删除符号 PhysicalUnits`Mole:
输入 Mole 后,就会得到符号 BiologicalOrganisms`Mole
当 Wolfram 语言显示一个符号名时,必须选择区显示全名或者短名. 其所做的是给出所有用户应该输入的名称以得到特定的符号,给出当前 $Context$ContextPath 的设置.
对第一个符号先显示短名,这就是输入短名后应该得到的符号:
当输入了一个短名,其既不在当前上下文内,也不在上下文的搜索路径内,Wolfram 语言就产生一个具有该短名的新符号,且总是把新符号放在由 $Context 指定的当前上下文中.
引入具有短名 tree 的符号:
Wolfram 语言在当前上下文 Global` 内增加了 tree
上下文和程序包
典型的 Wolfram 语言程序包引入一些能在这个包之外使用的符号. 这些符号可能与在包中定义的新函数或目标相对应.
约定在一个程序包中引入的新符号放在名与该程序包的相关的上下文内. 在这个包中阅读时,这个上下文将加在上下文搜索路径 $ContextPath 的开头.
为证明 primality,读入一个程序包:
这个包将上下文添加在 $ContextPath 之前:
符号 ProvablePrimeQ 在程序包设置的上下文之中.
可以用短名来指代这个符号:
在一个程序包中定义的变量的全名往往很长,大部分情况下,只需要用其短名即可,原因是在读入这个程序包时,其上下文被加在 $ContextPath 之中,当敲入短名后就会自动搜索这个上下文.
当同一个短名出现在两个不同的程序包中时就变得比较复杂,这时当读入第二个程序包时,Wolfram 语言就会发出警告,告诉我们哪一个符号在引入新符号时被屏蔽.
PrimalityProving` 中的符号 ProvablePrimeQ 被新程序包中的有相同短名的符号所屏蔽:
可以通过全名调用被屏蔽的符号:
冲突不仅在不同程序包的符号间发生,而且也在程序包的符号与 Wolfram 语言的进程中引入的符号间发生. 在当前上下文中定义了符号后,这个符号将被从程序包中读入的短名相同的符号所屏蔽,其原因是 Wolfram 语言总是先在上下文搜索路径中寻找符号,然后才在当前上下文中寻找符号.
在当前上下文中定义了一个函数:
此函数考虑复数标量:
任何其他名为 ScalarQ 的函数将被再当前上下文中的函数屏蔽:
使用程序包中的 ScalarQ
当不需要的符号屏蔽了所需的符号时,最好的途径是用 Remove[s] 去删除这个不需要的符号. 有时另一个合适的选择是去重新排列 $ContextPath 中的元素,重新设置 $Context 的值,以便包含所需符号的上下文先出现.
$Packages
一个对应于 Wolfram 语言进程中加载的所有程序包的上下文集合
给出一个程序包集合.
Wolfram 语言程序包
Wolfram 语言的一个重要特性在于其是一个可扩充系统. Wolfram 语言中已建立了一定数量的数学和其他功能. 然而,使用 Wolfram 语言,能添加更多的函数.
对许多种运算,Wolfram 语言标准版中建立的函数已经足够用了. 然而,当用户在一个特殊专业领域中讨论时,会需要使用特定函数,而这在 Wolfram 语言中是没有的.
在这种情况下,用户或许能在 Wolfram 语言程序包中找到所需的函数. Wolfram 语言程序包是用 Wolfram 语言写成的文件. 这些文件是由 Wolfram 语言定义集合组成,使 Wolfram 语言能进行特殊应用领域的工作.
<<package
读入一个 Wolfram 语言程序包
读入 Wolfram 语言程序包.
如果要使用某个程序包中的函数,必须首先把程序包读入 Wolfram 语言中. 具体做法将在 "外部程序" 节讨论. 使用软件包时有许多规定.
这个命令读入一个特定的 Wolfram 语言程序包:
该程序包中定义了 ProvablePrimeQ 函数:
在不同程序包的函数名之间有许多矛盾. 这些将在 "上下文和程序包" 节讨论. 需要注意一点,在读入某个程序包之前不要使用名称与该程序包中的函数名相同的函数. 如果你错误地这样做了,Wolfram 语言将会发出一条警告信息并且使用最后定义的名称. 这意味着你定义的函数版本将不会被使用;而其将从程序包中得到. 必须执行命令Remove["name"] 来取消程序包函数.
Remove["name"]
取消用户自定义的函数
Wolfram 语言使用程序包中定义的函数.
Wolfram 语言通过使用程序包能被扩充的事实说明 Wolfram 语言部分 的界限是模糊的. 从用法上来说,程序包中定义的函数与 Wolfram 语言的内部函数没有任何区别.
事实上,本书中所介绍的许多函数实际上是 Wolfram 语言程序包中的函数. 而在大多数 Wolfram 语言安装中,必须的程序包已经被预先装入了. 所以,其中定义的函数总是存在的.
为了进一步淡化 Wolfram 语言的边界, "包的自动调入" 节介绍了如何使 Wolfram 语言自动装入特定包. 如果用户从未使用过该包中某个函数,那么该函数是不在 Wolfram 语言中的,但是一旦用户要使用,就被从程序包中读入Wolfram 语言中.
从实际情况看来,被认为是 Wolfram 语言部分 的函数或许是那些在所有 Wolfram 语言安装中都有的函数. 本节讨论的也正是这些函数.
然而,Wolfram 语言的大多数版本都具有标准的 Wolfram 语言程序包集合,其中包含了更多的函数. 其中一些函数将来都会提到. 要使用这些函数,用户通常需要明确读入必要的程序包.
用户可以使用参考资料中心来获得 Wolfram 语言标准附加程序包的信息:

19.gif

当然,能够设置 Wolfram 语言,使得特定的程序包被预先装入或当用户需要的时候自动装入. 如果这样设置了,那么将有许多函数作为标准函数出现在所使用的 Wolfram 语言版本中,不过没有在 Wolfram 语言参考文档中出现.
有一点要提到的是程序包和笔记本之间的关系. 二者都是作为文件储存在计算机系统中,都能被读入 Wolfram 语言中. 但是,笔记本是要被显示的,而程序包只是作为 Wolfram 语言的输入. 事实上,许多笔记本包含了被认为是程序包的段落,其中包含着打算作为 Wolfram 语言输入的定义序列. 另外,还可以建立程序包来自动维护笔记本.
建立 Wolfram 语言程序包
在一个典型的 Wolfram 语言程序包中,一般引入两种类型的新符号,一种是输出为外部程序包使用的符号,另一种仅在本程序包中使用的符号. 可以将这两种类型的符号放入不同的上下文以便区分.
通常的约定是将用作输出的符号放在名为 Package` 的上下文内,这与程序包的名称相对应. 当这个程序包读入时,其将上下文加到上下文搜索路径中去,所以在这个上下文中的符号可以通过短名调用.
仅在程序包内使用的符号习惯上放在名为 Package`Private` 的上下文内. 这个上下文没有加入到上下文的搜索路径中去. 于是,这些符号只有通过全名才能调用.
Package`
用于输出的符号
Package`Private`
仅在内部使用的符号
System`
Wolfram 语言中的内部符号
Needed1`
,
Needed2`
,
在程序包中需要的其他上下文
Wolfram 语言程序包中使用上下文的约定.
有一个标准的 Wolfram 语言指令序列用来设置程序包中的上下文. 这些指令设置 $Context$ContextPath 的值以便新引入的符号放在适当的上下文之中.
BeginPackage["Package`"]
Package` 作为当前上下文,仅将 System` 放在上下文搜索路径中
f::usage="text"
,
引入作为输出的目标
Begin["`Private`"]
设置当前上下文为 Package`Private`
f[args]=value
,
给出在程序包中定义的主体
End[]
转换到前一个上下文(这里为 Package`
EndPackage[]
上下文结束程序包,将 Package` 放到上下文搜索路径前面
在程序包中的上下文控制指令标准序列.
BeginPackage["Collatz`"]

Collatz::usage =
"Collatz[n] gives a list of the iterates in the 3n+1 problem,
starting from n. The conjecture is that this sequence always
terminates."

Begin["`Private`"]

Collatz[1] := {1}

Collatz[n_Integer] := Prepend[Collatz[3 n + 1], n] /; OddQ[n] && n > 0

Collatz[n_Integer] := Prepend[Collatz[n/2], n] /; EvenQ[n] && n > 0

End[ ]

EndPackage[ ]
举例: Collatz.m 程序包.
定义在一个程序包开头使用 usage 信息的约定是在合适的上下文中产生用作输出的符号的一个基本的技巧. 在定义这些信息时,所涉及的符号都是用作输出的符号,这些符号都在作为当前上下文的 Package` 中产生.
在一个程序包中函数的实际定义中,有许多引入参数、局部变量等新符号. 约定将这些符号放在 Package`Private` 上下文中,当程序包读入后不放在上下文的搜索路径之中.
读入前面的样本程序包:
在这个包中的 EndPackage 指令将与该包对应的上下文放入上下文搜索路径中:
Collatz 函数在 Collatz` 上下文中产生:
参数 n 放在专用上下文 Collatz`Private` 中:
Collatz 包中所定义的函数仅依赖于 Wolfram 语言的内部函数,但通常在一个包中定义的函数依赖着另一个包中定义的函数.
为此必须有两个条件,第一是读入另一个程序包以便所需的函数有定义,第二是上下文搜索路径必须包括这些函数所在的上下文.
可以明确告诉 Wolfram 语言在任一处读入程序包,使用的指令为 <<context`. ("程序包中的文件" 讨论了从与系统不相关的上下文名称到与系统相关的文件名之间的转换). 然后,设置为需要时就读入某一程序包. 指令 Needs["context`"] 告诉 Wolfram 语言当与程序包相关的上下文不在 $Packages 列表中时就读入这个程序包.
Get["context`"]
or
<<context`
读入特定上下文对应的程序包
Needs["context`"]
当指定的上下文不在 $Packages 中时读入程序包
BeginPackage["Package`",{"Needed1`", }]
开始一个包,指定除 System` 外所需要的上下文
指定独立包函数.
当使用一个具有一个变量的 BeginPackage["Package`"] 时,Wolfram 语言仅将 Package` 上下文和 Wolfram 语言内部符号上下文放在上下文的搜索路径之中. 在自定义包中涉及到其他包中的函数时,一定要确认这些包含在上下文的搜索路径之中,这可以在 BeginPackage 的第二个变量中给出另外的上下文列表即可. BeginPackage 就自动调用 Needs,必要时读入与这些上下文对应的程序包,确定这些上下文在上下文的搜索路径之中.
Begin["context`"]
转向一个新的当前上下文
End[]
转向前一个上下文
Context 操作函数.
Begin 等上下文操作函数的执行改变了 Wolfram 语言翻译所输入名字的方式. 但要意识到这种改变仅对输入的下一个表达式有效,其原因是 Wolfram 语言总是先读入一个完整的表达式,在执行表达式的任何项之前翻译其中的名字. 于是,当一个特定的表达式中执行 Begin 时,表达式中的名称已被翻译,使 Begin 无法再产生效果.
上下文操作函数对第2个读入的完整表达式不产生影响这一事实意味着当编写 Wolfram 语言程序包时必须作为分离的表达式给出这些函数,经常是在不同的行.
x 的名字在表达式执行之前已经翻译,所以 Begin 没有作用:
上下文操作函数首先用作程序包的一部分被 Wolfram 语言读入. 有时,交互式地应用他们也很方便.
例如,在执行一个包中定义的函数时用 TraceDialog 进行一个对话,函数中的参数和临时变量一般是在与这个有关的上下文中. 由于这个上下文不在上下文的搜索路径之中,Wolfram 语言将显示这些符号的全名,并且需要用户输入全名去调用这些变量. 但还可以用 Begin["Package`Private`"] 使包专用上下文成为当前上下文. 这就使 Wolfram 语言显示符号的短名,也就可以用短名调用这些符号.
程序包中的文件
当产生或使用 Wolfram 语言程序包时,常常需要以与系统无关的方式去引用一个文件. 这可以用上下文去进行.
其基本思想是每一个计算机系统有一个约定,决定怎样根据 Wolfram 语言的上下文去命名文件. 当用一个上下文去引用一个文件时,所用的 Wolfram 语言版本就将上下文名转换为适应于所用计算机系统的文件名.
<<context`
读入一个对应于指定上下文的文件
使用上下文指定文件.
读入一个 Wolfram 系统标准程序包:
name.mx
DumpSave 格式文件
name.mx/$SystemID/name.mx
所用计算机系统的 DumpSave 格式文件
name.m
Wolfram 语言资源格式文件
name/init.m
一个目录的初始化文件
dir/
$Path 指定的其他目录中的文件
<<name` 寻找的文件序列.
Wolfram 语言的设置使 <<name` 自动装载一个文件的适当版本. 其先试装载对所用计算机系统优化过的 name.mx 文件,然后如果找不到这样的文件,则试装载与常用系统无关的 Wolfram 语言输入的 name.m 文件.
name 是一个目录时,Wolfram 语言就试装载该目录中的初始化文件 init.m. init.m 文件的目的是为设置含有许多文件的 Wolfram 语言程序包提供一个方便的途径. 其思想是先给出一个指令 <<name`,然后装载 init.m 将整个程序包初始化,需要时读入其他相关文件.
包的自动调入
我们已经讨论用 <<packageNeeds[package] 直接调入 Wolfram 语言程序包. 有时候,还要对 Wolfram 语言进行设置以便在必要时自动调入一个包.
可以用 DeclarePackage 去给出在一个包中定义的符号名,然后当某一符号被调用时,Wolfram 语言就自动调入包含这个符号的包.
DeclarePackage["context`",{"name1","name2",}]
指明当名为 namei 的符号使用时自动调入一个包
自动调入程序包的安排.
指明符号 VariationalDEulerEquationsFirstIntegralVariationalMethods` 中定义:
第一次使用 VariationalD 时,Wolfram 语言自动调入定义的包:
当建立大量的 Wolfram 语言程序包时,最好产生一系列 DeclarePackage 命令的名称文件,以便在某一名称被使用时调入一个程序包. 在一个 Wolfram 系统进程中,仅需要调入名称文件,随后其他的程序包在需要时就被自动调入.
DeclarePackage 对指定的名称立即产生符号,但给每个称号一个特定的属性 Stub. 当 Wolfram 语言发现一个符号具有 Stub 属性时就自动调入与该符号对应的内容以得到这个符号的定义.
通过名称操作符号和内容
Symbol["name"]
建立一个具有给定名称的符号
SymbolName[symb]
找出一个符号的名称
符号及其名称之间的转换.
符号 x
名字是一个字符串:
重新给出符号 x
当用 x=2 赋值后,计算时 x 就用 2 代替. 有时候,需要继续使用 x 本身,而不用立即获得其值 x.
可以通过调用 x 的名字来实现. 符号 x 的名字是一个字符串 "x",即使 x 本身用值代替,但字符串 "x" 永远用这个名字.
符号 xxp 的名称是字符串 "x""xp"
x 赋值:
任何时候输入 x,被 2 代替:
名称 "x" 没有受影响:
NameQ["form"]
测试是否有与 form 匹配的已命令名符号
Names["form"]
给出与 form 匹配的符号列表
Contexts["form`"]
给出与 form 匹配的所有上下文名称的列表
用名称去指代符号和内容.
xxp 是在 Wolfram 语言的这一进程中产生的符号,而 xpp 不是:
可以用 "字符串模式" 节讨论的字符串模式来指定符号名的类型,例如 "x*" 表示所有以 x 开头的符号名.
给出在一个 Wolfram 语言进程中所有以 x 开头的符号名:
这些名称与 Wolfram 语言的内部函数相对应:
寻找与 WeierstrssP 接近的名称:
Clear["form"]
删除名与 form 匹配的所有符号的值
Clear["context`*"]
删除在指定上下文中所有符号的值
Remove["form"]
完全删除名称与 form 匹配的符号
Remove["context`*"]
在指定的上下文中删除所有符号
通过名称删除变量.
删除所有名以 x 开头的符号的值:
"x" 名称还存在:
x 值已经被删除:
完全删除名以 x 开始的符号:
"x" 不再存在:
Remove["Global`*"]
完全删除在 Global` 的值 context 中的符号
删除所有引入的符号.
当没有建立其他上下文时,所有在 Wolfram 语言进程中引入的符号都放在 Global` context 中. 可以用Remove["Global`*"] 完全删除这些符号. 而 Wolfram 语言的内部对象都在 System` context 中,所以他们不受影响.
拦截新符号的产生
当新输入一个名称时,Wolfram 语言就产生一个新符号,有时候拦截新符号的产生是必要的. Wolfram 语言提供了几种途径来阻止新符号的产生.
On[General::newsym]
任何新符号产生时就显示一个信息
Off[General::newsym]
关闭新符号产生时的信息提示
当新符号产生时显示一个信息.
告诉 Wolfram 语言产生新符号时显示一个信息:
Wolfram 语言显示新产生符号的有关信息:
关闭信息:
在 Wolfram 语言产生新符号时显示信息是发现输入错误的一个好方法. Wolfram 语言自身不能区分要产生的新名称或者一个已有的拼写错误,但通过提示新产生符号的信息,Wolfram 语言可以使我们看到是否有错.
$NewSymbol
作用于新产生符号的名称和内容上的函数
当新符号产生时进行操作.
当 Wolfram 语言产生一个新符号时,有可能不需要显示信息,需要做一些其他的事情. 所指定的全局变量 $NewSymbol 函数将自动作用到给出 Wolfram 语言所产生的每一个新符号名称的字符串上.
定义产生新符号时使用的函数:
该函数分别对 vw 作用一次: