程序逻辑中的关系演算
关系演算是程序逻辑中一个用于规范和推理程序(尤其是包含状态和赋值的命令式程序)的框架。它的核心思想是将程序本身视为输入状态和输出状态之间的一种二元关系。我们将从最基础的概念开始,逐步构建起关系演算的完整图景。
第一步:程序状态与状态对
要理解关系演算,我们首先需要定义“程序状态”。一个程序状态(通常记为 σ, σ' 等)是程序在某个执行点上所有变量取值的一个快照。例如,一个只有两个变量 x 和 y 的程序,其状态可以表示为 (x=3, y=5)。
关系演算的基本构件是“状态对”(σ, σ')。这个有序对描述了一次状态转换:
- σ 是转换前的初始状态。
- σ' 是转换后的最终状态。
一个程序(或程序片段)的语义,就可以被定义为所有可能的状态对的集合。例如,程序 x := x + 1 的语义就是所有满足 σ‘.x = σ.x + 1 且 σ’.y = σ.y(对于其他变量 y)的状态对 (σ, σ') 的集合。
第二步:将程序视为关系
在关系演算中,我们正式地将一个程序 P 解释为一个二元关系 [P],这个关系由所有可能的状态对组成。如果程序 P 从初始状态 σ 开始执行,可能终止于状态 σ',那么状态对 (σ, σ') 就属于关系 [P]。
这里有几个关键点:
- 非确定性:关系可以包含多个具有相同初始状态 σ 的配对 (σ, σ‘₁), (σ, σ’₂), ... 这完美地描述了非确定性程序的行为。一个初始状态可能对应多个可能的最终状态。
- 不可终止性:如果程序
P从状态 σ 开始执行但永不终止(即发散),那么对于这个 σ,就不存在任何最终状态 σ‘ 使得 (σ, σ’) 属于[P]。因此,关系[P]中不包含任何以 σ 为第一分量的状态对。
第三步:关系运算与程序构造的对应
关系演算的强大之处在于,程序语言中的基本构造可以直接用标准的关系运算来定义。
-
顺序复合(Sequential Composition): 程序
P; Q(先执行 P,再执行 Q)的关系是关系[P]和[Q]的关系复合:
[P; Q] = [P] ; [Q] = { (σ, σ'') | ∃σ'. (σ, σ') ∈ [P] ∧ (σ', σ'‘) ∈ [Q] }
这意味着存在一个中间状态 σ‘,使得 P 将 σ 变为 σ’,然后 Q 将 σ‘ 变为 σ’‘。 -
非确定性选择(Nondeterministic Choice): 程序
P ⊓ Q(执行 P 或 Q)的关系是关系[P]和[Q]的并集:
[P ⊓ Q] = [P] ∪ [Q] -
空程序(Skip): 表示“什么都不做”的程序
skip,其关系是恒等关系:
[skip] = { (σ, σ) | σ 是一个状态 } -
不可终止程序(Abort): 表示总是发散的程序
abort,其关系是空关系:
[abort] = ∅
第四步:规范与精化
关系演算不仅用于描述程序“是什么”,更用于规范程序“应该做什么”。一个规范同样可以表示为一个关系 S,它规定了哪些状态转换是允许的。
核心概念是精化(Refinement)。我们说程序 P 精化了规范 S(记为 P ⊑ S),当且仅当关系 [P] 是关系 S 的一个子集:
P ⊑ S ⇔ [P] ⊆ S
这意味著:
- 任何
P能够实现的状态转换,都必须是S所允许的。 P可能比S更确定(消除了S中的一些非确定性)。P必须能够在S允许的每一个初始状态下终止(因为如果P在某个 σ 下发散,[P]中就没有以 σ 开头的对,而S中可能有,这就不满足子集关系)。
精化关系构成了程序开发和验证的基础:我们从抽象的高层规范 S₀ 开始,通过一系列精化步骤 S₀ ⊒ P₁ ⊒ P₂ ⊒ ... ⊒ P_n,最终得到一个具体的、可执行的程序 P_n,并且保证这个程序满足最初的规范。
第五步:处理条件与循环
为了建模完整的编程语言,我们需要定义条件语句和循环。
-
条件语句(Conditional): 程序
if B then P else Q fi的关系定义为:
[if B then P else Q fi] = { (σ, σ') | (σ ⊨ B ∧ (σ, σ') ∈ [P]) ∨ (σ ⊨ ¬B ∧ (σ, σ') ∈ [Q]) }
这里σ ⊨ B表示在状态 σ 下,布尔条件 B 为真。 -
循环(Loop): 循环
while B do P od的语义定义起来更复杂,它需要用到不动点理论。循环的关系可以被定义为满足以下等式的最小关系R:
R = [if B then (P; R) else skip fi]
这个等式表达了循环的直觉:如果条件 B 为真,则执行循环体 P 然后再次执行循环 R;如果 B 为假,则跳过。这个最小不动点确保了关系R只包含那些通过有限次迭代所能达到的状态对,从而排除了无限循环(不终止的情况)对应的状态对。求解这个不动点通常需要借助上一词条“偏序集与格论”中介绍的数学工具。
通过以上五个步骤,我们建立了一个将程序构造映射为关系运算的严格数学框架。这个框架是许多形式化方法(如Z记号、B方法)和程序验证工具的理论基础,它允许我们像处理数学对象一样,对程序的正确性进行精确的计算和推理。