TLA+ 《Specifying Systems》翻译初稿——Section 14.2 What TLC Can Cope With(TLC应用范围)

没有任何模型检查器可以处理所有的specification(这些specification是我们所能用的像TLA+这样具有表现力的语言编写的)。不过,TLC似乎能够处理人们实际编写的大多数TLA + specification。使用TLC处理specification可能需要一些技巧,但是通常可以在不对specification本身进行任何更改的情况下完成。本节说明了TLC可以和不能解决的问题,并提供了一些使其解决的方法。理解TLC局限性的最好方法是了解其工作方式。因此,本节描述TLC如何"执行"specification。

14.2.1 TLC Values(TLC值)

一个state是给一组变量赋值的操作。 TLA+允许您描述大量不同类型的数值------例如,所有素数序列的集合。TLC只能计算受限的一类数值,称为TLC数值。这些数值是根据以下四种原始值构建的:

  • Booleans:布尔值,其值为TRUE 或 FALSE ;
  • Integers Values: 整数值,如 3和-1;
  • Strings Values:字符串,如"abc";
  • Model Values: 这些是在配置文件的CONSTANT语句中引入的值。
    例如,第227页图14.3所示的配置文件引入了模型值d1和d2。
假定具有不同名称的Model Value是不同的,TLC数值可以归纳定义为
  1. 原始值
  2. 有限的具有可比性的TLC数值的集合(在下面定义可比性)
  3. 对于f值域中的所有x,函数f的域为TLC值,f[x]也为TLC值。

举例来说,根据第1,2条规则,(14.3) { { " a " , " b " } , { " b " , " c " } , { " c " , " d " } } \left \{ \left \{"a","b" \right \} ,\left \{"b","c" \right \} ,\left \{ "c","d" \right \} \right\}
是一个TLC值,因为,根据规则1,“a”,“b”,“c”,“d"都是TLC值,根据规则2可以推导出14.3也是一个TLC值,既然元组和记录都是函数,由规则3可以推导出一条由TLC值组成记录或者元组也是一个TLC值.
例如 1 , " a " , 2 , " b " \left \langle 1,"a", 2,"b" \right \rangle 也是一个 TLC值.为了完善TLC值的定义,我必须解释规则2中的可比性。
基本思想是两个值应该是可比较的当且仅当TLA+语义确定是相等还是不相等。 例如,字符串和数字是不可比较的,因为TLA+的语义不能判定"abc"和42是否相等。 因此集合{“abc”,42}不是TLC值;规则2不适用,因为” abc"和42不具有可比性。
另一方面,{“abc”}和{4,2}具有可比性,因为元素数量不同的集合必然不相等。因此,两个元素{{“abc”},{4,2}}也是TLC值。TLC认为模型值可与任何其他值进行比较,但不相等。第14.7.2节中给出了可比性的更精确的规则定义。

14.2.2 How TLC Evaluates Expressions(TLC如何计算表达式)

检查规范需要计算表达式。
例如,TLC通过计算各个可到达状态下的不变性来进行不变性检查,即计算其TLC值是否为TRUE。要了解TLC可以做什么和不能做什么,必须知道它如何计算表达式的,TLC以非常直接的方式计算表达式,通常采用"从左到右"的方式计算子表达式,特别的:

  • 计算 p q p\wedge q 时,先计算p的值,如果p值为TRUE,则继续计算q的值;
  • 计算 p q p \vee q 时,先计算p的值,如果p值为FALSE,则继续计算q的值,即以 ¬ p q \lnot p\wedge q 方式计算 p q p \vee q ;
  • 计算IF p THEN e1 ELSE e2时,先计算p,再继续计算e1 或者 e2;

为了理解这些规则的重要性,我们来看一个简单的例子。 如果x等于 \left \langle \right \rangle ,TLC无法评估表达式x[1],因为 [ 1 ] \left \langle \right \rangle\left [ 1 \right ] 没有意义。(空序列是一个函数,其值域是空集,因此不包含1。)第一条规则意味着,如果x等于 \left \langle \right \rangle ,则TLC可以计算表达式 ( x ) ( x [ 1 ] = 0 ) (x \neq \left \langle \right \rangle) \wedge (x[1] = 0) ,但不能计算表达式 ( x [ 1 ] = 0 ) ( x ) (x[1] = 0) \wedge(x \neq \left \langle \right \rangle) (因为计算此表达式是,根据规则1,会先计算 x [ 1 ] = 0 x[1] =0 ,TLC会报错,因为不能计算)幸运的是,我们会很自然地编写第一个公式而不是第二个公式,因为它更容易理解。

人们可以通过从左到右的"心理计算"来理解公式,这与TLC的做法很相似。TLC计算 x S : p \exists x \in S:p 时,是将集合S中的元素 s 1 , , s n s_{1} ,\cdots,s_{n} (其中 i = 1 , , n i = 1,\cdots,n ),经过一定的顺序,逐个代替变量x,代入公式p,计算p的值,TLC以非常简单的方式枚举集合S的元素,如果集合显然不是有限的,则会终止并声明错误。举例来说,集合 { 0 , 1 , 2 , 3 } \left\{ 0,1,2,3 \right \} 0..3 0..3 是非常明显可被遍历的有限集,在计算 x S : p \exists x \in S:p 时,会先对S中的元素进行遍历,所以 { i 0..5 : i < 4 } \left \{ i\in0..5:i<4 \right \} 可被计算而 { i N a t : i < 4 } \left \{ i\in Nat:i<4 \right \} 不能。

TLC计算 x S : p \forall x \in S:p CHOOSE    x S : p \textrm{CHOOSE} \;x \in S:p 时,都是和对 x S : p \exists x \in S:p 一样,先遍历S中的所有元素,TLA+的语义指定对 CHOOSE    x S : p \textrm{CHOOSE} \;x \in S:p ,如果S中没有一个元素满足p,则返回一个任意值,不过,这种情况通常是由于出现了某种错误导致,所以TLC会把它当成错误处理。

注意到表达式 IF    n 5    THEN    CHOOSE  i 1.. n : i 5    ELSE    42 \textrm{IF} \;n\>5\; \boldsymbol{\textrm{THEN} }\;\textrm{CHOOSE }i \in 1..n: i\> 5 \;\textrm{ELSE} \;42 不会报错,因为当 n 5 n\leq5 的时候不会进入CHOOSE子句,当 n 5 n\>5 时TLC才会在计算CHOOSE子句时报错。

TLC无法计算"无界"量词或CHOOSE表达式------即具有以下形式之一的表达式:

x : p x : p CHOOSE    x : p \exists x:p\quad \forall x:p\quad \textrm{CHOOSE} \;x:p

TLC无法计算其值不是TLC值的任何表达式,如上文第14.2.1节中所定义的。

特别的,TLC仅可计算其值是一个有限集的集值表达式,并且仅当其值域是一个有限集时,才可以评估一个函数值表达式。TLC仅在能遍历集合S时,才会计算以下形式的表达式:

x S : p x S : p CHOOSE    x S : p \exists x \in S:p\quad \forall x \in S :p\quad \textrm{CHOOSE} \;x \in S:p

{ x S : p } { e : x S } { x S e } \left \{ x \in S: p \right \} \quad \left \{e: x \in S \right \} \quad \left \{ x \in S \mapsto e \right \}

S U B S E T    S U N I O N    S \mathrm{SUBSET} \;S\quad\mathrm{UNION}\; S

TLC经常可以计算某些表达式,却它不能计算所有的子表达式,举个例子:TLC可以计算 [ n N a t n ( n + 1 ) ] [ 3 ] [ n \in Nat \mapsto n*(n+1) ][3] 的值为12,但它不能计算 [ n N a t n ( n + 1 ) ] \left [ n \in Nat \mapsto n*(n+1) \right ] 的值,这个表达式的值是一个值域为Nat的函数表达式(一个函数为TLC值当且仅当其值域是一个有限集)TLC通过简单的递归procedure来计算由递归定义的函数。 如果f由 f [ x S ] e f[x \in S]\triangleq e 定义,则TLC通过用c代替x 计算e来得出f[c]的值。
这意味着它无法处理某些合法的定义。 例如,参考第68页的以下定义:
m r [ n S ] [ f i f    n = 0    t h e n    17    e l s e    m r [ n 1 ] . f m r [ n ] . g , g i f    n = 0    t h e n    42    e l s e    m r [ n 1 ] . f m r [ n 1 ] . g ] mr[n \in S]\triangleq\\ \quad \left [ f \mapsto if \;n=0 \; then\; 17\; else \; mr[n-1].f*mr[n].g, \\ \quad g \mapsto if \;n=0 \; then\; 42\; else \; mr[n-1].f*mr[n-1].g \right ]

为了计算mr[3],我们在表达式中用3代替n来计算 \triangleq 右边的值,不过因为mr[3]也出现在等式右边,所以TLC认为它是一个无限循环,从而报错,合法的递归定义导致如上死循环的毕竟是少数,可以换一种符合TLC的写法,回到我们之前的交互递归定义 f [ n ] = i f    n = 0    t h e n    17    e l s e    f [ n 1 ] g [ n ] , g [ n ] = i f    n = 0    t h e n    42    e l s e    f [ n 1 ] g [ n 1 ] f[n]=if \;n=0 \; then\; 17\; else \; f[n-1]*g[n], \\ \quad g[n] = if \;n=0 \; then\; 42\; else \; f[n-1]*g[n-1]

子表达式mr[n]出现在定义mr[n]的表达式中,因为f[n]取决于g[n]。
为了消除它,我们必须重写相互递归,以便f[n]仅取决于f[n-1]和g[n-1],我们可以通过展开f[n]表达式中g[n]的定义来做到这一点,由于else子句仅适用于 n 0 n\neq 0 的情况,因此我们可以将f[n]的表达式重写为

f [ n ] = i f    n = 0    t h e n    17    e l s e    f [ n 1 ] ( f [ n 1 ] + g [ n 1 ] ) f[n]= if \;n=0 \; then\; 17\; else \; f[n-1]*(f[n-1]+g[n-1]) ,
这样原公式可以推导成如下形式:
m r [ n S ] [ f i f    n = 0    t h e n    17    e l s e    m r [ n 1 ] . f ( m r [ n 1 ] . f + m r [ n 1 ] . g ) , g i f    n = 0    t h e n    42    e l s e    m r [ n 1 ] . f m r [ n 1 ] . g ] ] \\mr[n \in S]\triangleq\\ \quad \left [ f \mapsto if \;n=0 \; then\; 17\; else \; mr[n-1].f*(mr[n-1].f+mr[n-1].g), \\ \quad g \mapsto if \;n=0 \; then\; 42\; else \; mr[n-1].f*mr[n-1].g \right ]]

这样,TLC就可以计算mr[3]的值而不出问题了。

第14.2.6节的第240页描述了如何计算ENABLED谓词和复合action操作符".",
第14.3节介绍了TLC如何计算用于时态检测的时态逻辑公式。
如果不确定TLC是否可以对表达式求值,请尝试看看。
但是不要在检查整个specification的过程中检查该表达式。

相反,做一个小例子,让TLC仅计算该表达式。 有关如何将TLC用作TLA+计算器,请参见第14.5.3页的说明。

14.2.3 Assignment and Replacement(赋值和替换)

正如我们在alternating bit示例中看到的那样,配置文件必须确定每个常量参数的值。要将TLC值v分配给specification的常量参数c,我们在配置文件的CONSTANT语句中写入 c = v c=v 。 值v可以是原始TLC值或以 { v 1 , , v n } \left \{ v_{1}, \cdots, v_{n} \right \} 形式编写的有限的原始TLC值集。 例如{1,-3,2}。
在v中,将a1或foo之类的任何非数字字符序列,带引号的字符串,或TRUE或FALSE都视作model value。
在赋值表达式 c = v c=v 中,符号c不必是常数,也可以是已定义的符号,此赋值语句可以使TLC忽略c的实际定义,并以v为它的值。
当TLC无法根据其定义计算出c的值时,通常使用这种赋值语句。
特别是,像在下面例子中,TLC无法根据定义计算NotAnS的值

N o t A n S C H O O S E    n : n S NotAnS\triangleq \mathrm{CHOOSE} \; n : n \notin S

因为TLC无法计算无边界的CHOOSE表达式。您可以通过在配置文件的CONSTANT语句中为NotAnS分配一个值来覆盖此定义。
例如,赋值NotAnS = NS 可以使TLC为NotAnS分配model value NS。
TLC忽略了NotAnS的实际定义。

