本节描述了TLA+中定义的含义及书写方式,及声明或定义的使用范围,着重指出在TLA+中,在有重叠的定义域下,不能重复声明或定义标识符
让我们研究一下定义的含义。如果
Id是像
Init或
Spec这样的简单标识符,则表达式
Id≜exp将
Id定义为表达式
exp,在任何表达式中用
exp代替
Id或者反过来,都不会改变该表达式的含义。我们必须在解析表达式之后而不是在“raw input”中执行此替换,例如,定义
x≜a+b使得
x∗c等于
(a+b)∗c,而不是
a+b∗c(即
a+(b∗c))。
定义
Send的形式为
Id(p)=exp,其中
Id和
p表都是标识符。对于任何表达式
e,
Id(e)可以通过将
exp中的
p替换为
e得到。例如,
Channel模块中
Send定义
的Send(−5)等于
∧chan.rdy=cha.ack∧chan′=[chan EXCEPT !.val=−5,!.rdy=1−@]对于任何表达式
e,
Send(e)也是一个表达式。因此,我们可以编写公式
Send(−5)∧(chan.ack=1)。标识符
Send本身不是表达式,
Send∧(chan.ack=1)也不是完全符合语法的字符串,只是没有语义的废话,例如
a+∗b+。
Send是一个带有单个入参的运算符。我们显式定义带多个入参的运算符,一般形式为
(3.1)
Id(p1,…,pn)≜exp
其中
pi是独立的标识符,而
exp是表达式。我们可以将已定义的诸如
Init和
Spec之类的标识符视为不带任何参数的运算符,但是我们通常使用
operator来表示带一个或多个参数的运算符。
我将使用“符号”一词来表示诸如
Send之类的标识符或诸如“
+”之类的运算符。规约中使用的每个符号都必须是TLA+的内置运算符(如
∈),或者必须被声明或定义。每个被声明或定义的符号都有其使用范围,
VARIABLE或
CONSTANT声明或定义,其使用范围是其后的整个模块。因此,在模块
Channel中,
Init定义语句之后,其他表达式都可以使用
Init。
EXENTNDS Naturals语句会将在
Naturals模块中定义的符号(如
+)的使用范围扩展到
Channel模块。
运算符定义(3.1)隐式包含
p1,…,pn这些标识符的使用范围为表达式
exp中。下面这种形式的表达式:
∃v∈S:exp
中声明的标识符
v,也是如此,其范围是表达式
exp。因此,标识符
v在表达式
exp中有意义(但在表达式
S中没有意义)。
如果符号已经具有意义,则不能再声明或定义它,不过像这种表达式
(∃v∈S:exp1)∧(∃v∈S:exp2)没关系,因为两个
v的声明范围不重合,同样,
Channel模块中符号
d的两个声明(一个在
Send的定义中,一个在
Next的定义
∃d中)具有不相交的范围。但是,下面表达式是非法的:
(∃v∈S:(exp1∧∃v∈T:exp2)),之所以是非法的,是因为第二个
∃v中的
v声明位于第一个
∃v的声明范围之内。尽管常规的数学和编程语言允许进行此类重复定义或声明,但在TLA+中是禁止,因为它们可能导致混淆和错误。