h5i
本中文版由作者借助 AI 翻译而成。作者并非中文母语者,译文未经人工逐句校对,可能存在错误。如发现问题,请在 GitHub 上提交 issue。

第 9 章

相等也是归纳类型

要陈述 0+n=n0 + n = n 这样的数论事实,就需要表达相等。虽然相等似乎需要内核内置专门的规则,它其实是按第 8 章的方法生成的归纳类型;利用它的递归子,可以把关于 aa 的事实转化为关于 bb 的事实。本章声明这个类型,说明它与定义相等的区别,并证明本书第一个实质性的定理。

声明

在 Lean 中,相等的声明是

inductive Eq {A : Sort u} (a : A) : A -> Prop where
  | refl : Eq a a

对 a,b:Aa, b : A,类型 Equ  A  a  b\Eq{u}\;A\;a\;b 是“aa 与 bb 相等”这一命题;当 AA 是 Nat\Nat 时我们把它写作 a=ba = b。用第 8 章的术语说,AA 和 aa 是参数,bb 是索引:

Equ:Π(A:Sort u). A→A→Sort 0,reflu:Π(A:Sort u). Π(a:A). Equ  A  a  a.\Eq{u} : \tPi{A}{\Sort{u}}{A \to A \to \Sort{0}}, \qquad \refl{u} : \tPi{A}{\Sort{u}}{\tPi{a}{A}{\Eq{u}\;A\;a\;a}}.

唯一的构造子 reflu\refl{u} 没有字段,它的索引项是 aa:它只能证明两边是同一个 aa 的等式。所以 Eq\Eq{} 像 Vec\mathsf{Vec} 一样是归纳族,而不是单个类型。左边由参数固定;右边可变,而构造子把它钉在左边上。

递归子:传输

非形式地说,Eq\Eq{} 的递归子表示:若 a=ba = b,则凡对 aa 成立者对 bb 也成立。 这是莱布尼茨原理,其最常见的形式是

Eq.ndrecu,v:Π(A:Sort u). Π(a:A). Π(M:A→Sort v). M  a→Π(b:A). Equ  A  a  b→M  b.\EqRec{u,v} : \tPi{A}{\Sort{u}}{\tPi{a}{A}{\tPi{M}{A \to \Sort{v}}{M\;a \to \tPi{b}{A}{\Eq{u}\;A\;a\;b \to M\;b}}}}.

给定 AA 的元素的一个性质 MM、证据 m:M  am : M\;a 和 a=ba = b 的证明 hh,它返回 M  bM\;b 的证据,我们说它沿着 hh 传输 mm。它的计算规则说,沿着 refl\refl{} 传输什么也不做:

Eq.ndrecu,v  A  a  M  m  b  (reflu  A  c)  →  m.\EqRec{u,v}\;A\;a\;M\;m\;b\;(\refl{u}\;A\;c) \;\to\; m.

第 8 章的通用方法生成的版本略为一般,Eq.ndrec\EqRec{} 是由它构造出的一个定义。

由通用方法生成的完整依赖递归子

由于 bb 是索引,motive 接受 bb 以及证明 h:Equ  A  a  bh : \Eq{u}\;A\;a\;b,并且只有一个小前提,对应 reflu\refl{u},没有字段也没有假设:

