柯里 - 霍华德对应关系揭示:类型检查器为何可能出错及证明辅助工具局限
类型检查器也会出错柯里 - 霍华德对应关系揭示证明辅助工具局限Max 的博客[/][~/][~/关于我/](/about-me/) [~/系列文章/](/series/) [~/博客文章/](/blog/)2026 年 7 月 25 日在编写代码时类型检查器多次为我们节省了时间。它能确保你不会将字符串与整数相加或者返回值的引用而非值本身。然而尽管类型检查器很实用有时也会让人烦恼除了帮我们避免错误它的能力似乎也有限……你可能会惊讶地发现类型检查器也是证明辅助工具如 Lean 和 Rocq 等语言的核心。它们利用类型的结构明确检查某个陈述是否能从其他陈述推导出来或者更通俗地说验证数学证明。在这篇博客中我先介绍柯里 - 霍华德对应关系的一些基础知识接着说明它在证明辅助工具中的应用最后解释为什么这可能意味着你的类型检查器“出错”了或者说它可能不知道你是对的。柯里 - 霍华德对应关系柯里 - 霍华德CH对应关系可简单定义为证明可以表示为程序……证明可以运行。这个定义没给出太多信息我们可以这样思考既然证明能表示为程序那我们就需要一种方法让程序返回证明。但返回证明到底意味着什么呢回到基础概念如果一个程序要返回一个整数我们说它返回的类型是 $\text{int}$它可以是任意整数所以 $\text{int}$ 代表整数集。同样如果一个程序要返回 $\text{True}$ 或 $\text{False}$我们说它返回的类型是 $\text{bool}$它包含这两种可能性。将类型近似看作集合并不完全准确但对于本文来说已经足够。尝试将这种思路扩展到证明上我们可以说当一个程序返回某个事实的证明时它返回的是类型 $P(X)$ 的一个元素其中 $P(X)$ 是事实 $X$ 的所有证明的集合。为深入研究这个新的证明对象我们首先要将命题逻辑和谓词逻辑中的一些逻辑运算转换到这个新的范式中。我们从最简单的开始$$ X \text{ 为真} $$在我们的例子中要使 $X$ 为真我们必须有 $X$ 的证明即$$ \exists p : p \in P(X) $$我们这样表述$P(X)$ 是可构造的。例如$P(5 5)$ 是可构造的但 $P(5 2 6)$ 不是因为在皮亚诺算术中没有这个证明它是错误的。接下来要表示的逻辑运算是“与”$\wedge$。对于不熟悉的人来说当且仅当 $X$ 和 $Y$ 都为真时才有 $X \wedge Y$。所以 $P(X)$ 和 $P(Y)$ 都是可构造的即 $\exists p: p \in P(X)$ 且 $\exists p\prime : p\prime \in P(Y)$。这意味着我们可以构造一个对象 $(p, p\prime)$所以$$ P(X) \times P(Y) $$其中 $\times$ 表示笛卡尔积是可构造的$(p, p\prime) \in P(X) \times P(Y)$。接下来我们要表示蕴含运算。如果 $X \implies Y$那么要么 $X$ 为假要么 $X$ 为真且 $Y$ 为真。我们将其表示为从 $P(X)$ 到 $P(Y)$ 的函数的存在$$ P(X) \to P(Y) $$如果这个函数存在那么只要我们有 $X$ 的证明就可以推导出 $Y$ 的证明。如果 $X$ 为假即 $P(X)$ 不可构造那么函数没有输入所以 $P(Y)$ 可能成立也可能不成立。为简洁起见我省略了对其他标准逻辑运算的讨论。它们在集合论中的表示如下但对本文的其余部分无关紧要| 逻辑运算 | 集合论表示 || --- | --- || $X \lor Y$ | $P(X) P(Y)$其中 $$ 表示不相交并集 || $(\forall(n \in \mathbb{N})X(n))$ | $(n: \mathbb{N}) \to P(X(n))$ || $(\exists(n \in \mathbb{N})X(N))$ | $(n: \mathbb{N}) \times P(X(n))$ |证明辅助工具如何运用柯里 - 霍华德对应关系证明辅助工具利用这种对应关系和它们的类型检查器来验证证明。但它们是如何做到的呢为说明这一点让我们用 [Lean](https://lean - lang.org/) 符号来证明一个简单的定理。一个定理考虑下面名为 blog 的定理theorem blog (X Y Z: Prop) (h_1: X) (h_2: Y) (h_3: Y → Z) : X ∧ Z它首先声明 $X$、$Y$ 和 $Z$ 是逻辑陈述即它们可能为真也可能为假。这就像构造集合 $P(X)$、$P(Y)$ 和 $P(Z)$但还没有说明它们是否可构造。接下来我们有一个假设 $h_1 : X$它是 $X$ 的证明。回想一下我们之前的讨论有 $X$ 的证明就相当于 $X$ 为真所以 $h_1 : X$ 简单地表明 $X$ 为真。$h_2$ 也是类似的它是 $Y$ 的证明。然后我们有最后一个假设我用无限的创造力和智慧将其命名为 $h_3$。它的类型是$$ Y \to Z $$这意味着存在一个函数从 $Y$ 的证明可以得到 $Z$ 的证明这相当于 $Y \implies Z$之前也讨论过。定理的最后一部分是期望的结果 $X \land Z$。为证明这一点我们必须构造一个属于 $P(X) \times P(Z)$ 的元素为此我们必须构造 $X$ 和 $Z$ 的证明。理解这个定理陈述花了不少功夫。不过我希望你现在能明白之前将逻辑运算映射到集合论的讨论是如何让我们将定理从类型语言转换到逻辑领域的。该定理的证明现在我们要证明这个定理。眼尖的人可能已经注意到我们已经有了一个想要的组件。我们需要元素来填充 $P(X)$ 和 $P(Z)$而我们有 $h_1$它是 $X$ 的证明因此可以填充 $P(X)$这很容易。现在我们需要证明 $P(Z)$。我们有 $h_2 : Y$ 和 $h_3$$h_3$ 是一个函数它接受 $Y$ 的证明并给出 $Z$ 的证明。通过将 $Y$ 的证明$h_2$传递给 $h_3$我们得到了 $Z$ 的证明它可以填充 $P(Z)$。现在如何在 Lean 中编写这个证明呢有很多方法下面是其中一种我们首先将期望的结果分解为两部分然后依次填充。使用 constructor 语句我们让 Lean 告诉我们要实现期望的结果需要做什么。Lean 忠实地给出了两个目标一个是填充 $X$另一个是填充 $Z$。为填充 $X$我们可以直接告诉 Lean 它是 $h_1$使用 exact h_1。为填充 $Z$我们需要将 $h_3$ 应用到 $h_2$ 上记住$h_3$ 是一个函数。在 Lean 中这很简单就是 h_3 h_2或者你可以写成 h_3 (h_2)让它更像非函数式语言。所以我们定义一个变量 z类型为 Zhave z : h_3 h_2然后再次使用 exact 完成证明。Lean 会用“目标达成”的消息祝贺我们。完整的代码如下theorem blog (X Y Z: Prop) (h_1: X) (h_2: Y) (h_3: Y → Z) : X ∧ Z : by constructor exact h_1 have z: Z : h_3 (h_2) exact z类型检查器在文章开头我承诺要解释为什么你的类型检查器可能出错现在我就来解释。如前所述为让证明辅助工具验证你已经证明了期望的结果它会检查你是否成功输出了正确的类型即填充 $P(\text{你想要证明的内容})$ 的东西。类型检查器的局限性为让类型检查器安全地断言你已经做到了这一点它需要评估你提供的表达式序列中每个表达式的类型。这个要求存在一个问题它要求所有表达式都能完成求值。有两种情况可能导致表达式永远无法完成求值一种比较特殊的情况是 C 或 Python 中的 exit()它通过直接退出程序来逃避完成求值的要求。我们的解决方法是限制编程语言中允许的表达式这正是 Lean 和 Agda 等语言所做的。另一种表达式可能永远无法完成求值的情况更难解决。我们必须确保表达式序列不会陷入某种无限循环否则它们将永远无法完成。所以我们只需要一种方法来检查给定输入时表达式序列是否会停止。不幸的是这在有限时间内是不可能做到的。1936 年艾伦·图灵证明了一个程序是否会在有限时间内停止即停机问题是不可判定的这意味着在有限时间内无法计算。如果你想了解他用来证明这一点的图灵机的一些直觉可以看看我关于这个主题的文章 [这里](/blog/an_introduction_to_turing_machines_and_computation/)。形式语言试图回避这个事实的方法是进一步限制计算语言。在某些情况下递归可以被证明是有限的例如对自然数的向下递归。所以通过只允许可以被证明会停止的递归我们确保类型检查器总是能在有限时间内完成。需要注意的是这不是当前技术或软件的限制而是证明辅助工具的一个基本限制无法解决。总会存在一些结果其证明是无法验证的。数学后果及证明这种限制极大地降低了这些语言的表达能力意味着它们无法表示每一个可能的证明。但为什么会这样呢为进行反证我们假设受限语言有足够的表达能力来表示每个命题陈述的证明或反证明即 $\forall S$我们可以在语言中证明 $S$ 或 $\lnot S$。现在既然我们假设语言是无所不知的那就来玩一玩吧……考虑一个任意程序 $P$它有一组有限的任意输入 $A$以及命题 $H$$P$ 在输入 $A$ 时会停止。现在使用我们的语言我们知道可以写出这个命题的证明或反证明并在有限时间内验证它。我们的做法是生成 $H$ 和 $\lnot H$ 的所有可能证明然后使用类型检查器检查其中一个是否有效。由于类型检查器在有限时间内运行并且其中一个证明是正确的因为我们的语言有足够的表达能力这个过程是有限的。由于我们可以对任何程序都这样做我们现在已经能够在有限时间内检查任意程序是否会停止然而正如之前讨论的这是不可能的又是图灵的停机问题。所以我们的假设一定是错误的因此受限语言没有足够的表达能力来表示每个命题陈述的证明或反证明。柯里 - 霍华德对应关系告诉我们证明和程序是等价的但这现在导致了一个令人不安的事实。如果我们的语言必然受到限制无法证明或反驳某些命题那么这表明一般情况下可能无法做到这一点……这是数学中的一个著名问题。哥德尔第一不完备性定理指出任何能够进行一定量初等算术运算的一致形式系统都是不完备的。通俗地说对于任何用于计算的形式系统数学中的每个形式系统都是如此都存在既无法证明也无法反驳的陈述。现在我们已经证明了哥德尔第一不完备性定理的否定意味着图灵停机问题的否定因此通过逆否命题柯里 - 霍华德对应关系得出了一个令人震惊的结果。图灵停机问题意味着哥德尔不完备性定理。如果你想了解逆否命题的一些直觉可以看看我以鱼为主题的关于逆否命题的文章 [这里](/blog/some_intuition_behind_the_contrapositive/)。你的类型检查器可能出错我们现在已经看到类型检查器并不完美事实上它被证明是不完美的。因此……在某些情况下……你的类型检查器可能……出错。可能会有这样的情况你的类型检查器为了避免无限运行而拒绝了你的代码但实际上它是正确的。当然这种情况不太可能发生例如 Rust 中的类型检查器递归限制是 128。但这是有可能的所以当你的同事抱怨你的代码无法通过类型检查时要知道……你可能是正确的虽然可能性不大 :)。结论在这篇文章中我们绕了一大圈说明了类型检查器可能无法验证你的代码是否正确。不过它永远不会接受错误的代码所以如果它接受了你的代码你可以放心它是正确的。现在只需要找出逻辑错误了……* * *1. 特别要排除 JavaScript在那里像 $5 \text{five}$ 这样的杰作是可能的。 ↩︎2. ↩︎3. ↩︎4. ↩︎5. ↩︎6. 我们所说的可能证明是指语言中任何可能的语法表达式序列。 ↩︎7. ↩︎8. 嗯……实际上我们称这种情况为不完备而不是错误。它不会接受错误的东西只是可能不接受正确的东西。 ↩︎9. ↩︎10. Rust 类型检查器检查代码的过程当然与 Lean 或 Agda 中的检查器不同但基本限制是相同的所以这个玩笑还是成立的 。 ↩︎[ 上一篇文章](https://max - amb.github.io/blog/zero_knowledge_tolstoyan_art/)|~~下一篇文章 ~~使用 [Hugo ʕ•ᴥ•ʔ Bear](https://github.com/janraasch/hugo - bearblog/) 构建

相关新闻

如何对 eBPF 代码进行性能分析?实例展示完整流程

如何对 eBPF 代码进行性能分析?实例展示完整流程

如何对 eBPF 代码进行性能分析?实例展示完整流程在运行 eBPF 工作负载或编写 eBPF 代码时,通常希望衡量其对性能的影响。本文通过实例展示对 eBPF 代码进行性能分析的方法。例子目标是测量文件打开操作性能,代码使用 eBPF 中的文件打开钩子&a…

2026/10/14 22:09:56 阅读更多 →
Zig 增量编译:毫秒级重建复杂应用,开发效率大提升!

Zig 增量编译:毫秒级重建复杂应用,开发效率大提升!

Zig 增量编译内幕揭秘2026 年 7 月 28 日,作为 Zig 核心团队一员,参与过的最具影响力项目之一,便是在 Zig 编译器实现 _增量编译_ 功能。该功能可让编译器检测项目上次构建后函数和声明变化,仅重编更改代码,将生成字节…

2026/10/5 3:17:40 阅读更多 →
Spring AI Token成本优化与结构化输出实战

Spring AI Token成本优化与结构化输出实战

1. Spring AI 中的 Token 成本优化实战在构建基于 Spring AI 的应用时,Token 消耗直接关系到 API 调用成本。以 GPT-4 为例,其输入输出 Token 价格约为 $0.03/1K tokens,一个中型应用月消耗可能高达数千美元。通过实测发现,未经优…

2026/10/13 3:10:02 阅读更多 →

最新新闻

2026年青岛栈桥怎么玩?本地人推荐这5家必吃馆子

2026年青岛栈桥怎么玩?本地人推荐这5家必吃馆子

每年夏天,青岛栈桥总是游人如织。但说实话,大部分游客的体验都停留在“看海、拍照、吃烧烤”三件套,真正能吃到青岛本地味道的,少之又少。作为在青岛生活多年的“老土著”,今天不聊景点路线,就聊吃——栈桥…

2026/10/14 22:09:29 阅读更多 →
9颗三色珠子手链有多少种样式?Burnside引理与Polya计数详解

9颗三色珠子手链有多少种样式?Burnside引理与Polya计数详解

如果给你9颗珠子,红、蓝、黄各3颗,让你串一条手链,你觉得最后能得到多少种真正不同的样式?我第一次认真算这道题时,随口猜了个大概几百上千,结果程序跑出来是个很不起眼的94。这个答案让我愣了很久&#xf…

2026/10/14 22:09:29 阅读更多 →
矩阵旋转的底层原理:C翻转法与原地坐标变换

矩阵旋转的底层原理:C翻转法与原地坐标变换

1. 项目概述:一道被低估的矩阵操作题,藏着算法思维的底层训练价值“牛客网-旋转矩阵/C翻转【简洁实现】”——光看标题,很多人会下意识划走:不就是个二维数组遍历题?刷过LeetCode的都见过。但我在带某高校算法实训班时…

2026/10/14 22:09:29 阅读更多 →
一步法TUNEL细胞凋亡检测试剂盒孵育与核染色步骤

一步法TUNEL细胞凋亡检测试剂盒孵育与核染色步骤

在细胞凋亡检测中,TUNEL 法(TdT 介导的 dUTP 缺口末端标记法) 是原位定量分析晚期细胞凋亡的金标准。然而,在实际操作中,TUNEL 实验极易受到样本固定程度、蛋白酶消化过度或不足、组织脱水干涸以及内源性核酸酶干扰的影…

2026/10/14 22:09:29 阅读更多 →
PaddleNLP Taskflow 文本生成任务(text_generation)API 深度解析:基于 CPM 中文模型的问答与写诗实战

PaddleNLP Taskflow 文本生成任务(text_generation)API 深度解析:基于 CPM 中文模型的问答与写诗实战

人工智能大模型预训练微调LoRARLHF强化学习分布式训练 【免费下载链接】PaddleNLP Easy-to-use and powerful LLM and SLM library with awesome model zoo. 项目地址: https://gitcode.com/gh_mirrors/pa/PaddleNLP 点击查看 免费下载 本篇文章围绕 PaddleNLP pad…

2026/10/14 22:09:29 阅读更多 →
2026年科研绘图AI红黑榜:按需求对号入座

2026年科研绘图AI红黑榜:按需求对号入座

先说结论:今年测了6款科研绘图AI工具,覆盖综合写作、文科降重、理工图表三个赛道,红榜3款各有专攻,黑榜3款各有硬伤。这篇文章不吹不黑,按你的实际需求对号入座就行。 红榜黑榜怎么分的 评选标准三条:功能…

2026/10/14 22:08:28 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →