当需要判断两个 Term 是否在定义上相等 (definitionally equal) 时,可以使用转换检查 (conversion check)。
这是一个很符合直觉的过程:
- 将两个 Term normalize 成 Normal Form (NF)
- 比较两个 NF 的 Head 是否相等
- 如果相等,那么递归地对两个 NF 的参数 (arguments) 进行此过程
但无论是处于工程角度上考虑,还是处于理论角度考虑,
将 Term normalize 成 NF 是十分昂贵的操作,甚至某些类型论并不总是保证 NF 的存在 {?}
。
于是出现了一种更加 lossy 的方式:
- 将两个 Term normalize 成 Weak Head Normal Form (WHNF)
- 比较两个 WHNF 的 Head 是否相等
- 如果相等,那么递归地对两个 WHNF 的参数 (arguments) 进行此过程
在上面的过程中,我们总是用到“比较”这个操作,这需要一些转换规则 (conversion rules) 来说明如何做这个比较。
这些规则可以通过不同的方法定义:基于语法的和基于类型的。
为保证符号的一致性:
符号 | 说明 |
Γ⊢u:A | 在语境 Γ 下,Term u 具有类型 A |
u≡v | u 和 v 在定义上相等 |
Γ⊢u≡v:A | 在已知 u 和 v 在语境 Γ 下都具有类型 A 时,u 和 v 在定义上相等 |
u[x↦v] | 无需捕获 (capture-avoiding) 的替换 (substitution),即将 u 中的 x 替换为 v |
Untyped conversion
为了方便起见,先考虑基于语法的转换检查 (syntax-directed conversion,一些地方会翻译成语法制导
,但我不喜欢这种翻译)。 一种方便的做法是从类型规则推导出转换规则:
在 Σ 类型的成型规则
Γ⊢Σ(x:A)×BtypeΓ⊢AtypeΓ,x:A⊢Btype
的基础上进行一些“改写”,可以得到 Σ 类型的转换规则
Γ⊢Σ(x:A)×B≡Σ(x′:A′)×B′Γ⊢A≡A′Γ,x:A⊢B≡B′[x′↦x]
这个“改写”从直觉上看,好像就做了两件事:
- 复制一份 Σ 类型的成型规则并将其中的 x, A, B 等都改写成带角标的格式
- 将这两份规则根据语法构造合并
这其实是反映了比较两个 Term 的初衷:有 Σ(x:A)×B 和 Σ(x′:A′)×B′ 构造的两个 Term,
他们都是被类型检查器根据 Σ 类型的成型规则产生的 Term,即类型检查器会做如下判断:
Γ⊢Σ(x:A)×BtypeΓ⊢AtypeΓ,x:A⊢Btype
Γ⊢Σ(x′:A′)×B′typeΓ⊢A′typeΓ,x′:A′⊢B′type
那么相应地,conversion check 只要比较其中的 A 和 A′,B 和 B′ 是否相等即可判断两个 Σ Term 是否相等。
消去规则 (Elimination-rule)
再看 Σ 类型的消去规则
Γ⊢t.1:AΓ⊢t:Σ(x:A)×B
Γ⊢t.2:B[x↦t.1]Γ⊢t:Σ(x:A)×B
根据上面的方法,很简单就能写出投影 (Projection) 的转换规则*
Γ⊢t.i≡t′.i′Γ⊢t≡t′i=i′
构造规则 (Introduction-rule)
至此为止,从类型规则中得到转换规则的过程看似十分顺利,但是问题马上就来了:考虑 Σ 类型的构造规则,即我们可以通过如下规则构造
Σ 类型的实例,一般称为元组 (Tuple):
Γ⊢(a,b):Σ(x:A)×BΓ⊢a:AΓ,a:A⊢b:B[x↦a]
按照上面的方法,可以得到
Γ⊢(a,b)≡(a′,b′)Γ⊢a≡a′Γ,a:A⊢b≡b′[x↦a]
这有什么问题呢?现在让我们考虑 Σ 类型的某个特殊例子,即0-元组类型,通常也被称为 Unit
类型,
写作 ⊤ 类型。
顾名思义,这是一个没有任何元素的元组类型,它的构造规则和对应的转换规则为:
Γ⊢():⊤
Γ⊢()≡()
⊤ 类型的转换规则表示:任何两个 ⊤ 类型的实例,都应该相等。
现在看来这套规则依然很美好,但是问题马上就要出现了。现在让我们引入中性 Term 这一概念。
中性 Term (Neutral Term)
中性 Term 是指无法化简 (irreducible) 的 Term。例如引用其他变量的 Term (称为 RefTerm
) 是不可化简的,比如在下面的
Aya 代码中
函数体中的 r
就是一个中性 Term,因为在函数的参数为给定前,无法知道 r
具有怎样的形式。 进一步地,
对中性 Term 的消去也都是中性 Term (例如上面代码中的 r.1
),因为无法知道被消去的 Term 的形式,
所以也无法知道其第一个元素的语法构造,因此无法化简。
但是 RefTerm
的转换规则却很平凡:只要引用的变量相同,那么两个 RefTerm
就相同
Γ⊢a≡a′identity(a)=identity(a′)
其中,identity 用于计算被引用的变量的唯一标识符 (可以视为一种 UUID)。
至此,上述由消去规则导出的转换规则也可以处理对中性 Term 的消去的转换检查了 (之前只能处理 elim(intro) 的情况)。
⊤ 类型和中性 Term
当有两个中性 Term 的类型为 ⊤ 时,即 Γ⊢tt:⊤,
和 Γ⊢uu:⊤
现在我们需要将 tt 和 uu 进行转换检查。因此,根据上文的内容,我们需要判断:
Γ⊢tt≡uuidentity(tt)=identity(uu)
显然,tt 和 uu 并不是同一个变量,因此该判断显然不成立,即结论为 tt 和 uu 在定义上不相等。
然而根据 ⊤ 类型的转换检查规则,所有 ⊤ 类型的实例都应该在定义上相等。
但根据刚刚写出的规则,这是无法完成的!
如果能知道它们的类型,那么就可以纠正这个错误,从而给出公正的判断。
但是该公式中没有任何地方可以告诉我们 tt 和 uu 的类型!唉!中性 Term!
所以,我们需要一些类型信息的帮助。
η-转换
另一个问题是,在进行转换检查之前,所有的 Term 已经被 normalize 成 WHNF。但如果 Term 本身是中性 Term,那么 normalize 的过程将会被卡住 (
stuck),
无法得到“理想的 WHNF”。同时,由于类型信息的缺失,尽管类型检查器知道 Γ⊢t:Σ(x:A)×B,
也无法在转换检查中证明 WHNF t 和 WHNF (t.1,t.2) 相等 (这是 intro(elim),也被称为 η-转换)。
但如果转换检查知道 t 是一个 Σ 类型,那么就可以正当地构造 t.1 和 t.2,
并将它们分别和 (t.1,t.2).1 和 (t.1,t.2).2 进行比较,从而处理 η-转换。
Typed conversion
在基于语法的基础上,引入类型作为辅助信息,从而变成基于类型的转换检查 (type-directed conversion)。
这个思想本质上是将 u≡v 变成了 Γ⊢u≡v:A,这条新的规则表明三件事:
- Γ⊢u:A
- Γ⊢v:A
- u≡v
现在用新的方法来定义转换检查,这次以 Π 类型为例
Γ⊢Π(x:A)→B≡Π(x′:A′)→B′typeΓ⊢A≡A′typeΓ,x:A⊢B≡B′[x′↦x]type
Γ⊢fa≡f′a′:B[x↦a]Γ⊢f≡f′:Π(x:A)→BΓ⊢a≡a′:A
而构造规则的转换检查 (即λ和元组的转换检查) 可以写成
Γ⊢λx⇒t≡λx′⇒t′:Π(x:A)→BΓ,(x:A)⊢t[x↦a]≡t′[x↦a]:B[x↦a]
Γ⊢(a,b)≡(a′,b′):Σ(x:A)×BΓ⊢a≡a′:AΓ,a:A⊢b≡b′[x↦a]:B[x↦a]
如果定义 RefTerm
的转换检查
Γ⊢a≡a′:Aidentity(a)=identity(a′)Γ=Γ′,a:A
其中 Γ=Γ′,a:A 表示 a:A 在语境 Γ 中。
并考虑 η-转换,又可以将λ和元组的转换检查写为
Γ⊢f≡f′:Π(x:A)→BΓ,a:A⊢fa≡f′a:B[x↦a]
Γ⊢t≡t′:Σ(x:A)×BΓ⊢t.1≡t′.1:AΓ⊢t.2≡t′.2:B[x↦t.1]
在这个框架下,处理 η-转换就变得更加方便。
Example: Σ 类型的 η-转换
例如 t≡(t.1,t.2),
在类型的辅助下可以写成 Γ⊢t≡(t.1,t.2):Σ(x:A)×B。
套用上述公式将 t′ 替换成 (t.1,t.2),得到
Γ⊢t≡(t.1,t.2):Σ(x:A)×BΓ⊢t.1≡(t.1,t.2).1:AΓ⊢t.2≡(t.1,t.2).2:B[x↦t.1]
将其中的 (t.1,t.2).1 和 (t.1,t.2).2 都 normalize 成 WHNF 即可得到
Γ⊢t≡(t.1,t.2):Σ(x:A)×BΓ⊢t.1≡t.1:AΓ⊢t.2≡t.2:B[x↦t.1]
显而易见这个转换是成立的。
Example: Π 类型的 η-转换
同样地,也有 Γ⊢f≡(λx⇒fx):Π(x:A)→B,
套用上述公式将 f′ 替换成 (λx⇒fx),得到
Γ⊢f≡(λx⇒fx):Π(x:A)→BΓ,a:A⊢fa≡(λx⇒fx)a:B[x↦a]
将其中的 (λx⇒fx)a normalize 成 WHNF 即可得到
Γ⊢f≡(λx⇒fx):Π(x:A)→BΓ,a:A⊢fa≡fa:B[x↦a]
显而易见这个转换也是成立的。
Bidirectional conversion
如果观察两种转换检查的方式,不难发现:
- 基于语法的转换检查可以产生类型信息。如转换检查函数应用 fa 时,产生类型信息 B[x↦a]。
- 基于类型的转换检查可以消耗类型信息。如转换检查Π类型的 η−规则时,消耗类型信息 Π(x:A)→B
这与双向类型检查十分类似,其 synthesis/infer
阶段产生类型信息,其 inherit/check
阶段消耗类型信息。所以转换检查可以分为两个方向:
- 当比较中性 Term 时, 推导 (synthesis/infer) 它们的类型
- 当比较 WHNF 时, 使用推导出来的类型协助 η−转换
为了建立“双向”的直觉,需要定义新的符号:
- Γ⊢u≡v⇒A 表示对 u 和 v 进行基于语法的转换检查,并推导得出其在语境 Γ 下都具有类型 A。
- Γ⊢u≡v⇐A 表示在已知 u 和 v 在语境 Γ 下都具有类型 A 的情况下,进行基于类型的转换检查。
RefTerm
Γ⊢a≡a′⇒Aidentity(a)=identity(a′)Γ=Γ′,a:A
成型规则
Γ⊢Π(x:A)→B≡Π(x′:A′)→B′⇒typeΓ⊢A≡A′⇒typeΓ,x:A⊢B≡B′[x′↦x]⇒type
Γ⊢Σ(x:A)×B≡Σ(x′:A′)×B′⇒typeΓ⊢A≡A′⇒typeΓ,x:A⊢B≡B′[x′↦x]⇒type
消除规则
Γ⊢fa≡f′a′⇒B[x↦a]Γ⊢f≡f′⇒Π(x:A)→BΓ⊢a≡a′⇐A
Γ⊢t.i≡t′.i′⇒B[xi−1↦t.(i−1)]Γ⊢t≡t′⇒Σ(xj:Aj)×Bi=i′
其中,i=1,2,⋯,j+1 并且 j=1,2,⋯。
符号 u 表示 u 可以出现至少一次。
构造规则
Γ⊢f≡f′⇐Π(x:A)→BΓ,a:A⊢fa≡f′a⇐B[x↦a]
Γ⊢t≡t′⇐Σ(x:A)×BΓ⊢t.1≡t′.1⇐AΓ⊢t.2≡t′.2⇐B[x↦t.1]
Mode Switch
类似于双向类型检查,转换检查也可以在两个方向之间切换
Γ⊢u≡v⇐AΓ⊢u≡v⇒A
规律
不难发现,上面的双向转换规则可以由双向类型检查中的类型检查规则 (Type checking rule)
和类型推导规则 (Type inference rule) 导出 (How?)。
Reference