如果在specification中使用名称NotAnS,你可能希望TLC在报错的消息中使用名称NotAnS而不是NS。因此,您可以会使用赋值语句NotAnS = NotAnS,它将model value NotAnS分配给符号NotAnS。 请记住,在赋值语句 c = v c=v 中,必须在TLA
+模块中定义或声明符号c,并且v必须是原始TLC值或此类值的有限集合。配置文件的CONSTANT语句还可以包含形式为 c d c\leftarrow d 的替换,其中c和d是TLA+中定义的符号,这将使TLC在执行计算时将c替换为d。

替换的一种用途是为操作符参数赋值。例如,假设我们要使用TLC检查第5.6节(第54页)的write-through cache specification,WriteThroughCache模块扩展了MemoryInterface模块,该模块包含声明

constants Send (_,_,_,_ ), Reply(_,_,_,_ ),…

我们必须告诉TLC如何计算操作符Send 和 Reply,我们可以先写一个名为MCWriteThroughCache的模块,该模块是模块WriteThroughCache的扩展,在其中定义两个操作符
M C S e n d ( p , d , o l d , n e w ) M C R e p l y ( p , d , o l d , n e w ) \\MCSend (p, d , old , new) \triangleq \cdots \\ MCReply(p, d , old , new ) \triangleq \cdots

然后,我们将替换内容

S e n d M C S e n d , R e p l y M C R e p l y \\Send \leftarrow MCSend,\\ Reply \leftarrow MCReply

添加到配置文件的CONSTANT语句中, 替换也可以是一个定义的符号替换另一个。在specification中,我们通常会编写最简单可行的定义。对于TLC而言,最简单的定义并不总是最容易用的定义。例如,假设我们的specification需要一个Sort运算符,则如果S是一个有限的数字集合,则 S o r t ( S ) Sort\left ( S \right ) 是一个按升序排列的包含所有S元素的序列。我们在SpecMod模块中的规范可以使用如下简单的定义:

S o r t ( S ) C H O O S E    s [ 1.. C a r d i n a l i t y ( S ) S ] : i , j D O M A I N    s : ( i < j ) ( s [ i ] < s [ j ] ) Sort(S)\triangleq \mathrm{CHOOSE} \; s \in \left [ 1..Cardinality(S)\rightarrow S \right ]: \\ \quad \quad \forall i,j \in \mathrm{DOMAIN}\;s: (i<j)\Rightarrow (s[i]<s[j])

为了计算包含n个元素的集合S的Sort(S ),TLC必须遍历函数集合 [ 1.. n S ] [1..n \rightarrow S] 中的 n n n^{n} 个元素, 这可能太慢了,我们可以编写一个模块MCSpecMod来扩展SpecMod并在其中定义FastSort,以便在应用于有限的数字集时它等于Sort,但可以让TLC计算得更快。

这样我们就可以在包含了替换 Sort<-FastSort的配置文件基础上运行TLC,在14.4节,第250页给出了FastSort的一种可行定义。

14.2.4 Evaluating Temporal Formulas(计算时态公式)

14.2.2节(第231页)说明了TLC可以"计算"哪种常规表达式。TLC可以"检查"的specification和属性是时态公式;本节描述了它可以处理的时态公式的类别。TLC可以计算满足如下条件的TLA+时态公式

(i)该公式是nice的------参见下一段中定义的术语,且
(ii)TLC可以计算组成该公式的所有常规表达式;

例如,形式为 P Q P\leadsto Q 是nice的,所以TLC可以计算它当且仅当可以计算P和Q。(下面的14.3节解释了TLC计算时态公式的组成表达式时涉及哪些状态和状态对。)

时态公式是nice的当且仅当它是以下四类公式的并集:

  1. State Predicate:状态谓词
  2. Invariance Formula: 不变性公式,形如 P \square P 的公式 , 这里 P 是一个状态谓词.
  3. Box-Action Formula: 形如 [ A ] v \square \left [A \right ]_{v} 的公式 , 这里 A 是一个action, v 是一个 state 函数.
  4. Simple Temporal Formula:为了方便定义这种公式,