Eq.recu,v:Π(A:Sort u). Π(a:A). Π(M:(Π(b:A). Equ  A  a  b→Sort v)). M  a  (reflu  A  a)→Π(b:A). Π(h:Equ  A  a  b). M  b  hEq.recu,v  A  a  M  m  b  (reflu  A′  c)  →ι  m.\begin{array}{l} \EqRecDep{u,v} : \tPi{A}{\Sort{u}}{\tPi{a}{A}{\tPi{M}{(\tPi{b}{A}{\Eq{u}\;A\;a\;b \to \Sort{v}})}{{}}}} \\[2pt] \qquad\qquad M\;a\;(\refl{u}\;A\;a) \to \tPi{b}{A}{\tPi{h}{\Eq{u}\;A\;a\;b}{M\;b\;h}} \\[8pt] \EqRecDep{u,v}\;A\;a\;M\;m\;b\;(\refl{u}\;A'\;c) \;\iotared\; m. \end{array}

Eq\Eq{} 是子单元(一个构造子,没有字段),所以 vv 是任意的:相等的证明可以用来构造数据,而不只是证明。非依赖的 Eq.ndrecu,v\EqRec{u,v} 是 motive 忽略证明的特例:

Eq.ndrecu,v:=λ(A:Sort u). λ(a:A). λ(M:A→Sort v). λ(m:M  a). λ(b:A). λ(h:Equ  A  a  b). Eq.recu,v  A  a  (λ(b′:A). λ(_:Equ  A  a  b′). M  b′)  m  b  h.\begin{array}{l} \EqRec{u,v} \mathrel{:=} \tLam{A}{\Sort{u}}{\tLam{a}{A}{\tLam{M}{A \to \Sort{v}}{\tLam{m}{M\;a}{\tLam{b}{A}{\tLam{h}{\Eq{u}\;A\;a\;b}{{}}}}}}} \\[2pt] \qquad\qquad \EqRecDep{u,v}\;A\;a\;(\tLam{b'}{A}{\tLam{\_}{\Eq{u}\;A\;a\;b'}{M\;b'}})\;m\;b\;h. \end{array}

在良类型的可归约式中 bb 和 cc 都可转换为 aa,所以规则可以忽略它们。图 9.1 总结了这些常量。

图 9.1。相等的常量及其计算规则。Eq.ndrecu,v\EqRec{u,v} 的规则由其定义经 δ、β 以及 Eq.recu,v\EqRecDep{u,v} 的规则推出。

Equ:Π(A:Sort u). A→A→Sort 0Lean: Eqreflu:Π(A:Sort u). Π(a:A). Equ  A  a  aEq.reflEq.recu,v:Π(A:Sort u). Π(a:A). Π(M:(Π(b:A). Equ  A  a  b→Sort v)). M  a  (reflu  A  a)→Π(b:A). Π(h:Equ  A  a  b). M  b  hEq.recEq.ndrecu,v:Π(A:Sort u). Π(a:A). Π(M:A→Sort v). M  a→Π(b:A). Equ  A  a  b→M  bEq.ndrec (definition)\begin{array}{lcll} \Eq{u} &:& \tPi{A}{\Sort{u}}{A \to A \to \Sort{0}} & \text{Lean: } \texttt{Eq} \\[2pt] \refl{u} &:& \tPi{A}{\Sort{u}}{\tPi{a}{A}{\Eq{u}\;A\;a\;a}} & \texttt{Eq.refl} \\[2pt] \EqRecDep{u,v} &:& \tPi{A}{\Sort{u}}{\tPi{a}{A}{\tPi{M}{(\tPi{b}{A}{\Eq{u}\;A\;a\;b \to \Sort{v}})}{M\;a\;(\refl{u}\;A\;a) \to \tPi{b}{A}{\tPi{h}{\Eq{u}\;A\;a\;b}{M\;b\;h}}}}} & \texttt{Eq.rec} \\[2pt] \EqRec{u,v} &:& \tPi{A}{\Sort{u}}{\tPi{a}{A}{\tPi{M}{A \to \Sort{v}}{M\;a \to \tPi{b}{A}{\Eq{u}\;A\;a\;b \to M\;b}}}} & \texttt{Eq.ndrec}\ \text{(definition)} \end{array}
Eq.recu,v  A  a  M  m  b  (reflu  A  c)→ιmEq.ndrecu,v  A  a  M  m  b  (reflu  A  c)→δβιm\begin{array}{rcl} \EqRecDep{u,v}\;A\;a\;M\;m\;b\;(\refl{u}\;A\;c) &\iotared& m \\[2pt] \EqRec{u,v}\;A\;a\;M\;m\;b\;(\refl{u}\;A\;c) &\to_{\delta\beta\iota}& m \end{array}

例如,设 P:Nat→Sort 0P : \Nat \to \Sort{0},a,b:Nata, b : \Nat,h:a=bh : a = b,pa:P  a\mathit{pa} : P\;a。取 motive M=PM = P,得到

Eq.ndrec1,0  Nat  a  P  pa  b  h  :  P  b.\EqRec{1,0}\;\Nat\;a\;P\;\mathit{pa}\;b\;h \;:\; P\;b.

这一行就是重写的逻辑内容:关于 aa 的事实变成关于 bb 的事实。第 10 章的 rw 策略会自动构造这样的项。

定义相等与命题相等

现在有了两种相等概念,区分它们是理解证明助手行为的关键。

  • 定义相等 ≡\conv 由内核通过计算来判定。它是关于项的判断,不是类型;不涉及任何证明对象。
  • 命题相等 Eq\Eq{} 是一个类型,它的证明是项,而找到一个证明可能需要真正的推理,例如归纳。

构造子 refl\refl{} 把二者联系起来:由 T-Conv,只要 a≡ba \conv b,就有 reflu  A  a:Equ  A  a  b\refl{u}\;A\;a : \Eq{u}\;A\;a\;b。所以每个定义相等都免费给出一个命题相等,但反之不成立,标准的例子是 n+0=nn + 0 = n 和 0+n=n0 + n = n 这两半。采用第 7 章 add\add 的定义,并记 ss 为它的步骤函数,

n+0  →δβ  Nat.rec1  (λ(_:Nat). Nat)  n  s  zero  →ι  n,n + 0 \;\to_{\delta\beta}\; \NatRec{1}\;(\tLam{\_}{\Nat}{\Nat})\;n\;s\;\zero \;\iotared\; n,

所以 refl1  Nat  n:n+0=n\refl{1}\;\Nat\;n : n + 0 = n,Lean 用 rfl 证明它。相反,

0+n  →δβ  Nat.rec1  (λ(_:Nat). Nat)  0  s  n0 + n \;\to_{\delta\beta}\; \NatRec{1}\;(\tLam{\_}{\Nat}{\Nat})\;0\;s\;n

是卡住的:nn 是变量,而 ι 规则只在 zero\zero 和 succ\suc 上触发。所以 0+n=n0 + n = n 不能用 refl\refl{} 证明,需要归纳。

归约浏览器展示了这两个计算,其中 nn 是上下文中的变量。第一个到达 nn;第二个停在递归子处,等待一个永远不会到来的构造子。

def add : Nat -> Nat -> Nat := fun (m : Nat) => fun (n : Nat) => Nat.rec1 (fun (_n : Nat) => Nat) m (fun (k : Nat) => fun (r : Nat) => Nat.succ r) n
def add : Nat -> Nat -> Nat := fun (m : Nat) => fun (n : Nat) => Nat.rec1 (fun (_n : Nat) => Nat) m (fun (k : Nat) => fun (r : Nat) => Nat.succ r) n
def add : Nat -> Nat -> Nat := fun (m : Nat) => fun (n : Nat) => Nat.rec1 (fun (_n : Nat) => Nat) m (fun (k : Nat) => fun (r : Nat) => Nat.succ r) n

# n + 0 = n 由计算成立:递归子看到的是构造子 zero。
goal forall (n : Nat), Eq Nat (add n Nat.zero) n
intro n
rfl
qed

# 0 + n = n 则不然:递归子卡在变量 n 上。
goal forall (n : Nat), Eq Nat (add Nat.zero n) n
intro n
rfl

示例:证明 0 + n = n

我们用归纳证明 Π(n:Nat). 0+n=n\tPi{n}{\Nat}{0 + n = n},使用 Nat.rec0\NatRec{0} 和 motive M0=λ(n:Nat). 0+n=nM_0 = \tLam{n}{\Nat}{0 + n = n}。

基础情形。 我们需要 M0  0M_0\;0,它可转换为 0+0=00 + 0 = 0。由于 0+00 + 0 计算为 00,项 refl1  Nat  0\refl{1}\;\Nat\;0 即可。

归纳步骤。 给定 k:Natk : \Nat 和 ih:0+k=k\mathit{ih} : 0 + k = k,我们需要 M0  (succ  k)M_0\;(\suc\;k),即 0+succ  k=succ  k0 + \suc\;k = \suc\;k。由第二条 ι 规则,0+succ  k0 + \suc\;k 计算为 succ  (0+k)\suc\;(0 + k),所以只需证明 succ  (0+k)=succ  k\suc\;(0 + k) = \suc\;k。我们从 refl1  Nat  (succ  (0+k))\refl{1}\;\Nat\;(\suc\;(0 + k)) 出发,它是 succ  (0+k)=succ  (0+k)\suc\;(0+k) = \suc\;(0+k) 的证明,然后用 motive N=λ(b:Nat). succ  (0+k)=succ  bN = \tLam{b}{\Nat}{\suc\;(0 + k) = \suc\;b} 沿着 ih\mathit{ih} 传输它,这把第二个 0+k0 + k 替换为 kk:

step=λ(k:Nat). λ(ih:0+k=k). Eq.ndrec1,0  Nat  (0+k)  N  (refl1  Nat  (succ  (0+k)))  k  ih.\mathit{step} = \tLam{k}{\Nat}{\tLam{\mathit{ih}}{0 + k = k}{ \EqRec{1,0}\;\Nat\;(0 + k)\;N\;(\refl{1}\;\Nat\;(\suc\;(0 + k)))\;k\;\mathit{ih}}}.

证明为

zero_add=Nat.rec0  M0  (refl1  Nat  0)  step  :  Π(n:Nat). 0+n=n.\mathit{zero\_add} = \NatRec{0}\;M_0\;(\refl{1}\;\Nat\;0)\;\mathit{step} \;:\; \tPi{n}{\Nat}{0 + n = n}.

内核用算法 5.1 检查它:每个参数都对照 Nat.rec0\NatRec{0} 的类型中以 M0M_0 实例化后的相应部分进行检查,每次比较都在规范化之后成功。下面是完整的项,一次性提交给内核,其中非依赖递归子先定义,如图 9.1 所示。

def add : Nat -> Nat -> Nat := fun (m : Nat) => fun (n : Nat) => Nat.rec1 (fun (_n : Nat) => Nat) m (fun (k : Nat) => fun (r : Nat) => Nat.succ r) n

# 层级 (1, 0) 的 Eq.ndrec:沿着 a = b 把 M a 的证明传输到 M b。
def ndrec : forall (A : Sort 1), forall (a : A), forall (M : A -> Sort 0), M a -> forall (b : A), Eq A a b -> M b := fun (A : Sort 1) => fun (a : A) => fun (M : A -> Sort 0) => fun (m : M a) => fun (b : A) => fun (h : Eq A a b) => Eq.rec0 A a (fun (b' : A) => fun (_h : Eq A a b') => M b') m b h

goal forall (n : Nat), Eq Nat (add Nat.zero n) n
exact Nat.rec0 (fun (n : Nat) => Eq Nat (add Nat.zero n) n) (Eq.refl Nat Nat.zero) (fun (k : Nat) => fun (ih : Eq Nat (add Nat.zero k) k) => ndrec Nat (add Nat.zero k) (fun (b : Nat) => Eq Nat (Nat.succ (add Nat.zero k)) (Nat.succ b)) (Eq.refl Nat (Nat.succ (add Nat.zero k))) k ih)
qed

手写这样的项很繁琐,而找到正确的 motive 才是难点:motive NN 必须恰好在 0+k0 + k 出现的两处之一提及 bb。策略(第 10 章)把记账工作和 motive 的选择都自动化了。

内核的变化

从命题逻辑走到带相等的算术,内核改动很少:Infer\Infer 增加了常量的情形,它在 Σ\Sigma 中查找类型(T-Const);Check\Check 没有变化;只有 Whnf\Whnf,以及经由它的 IsDefEq\IsDefEq,学会了新的 δ 步骤和 ι 步骤。第 12 章展示单个 Whnf\Whnf 如何处理每一种归纳类型,第 14 章讨论归纳类型给元理论带来的变化。

练习

证明相等的对称性:用 Eq.ndrec\EqRec{} 构造一个类型为 Π(a:Nat). Π(b:Nat). a=b→b=a\tPi{a}{\Nat}{\tPi{b}{\Nat}{a = b \to b = a}} 的项。

解答

用 motive λ(x:Nat). x=a\tLam{x}{\Nat}{x = a} 沿着 h:a=bh : a = b 传输证明 refl  a:a=a\refl{}\;a : a = a,这把 a=aa = a 变成 b=ab = a: λ(a:Nat). λ(b:Nat). λ(h:a=b). Eq.ndrec1,0  Nat  a  (λ(x:Nat). x=a)  (refl1  Nat  a)  b  h\tLam{a}{\Nat}{\tLam{b}{\Nat}{\tLam{h}{a = b}{\EqRec{1,0}\;\Nat\;a\;(\tLam{x}{\Nat}{x = a})\;(\refl{1}\;\Nat\;a)\;b\;h}}}。

goal forall (a : Nat), forall (b : Nat), Eq Nat a b -> Eq Nat b a
exact fun (a : Nat) => fun (b : Nat) => fun (h : Eq Nat a b) => ndrec Nat a (fun (x : Nat) => Eq Nat x a) (Eq.refl Nat a) b h
qed

(其中 ndrec 的定义同上面的演练场。)

解释为什么 Eq\Eq{} 虽是命题却能消去到每个宇宙,而有两个构造子的类型 Or 却不能。

解答

Eq\Eq{} 是子单元:一个构造子,没有字段,所以由其递归子定义的函数无法区分 a=ba = b 的两个证明,因为构造任何这样的证明只有一种方式。Or 有两个构造子,所以它到 Nat\Nat 的递归子可以对两个注入返回不同的数,使程序的值依赖于提供的是哪个证明。第 8 章精确地给出了这条规则。

要点回顾

  • Eq\Eq{} 是归纳族,参数为 AA 和 aa,索引为 bb,只有一个没有字段的构造子 refl\refl{}。
  • 它的递归子沿着 a=ba = b 的证明把关于 aa 的事实传输为关于 bb 的事实,而在 refl\refl{} 上什么也不做。这就是重写。
  • 定义相等由计算判定;命题相等是一个类型,其证明可能需要归纳。refl\refl{} 把前者变成后者。
  • n+0=nn + 0 = n 是定义相等,0+n=n0 + n = n 不是,因为加法对第二个参数递归。