直觉主义逻辑与 BHK 解释:什么算一个证明
形式化方法导引系列的主线问题是:“为什么编译通过就是定理得证”。要把这条等式撑起来,第一步是回答一个看似更基本的问题:什么算一个证明。
中学数学里"证明"通常意味着一段以"证:"开头、以"证毕"结束的自然语言论证。这种证明里允许使用反证法、双否定消去、排中律,论证的合法性靠人类同行评议把关。这条路线下,"证明"是一份可被阅读和判断的文档。
形式化系统走另一条路。证明被要求成为一个可以被机械构造、机械检查的对象。这条要求一旦贯彻到底,传统数学里的"反证"和"排中"会被部分剥离,剩下一种叫直觉主义逻辑或构造主义逻辑的子系统。BHK 解释(Brouwer-Heyting-Kolmogorov)正是把"证明"在这个子系统里重新定义成"构造一个见证物"的规范。
本篇展开 BHK 翻译表,解释为什么它天然导向 Curry-Howard 同构,并用 Python 把表里的每一条编码成一个数据类。
古典逻辑与直觉主义逻辑的分歧
考虑一条最简单的古典定理:
存在两个无理数
a、b,使得a^b是有理数。
经典反证:考虑 sqrt(2) ^ sqrt(2)。如果它是有理数,取 a = b = sqrt(2) 即可;如果它是无理数,取 a = sqrt(2)^sqrt(2)、b = sqrt(2),则 a^b = sqrt(2)^2 = 2,是有理数。无论哪种情况,命题成立。
这段证明在古典逻辑下完全合法。问题在于:它没有告诉读者到底是哪一组 (a, b)。论证依赖排中律——“要么 sqrt(2)^sqrt(2) 是有理数,要么不是”——而对哪种情形真正发生不作回答。
直觉主义视角拒绝这种"不指明见证就声称存在"的证明。构造主义者要求:
要证明
∃x. P(x),必须给出一个具体的x,并附上P(x)的证明。
整条逻辑在这种要求下被重新过滤:
| 原则 | 古典逻辑 | 直觉主义逻辑 |
|---|---|---|
排中律 P ∨ ¬P |
公理 | 不被普遍接受 |
双否定消去 ¬¬P → P |
公理 | 不普遍成立 |
| 反证法 | 标准手段 | 只在证 ¬P 时合法 |
∃x. P(x) |
可以非构造性声明 | 必须给出见证 |
P ∨ Q |
可以由 ¬(¬P ∧ ¬Q) 推出 |
必须明确指出哪一支成立 |
直觉主义逻辑不是"古典逻辑的扩展",而是"古典逻辑的子系统":它能证明的命题是古典逻辑能证明命题的真子集,但它证明的每一条命题都附带一个见证物。
这一点是形式化系统选择直觉主义为内核的根本原因:见证物可以是程序,程序可以被类型系统检查。
BHK 解释:把每条联结词翻译成"构造方法"
BHK 解释由 Brouwer、Heyting、Kolmogorov 在 20 世纪上半叶分别提出,给"什么是 φ 的证明"提供了一份按命题结构归纳的定义:
| 命题形式 | 一个证明是什么 |
|---|---|
⊥(恒假) |
没有证明 |
原子命题 A |
由系统给定,例如某条公理或假设 |
P ∧ Q |
一对:P 的证明 与 Q 的证明 |
P ∨ Q |
一个带标签的证明:(left, P 的证明) 或 (right, Q 的证明) |
P → Q |
一个方法:把 P 的任一证明转换成 Q 的一个证明 |
¬P |
P → ⊥ 的一个证明,即"把 P 的任一证明转换成 ⊥ 的证明"的方法 |
∀x : A, P(x) |
一个方法:给定任意 x : A,产出 P(x) 的一个证明 |
∃x : A, P(x) |
一对:一个具体的见证 x : A 与 P(x) 的一个证明 |
这张表里有几件事值得读者反复回味:
- “证明"被换成了"对象”。每条联结词的证明都是一个可命名、可传递、可拆开的东西。这件东西不是论证,而是数据。
→的证明是方法。直觉主义说"P → Q成立"不是声明"P真时Q也真",而是给出一个把P的证明加工成Q的证明的具体过程。¬P没有特殊地位。它就是P → ⊥。研究"否定"的工具就是研究"函数"的工具。∃必须给见证。回看前面那条"存在两个无理数a、b使a^b有理"的古典证明:它在直觉主义下不算证明,因为没有给出(a, b)是哪一对。
任何熟悉 CS 课程的读者读到这张表都会有一个反应:这看起来就是一组类型!配对类型、和类型、函数类型、依值函数类型、依值积类型——下一篇 Curry-Howard 同构正式把这个直觉变成定理。
为什么排中律失守
直觉主义下 P ∨ ¬P 不能作为公理,原因在 BHK 解释下变得直接:
要证明 P ∨ ¬P,必须给出:
- 一个
(left, P 的证明),或者 - 一个
(right, ¬P 的证明)。
如果对 P 一无所知,例如 P = "黎曼猜想",目前没有任何方法构造出哪一支。古典逻辑允许"反正必有一支成立",直觉主义要求"现在就指出是哪一支"。
排中律不是"被禁止",而是"不被白送"。在某些具体情形下排中律可证:例如 P = (n = 0 ∨ n ≠ 0),对自然数 n 这条总可以构造性判定。直觉主义只是拒绝把它当成万能公理。
工程上读者更熟悉的对照:
| 古典立场 | 工程对照 |
|---|---|
| “存在解,但我不告诉你怎么找” | 编译器告诉你 Object x = findIt(),但 findIt 抛出 UnsupportedOperationException |
“P ∨ ¬P 总成立,但具体哪支不管” |
函数签名是 Either<Yes, No>,但实现是 throw new Error("undecided") |
双否定消去 ¬¬P → P |
知道"不存在反例",就声称"有正例"——但代码没给出来 |
直觉主义把这些情形剔除,剩下的命题都自带一段可运行的"构造代码"。
用 Python 把 BHK 解释编码出来
把 BHK 表用 Python 数据类落地,可以让"证明是数据"这件事变得肉眼可见。先准备命题骨架:
1 | |
下面写两条小定理作为示范。
定理 1:P ∧ Q → Q ∧ P。BHK 解释里它的证明是"输入一对 (p, q),输出 (q, p)":
1 | |
输出:
1 | |
and_swap 是一个普通函数,它的代码就是 P ∧ Q → Q ∧ P 的证明。
定理 2:(P → Q) → (Q → R) → (P → R)。这条等价于函数组合:
1 | |
输出:
1 | |
证明的核心是 lambda p: qr.method(pq.method(p))——函数组合。古典逻辑里这条定理是一句口号,直觉主义里它是一段三行的代码。
反例:尝试在 Python 里写 P ∨ ¬P 的"通用证明"。它需要对任意 P,要么给出 InL(p: P),要么给出 InR(np: Not[P])。P 是任意类型,构造 p: P 通常做不到,构造 np: P → Bottom 也做不到。这正好对应排中律在直觉主义里失守。
模式提炼
| 模式 | 内容 |
|---|---|
| 证明即对象 | 把"证明 P"理解成"构造一个属于 P 的对象",而不是"写一段说服人的论证" |
→ 即方法 |
蕴含的证明就是把前提证明加工成结论证明的程序 |
∃ 必须给见证 |
存在性主张必须附带一个具体的 x 和 P(x) 的证明 |
| 否定即归谬函数 | ¬P 没有特殊地位,它就是 P → ⊥ 的证明 |
| 排中律不白送 | P ∨ ¬P 在直觉主义下需要逐案构造,不能预设 |
这五条模式是后续所有文章的"地基"。后面 STLC、依值类型、归纳类型只是给这套地基补上类型规则、机械检查算法、足够丰富的类型构造子。
给读者的练习
- 用上面的数据类证明
(P ∧ Q) → P,并跑出输出。 - 用上面的数据类证明
P → (Q → P)。这条定理叫常函数律。 - 用上面的数据类证明
¬¬¬P → ¬P。注意这条在直觉主义下成立,而¬¬P → P不成立——尝试构造¬¬P → P时会卡在什么地方? - 想清楚:为什么
(P → ⊥) → ⊥ → P也不可证?
练习答案的形态都是一个 Python 函数,函数体就是 BHK 意义下的证明。
衔接到下一篇
本篇把"证明"重新定义成"构造一个对象"。BHK 表里每条联结词都对应一个常见的数据类型——配对、标签和、函数、依值函数、依值对。这不是偶然。
下一篇正式给出 Curry-Howard 同构:把 BHK 表的每一行升级成命题与类型之间的一一对应,并把"证明检查"和"类型检查"对位。从那里开始,"形式化编译器编译通过就是定理得证"这条主线问题的第一层翻译就被打通了。
参考资料
- L. E. J. Brouwer. Über die Bedeutung des Satzes vom ausgeschlossenen Dritten in der Mathematik, insbesondere in der Funktionentheorie. 1924.
- Arend Heyting. Intuitionism: An Introduction. North-Holland, 1956.
- Dirk van Dalen. Logic and Structure. Springer, 2013.(第 5 章"直觉主义逻辑")
- Per Martin-Löf. Intuitionistic Type Theory. Bibliopolis, 1984.
- Stanford Encyclopedia of Philosophy: Intuitionistic Logic.