我们先引入如下定义:

  • 简单的布尔运算符:由命题逻辑的运算符以及对有限常量集的量化组成: ¬  TRUE   FALSE  \wedge \quad \vee \quad \neg \quad \Rightarrow \quad \equiv \quad \text { TRUE } \quad \text { FALSE }
  • 简单时间状态公式(A temporal state formula):是通过在状态谓词上应用简单布尔运算符和时态运算符( , , \square,\diamond,\leadsto )而获得的。
    例如,如果N为常数,则 i 1.. N : ( ( x = i ) j 1.. i : ( y = j ) ) \forall i \in 1 .. N: \square((x=i) \Rightarrow \exists j \in 1.. i: \diamond(y=j)) 是时态公式。
  • action公式: WF v ( A ) SF v ( A ) [ A ] v [ A ] v \text{WF}_{v} (A) \quad \text{SF}_{v} (A) \quad \square \diamond[A]_{v} \quad \diamond \square[A]_{v} ,其中A是action,而v是状态函数: WF v ( A ) , SF v ( A ) \text{WF}_{v} (A) ,\text{SF}_{v} (A) 的子表达式有 [ A ] v , ENABLED A v \left [ A \right ]_{v}, \text{ENABLED} \left \langle A \right \rangle_{v} (在第240页描述了ENABLED公式的计算方式),这样,就可以将上述第4项Simple Temporal Formula定义为通过应用简单的布尔运算符,组合了时态公式和简单的action公式而构成的公式。

为方便起见,我们从时态公式的类别中排除不变性公式,则这四类nice的时态公式是不相交的。这样TLC就可以计算下面的时态公式了:

i 1.. N : ( y = i ) W F y ( ( y = y + 1 ) ( y i ) ) \forall i \in 1.. N: \diamond(y=i) \Rightarrow \mathrm{WF}_{y}\left(\left(y^{\prime}=y+1\right) \wedge(y \geq i)\right)

如果N是一个常数,因为这是一个简单的时态公式(因此是nice的),TLC可以评估其所有组成部分的表达式。TLC无法评估 x = 1 x \Diamond \left \langle x'=1 \right \rangle_{x} ,因为这不是一个nice的公式。 TLC也无法评估公式 WF x [ 1 ] = 0 x \text{WF} \left \langle x'[1]=0 \right \rangle _{x} ,因为在step s t s\rightarrow t ,状态t中,如果 x = x = \left \langle \right \rangle ,则无法计算 x [ 1 ] = 0 x \Diamond \left \langle x'[1]=0 \right \rangle _{x}

PROPERTY语句可以设定TLC可以计算的任何公式。SPECIFICATION语句的公式必须恰好包含一个作为Box-Action公式的合取词。该合词指定了下一个状态action。

14.2.5 Overriding Modules(模块覆盖)

TLC无法根据标准Naturals模块中包含的"+“定义来计算2 + 2。即使我们真的使用TLC定义的计算总和的”+"定义,也算的不快。像+这样的算术运算符可以直接用编写TLC的语言Java来实现。

这是通过TLC的通用机制实现的,该机制允许模块被JAVA类覆盖,该JAVA类实现该模块中定义的运算符。当TLC遇到EXTENDS Naturals语句时,它将加载覆盖Naturals模块的Java类,而不是读取模块本身。
有Java类可以覆盖以下标准模块:Naturals,Integers,Sequences,FiniteSets和Bags。(下面的第14.4节中描述的TLC模块也被Java类覆盖。)有经验的Java程序员会发现编写Java类来覆盖模块并不难。

14.2.6 How TLC Computes States

TLC评估不变量时,它会计算不变量的值,该值可以为TRUE或FALSE。当TLC评估initial predicate或者 next-state aciton时,它会计算一组状态------对于initial predicate,会计算所有初始状态的集合,而对于next-state action,则是计算从给定的开始状态(unprimed)开始,可能的后继状态(primed状态)的集合。

我将描述TLC如何针对next-state action执行此操作。初始谓词的评估也是类似的。回想一下,一个state是给变量赋值的操作。
TLC是这样计算给定状态s的后续状态的:先给s状态中所有unprimed(未加 ’ )的变量赋值,接下来评估next- state action操作,来计算给定状态s的primed值。

TLC按第14.2.2节所述评估下一状态动作。

(第231页),但我会接下来描述两个区别。该描述假定TLC已经执行了配置文件的CONSTANT语句指定的所有赋值和替换,并且已展开了所有定义。因此,下一个状态操作是一个仅包含变量,primed变量,模型值以及内置TLA+运算符和常量的公式。

第一个区别是TLC在评估next-state action时,针对析取词(disjunction

or)不是从左至右,相反的,在评估子公式 A 1 A n A_{1} \vee \cdots \vee A_{n} 时,它将计算分成n个独立的计算,每一个都是独立的子公式 A i A_{i} ,类似的,在计算 x S : p \exists x \in S:p 时,TLC会对S的每一个元素分别计算。蕴含操作 P Q P \Rightarrow Q 则是计算它对等的析取操作 ¬ P Q \lnot P \vee Q ,

举个例子:TLC会将公式 ( A B ) ( C ( i S : D ( i ) ) E ) (A \Rightarrow B) \vee(C \wedge(\exists i \in S: D(i)) \wedge E) 拆成3个独立的子公式 ¬ A , B \lnot A,B C ( i S : D ( i ) ) E C \wedge(\exists i \in S: D(i)) \wedge E 分别计算的。在最后的析取计算前,我们首先需要计算C,如果C为TRUE,再对每一个S中的元素,独立计算 D ( i ) E D(i) \wedge E ,对每一个 D ( i ) E D(i) \wedge E ,也是先计算 D ( i ) D(i) ,如果为TRUE,再计算E.

第二个区别是TLC在评估next-state action时,对任意变量x,计算如 x = e x'=e x x' 尚未赋值这种形式的表达式时,表达式赋值为TRUE,再计算表达式e的值赋给 x x' 。TLC计算表达式 x S x' \in S 的对等表达式 v S : x = v \exists v \in S:x'=v .

计算表达式 UNCHANGED    x \text{UNCHANGED}\; x 的对等表达式: x = x x'=x ,

对任意的变量x,评估 UNCHANGED  e 1 e n \text{UNCHANGED } \left \langle e_{1} \cdots e_{n} \right \rangle 时,会对每一个 e i e_{i} f分别计算 UNCHANGED    e 1 UNCHANGED    e n \text{UNCHANGED} \; e_{1} \wedge \cdots \wedge \text{UNCHANGED} \; e_{n} ,这样, UNCHANGED  x , y , z \text{UNCHANGED }\left \langle x, \left \langle y, z \right \rangle \right \rangle 也是当做 x = x    y = y    z = z x'=x \; \wedge y'=y \; \wedge z'=z 计算。

除了在评估 x = e x'=e 这种形式的表达式时,如果遇到尚未赋值的primed变量,TLC会报告错误。如果合取词的值为假,则评估停止,返回"没有发现状态"。

完成并赋值为TRUE的评估将"找到状态",该状态由分配给primed变量的值确定。
在后一种情况下,如果尚未为某些primed变量分配值,TLC将报告错误。
为了说明这是如何工作的,让我们考虑TLC如何评估next-state action:
(14.4) x 1.. L e n ( y )        y = Append ( Tail ( y ) , x ) x = x + 1        y = Append ( y , x ) \begin{aligned} &\vee \wedge \mathrm{x'} \in 1 .. Len(y) \\ &\;\;\;\wedge y'=\text {Append}(\text {Tail}(y), x') \\ &\vee \wedge x'=x+1 \\ &\;\;\;\wedge y'= \text {Append}(y, x') \end{aligned}

我们先考虑起始状态 x = 1 , y = 2 , 3 x=1, \quad y=\left \langle 2,3 \right \rangle , TLC先独立计算这2个析取词,首先计算 x 1.. Len ( y ) \begin{array}{l}{ \mathrm{x}^{\prime} \in 1 .. \operatorname{Len}(y)} \end{array} , 如上面的说明,TLC计算它等价的 i 1.. L e n ( y ) : x = i \exists i \in 1..Len(y):x'=i , 既然 L e n ( y ) = 2 Len(y)=2 ,那么TLC将这个式子拆成两个独立的子公式:

(14.5) x = 1 y = Append ( Tail ( y ) , x ) \\ \wedge x'=1 \\ \wedge y'=\text {Append}\left(\text {Tail}(y), x'\right) x = 2 y = Append ( Tail ( y ) , x ) \\ \wedge x'=2 \\ \wedge y'=\text {Append}\left(\text {Tail}(y), x^{\prime}\right)

TLC计算14.5的第一个action如下:它计算第一个合取词,取值为TRUE,并将x值1赋给 x x' ;然后,它计算第二个合取词,取值为TRUE,并将值 A p p e n d ( T a i l ( 2 , 3 ) , 1 ) Append(Tail(\left \langle 2,3 \right \rangle),1) 分配给 y y' 。因此,评估(14.5)的第一个action会发现其后续状态是 x = 1 x=1 y = 3 , 1 y=\left \langle 3,1 \right \rangle 。类似地,评估(14.5)的第二个动作会发现其后续状态为 x = 2 x=2 y = 3 , 2 y=\left \langle 3,2 \right \rangle 。TLC以类似的方式评估(14.4)的第二个析取关系,得到其后续状态是 x = 2 x=2 y = 2 , 3 , 2 y=\left \langle 2,3,2\right \rangle
因此,对(14.4)的评估发现了三个后序状态。接下来,考虑TLC如何在 x = 1 x=1 且y等于空序列 \left \langle \right \rangle 的状态下评估(14.4)下一状态action。

由于 L e n ( y ) = 0 Len(y)=0 1..0 1..0 是空集 { } \left \{ \right\} ,TLC将第一个析取项评估为

i { } : x = i y = A p p e n d ( T a i l ( y ) , x ) \begin{array}{l}{\wedge \exists i \in\{\}: x'=i} \\ {\wedge y'=Append \left(Tail(y), x'\right)}\end{array}

评估第一个合取词会产生错误,因此会停止对(14.4)的第一个合取词进行评估,表明没有发现后继状态。评估第二个析取关系会得其后续状态出 x = 2 x=2 y = 2 y=\left \langle 2 \right \rangle

由于TLC从左到右评估合取,因此它们的顺序会影响TLC是否可以评估下一状态动作。例如,假设(14.4)的第一个析取语中的两个析取语颠倒了,像这样:

y = A p p e n d ( Tail ( y ) , x ) x 1.. L e n ( y ) \\{\wedge y'=Append \left(\text{Tail}(y), x'\right)} \\ {\wedge x' \in 1..Len(y)}

当TLC评估此action的第一个合取词时,它在将值赋给 x x' 之前先遇到表达式 y = A p p e n d ( Tail ( y ) , x ) \\{y'=Append \left(\text{Tail}(y), x'\right)} ,因此它会报告错误。此外,即使我们将 x x' 更改为 x x ,TLC仍无法评估以 y = y=\left \langle \right \rangle 为起始状态的动作,因为在评估第一个合取词时,它将遇到Silly表达式 T a i l ( ) Tail\left (\left \langle \right \rangle \right )

上面给出的关于TLC如何评估任意next-state action的描述足以解释它在几乎所有实际情况下如何工作的。但是,它并不完全准确。例如,按字面解释,这意味着TLC可以处理以下两个next-state actions, 它们在逻辑上均等价于 ( x = TRUE ) ( y = 1 ) (x' = \text{TRUE}) \wedge(y' = 1)

(14.6) ( x = ( y = 1 ) ) ( x = T R U E )  IF  x =  TRUE THEN  y = 1  ELSE FALSE  \left(x^{\prime}=\left(y^{\prime}=1\right)\right) \wedge\left(x^{\prime}=\mathrm{TRUE}\right) \quad \text { IF } \quad x^{\prime}=\text { TRUE THEN } y^{\prime}=1 \text { ELSE FALSE }

实际上,TLC在处理这些异常的next-state actions时都将产生错误消息。请记住,TLC通过使用类似评估初始谓词的方式来计算初始状态,与其从有初始值的unprimed变量开始,再将其赋值给primed变量,不如直接赋值给unprimed变量。

TLC评估ENABLED公式的方式基本上与评估next-state action的方式相同。更准确地说,要评估 ENABLED    A \text{ENABLED}\;A 的公式,TLC会计算其后继状态,就好像A是next-state action一样。如果存在后继状态当且仅当公式的计算结果为TRUE。为了检查步骤 s t s\rightarrow t 是否满足action A和B的合成action A B A \cdot B ,TLC首先计算所有状态 u u ,以使 s u s\rightarrow u 是A step,然后再检查 u t u\rightarrow t 是否是针对某些此类 u u 的B step。

TLC在检查属性时可能也需要评估action,在这种情况下,它会像评估其他表达式一样评估action,并且即使评估类似(14.6)的奇怪action也毫不费力。

发布了4 篇原创文章 · 获赞 1 · 访问量 5538

猜你喜欢

转载自blog.csdn.net/robinhzp/article/details/103513677