开头
先聊个事。我之前在一篇算法笔记里看别人用 Lean 语言写了一条定理,紧接着机器“啪”地一下就给出了一个证明,配文是“编译器帮我验证完了”。当时我第一反应是:编译器的活不是把代码变成机器码吗?它凭什么验证数学证明?后来我花了好几个晚上,从装环境到写出第一个被 Lean 接受的证明,才算真正明白这句话的意思。
这篇教程,我想把这三件事拧成一股绳讲清楚:Lean 是什么、为什么说“编译器验证数学证明”能成立、以及 AI 在这里面到底能帮你省多少事。如果你是想入门形式化数学、对证明助手好奇、或者平时写数学证明但总担心哪里漏了条件,那你来对地方了。我默认你会一点编程,但不要求你懂数理逻辑。跟着我从零搭环境、敲第一个定理、再让 AI 帮你把活儿干完,整个过程大概两三个小时,走完你就会发现自己已经能看懂 Lean 社区里不少内容了。
我目前用的是 Lean 4,这是当前最主流的版本。如果你之前搜到过 Lean 3 的教程,先别往下看,两者语法差距不小,照着 Lean 3 的写法抄到 Lean 4 里会报错报得你怀疑人生。这篇文章全按 Lean 4 来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
1. 先搞明白:为什么一门“编程语言”能证明数学定理
1.1 编译器不只是翻译,更是验证者
我们平时写代码,编译器做的事情一般有两件:把源代码翻译成目标机器能跑的东西,以及检查程序有没有“不合规则”的地方。比如你声明了一个变量是整数,后面给它赋了一个字符串,编译器就会跳出来说“类型不对”。这里的本质是编译器在利用一套类型规则做逻辑推理:我先规定 2 + 2 的结果必须是整数,你赋了一个字符串,这跟规则矛盾,所以报错。
这个“类型检查”机制,往前再走一步,就是证明助手能干活的地基。如果你把“类型”这个概念再抽象一层,让它不仅仅是 int、string、bool 这种东西,而是可以把“一个数学命题”本身当作一种类型,那么写一个命题的证明,就等价于构造一个满足该类型的实例。机器说自己“验证通过”,意思是它检查了你构造出来的东西,确认没有违反任何规则。
打个比方:你在银行填写存款单,柜员不是猜你填得对不对,而是拿一张规则表逐项打勾。你的每笔数字、日期、签名都必须满足表格上的约束,有一个不满足就退回。数学证明在 Lean 里走的就是这条路——每一步都让机器查一遍规则表,全部通过才算“证明完成”。
1.2 Lean 的定位:数学定理的“形式化裁判”
Lean 是一门依赖类型(Dependent Type Theory)的函数式编程语言,但它最重要的身份是“证明助手”,也就是一个交互式定理证明工具。你给它一个数学命题,它会问你:你有证据吗?你给出推理链,它逐条检查,最后点头说“这条命题在你的公理体系内成立”。或者说不上来哪里不对,但会提示某一步不能从已知条件推出。
这背后的机制是 Curry-Howard 同构,这个术语听起来吓人,其实意思很简单:一个命题可以被当作一种“类型”,而这个命题的证明,就是这种类型的一个“值”。换句话说,“证明存在”等同于“类型有实例”。机器只要确认你能构造出这个实例,就相当于确认命题成立。
所以“编译器验证数学证明”不是比喻,而是字面意义上的实现。Lean 里确实有一个内核(kernel)在做类型检查。你写文档、写策略、甚至不小心写了个错误的推理,最后都要过内核这一关。这个内核还刻意做得很小,目的是减少出漏洞的概率——因为一旦内核有bug,机器承认的证明就有可能是错的。
1.3 一个例子看懂“编译器验证数学证明”
光说理论没意思,直接看一个例子。打开 Lean,输入这段代码:
lean复制theorem add_comm_two (a b : Nat) : a + b = b + a := by
omega
如果你已经装了 Lean 扩展,鼠标放在这行代码上,会看到它显示一个绿色的标志,意思是通过了。如果你硬要手动写证明,也可以这样:
lean复制example (a b : Nat) : a + b = b + a := by
induction a with
| zero =>
simp
| succ a ih =>
simp [Nat.add_succ, ih]
这里 induction、simp 是 Lean 提供的证明策略(tactic)。它们不是在替你“猜答案”,而是在展开数学归纳法的每一步,并把化简过程展示给机器内核,让内核逐行确认。最终内核确认两个方向都没问题,于是这个定理就在 Lean 的体系里被“接受”了。
看到这里你就明白,所谓“编译器验证数学证明”,其实是一套非常朴素但无比严格的机制:你给出一串推理动作,机器检查这串动作每一步是否符合逻辑规则,全部合规就通过。跟编译器的关系,本质是同一种东西:类型检查。这也是 Lean 这类工具现在能在形式化数学、程序验证、甚至AI辅助推理研究里不断出现的根本原因。
2. 环境搭建:Lean 编译器与 IDE 安装(VS Code 方案)
2.1 安装 Lean 4 编译器后端
先说结论:装 Lean 最省事的方式是用官方提供的 elan 工具链管理器,类似 Rust 社区里的 rustup。
Linux 或 macOS 用户在终端里跑:
bash复制curl -fsSL https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh | sh
Windows 用户我建议直接装 WSL,然后在 Ubuntu 里执行上面命令。如果你非要在 Windows 原生环境用,也可以去 GitHub 上找 Lean 4 的 Windows 安装包,但后续依赖管理会相对折腾,第一次入门不建议硬磕。
装完后确认一下:
bash复制lean --version
正常会输出类似 Lean (version 4.x.x) 的信息。没输出说明环境变量没配对,检查一下 ~/.elan/bin 是否在 PATH 里。
2.2 VS Code 扩展配置
编译器装好只是开始,日常写证明你基本不会直接跟命令行打交道,而是靠编辑器实时反馈。目前最成熟的方案是 VS Code 加 Lean 官方扩展。
打开 VS Code,到扩展市场搜索 lean4,认准发布者是 Lean Prover 的扩展,安装。装好后打开任意一个 .lean 文件,编辑器会自动启用 Lean 语言服务器。
然后建一个项目。Lean 4 里推荐用 lake 管理项目,类似 Python 里的 pip,只是它同时管编译和依赖。创建一个新项目:
bash复制lake new my_lean_test
cd my_lean_test
这会生成一个包含 lakefile.lean、Main.lean 的工程。如果你想用标准库之外的数学库 Mathlib,在 lakefile.lean 里加上依赖:
lean复制require mathlib from git
"https://github.com/leanprover-community/mathlib4.git"
然后跑:
bash复制lake update
lake build
Mathlib 内容非常多,第一次构建可能要等十几分钟甚至更久,别急,这很正常。如果只是体验 Lean 基本功能,不装 Mathlib 也行,但我强烈建议还是装上,因为很多常用定理都在里面,后文案例也会用到。
2.3 验证环境是否正常
环境装好后,新建一个 Test.lean,写一行最简单的定义:
lean复制def hello : String := "Hello Lean"
保存后右侧的 Lean 面板应该会显示没有错误,状态栏附近提示 “No errors”。再写一个简单证明:
lean复制example : 1 + 1 = 2 := by
norm_num
如果鼠标停在 norm_num 上时没有红色错误波浪线,说明你的 Lean 环境已经能正常处理证明过程了。norm_num 是一个专门处理自然数算术运算的策略,后面会细说。
2.4 搭建时容易踩的坑
我装环境的时候踩过几个坑,分享出来帮你省时间。
第一个坑是版本不匹配。如果你直接用系统包管理器装 Lean,装到的很可能是旧版本,和 VS Code 扩展不兼容。解决方法是统一从 elan 装,并且在项目里通过 lakefile.lean 锁定 Lean 版本。
第二个坑是第一次构建 Mathlib 内存占用特别高。我笔记本当时 8G 内存直接卡死,后来换到 16G 才顺利构建。如果你内存不大,可以先不引入 Mathlib,用 Lean 标准库学基础,等后面真需要复杂定理再 lake update。
第三个坑是代理网络问题。lake update 要从 GitHub 拉代码,如果网络不稳定会失败。没有现成代理的话,多试几次,或者考虑用 gitee 镜像,但镜像版本不一定是最新,建议下载失败时先切换到官方源重试。这里就不展开讲网络细节了。
3. 核心语法:从命题到证明,三步写出一条被机器承认的数学证明
3.1 命题怎么写:Prop 与判断
在 Lean 里,数学命题属于一种特殊类型,叫 Prop。你可以把它当成“真值仓库”,一个命题被声明后,它还只是个类型,然后再通过证明构造它的实例。
看这几行:
lean复制#check 2 + 2 = 4
#check ∀ x : Nat, x + 0 = x
#check ∃ y : Nat, y * 2 = 6
#check 是 Lean 里的调试命令,用来查看表达式的类型。第一条会输出类似 2 + 2 = 4 : Prop 的内容,后两天分别是全称命题和存在命题。
注意,2 + 2 = 4 的类型是 Prop,并不等于 Lean 已经承认它是真的。你还需要提供一个证明,Lean 才能把它当作真命题使用。这个区分非常反直觉,但非常重要——很多新手在这里卡住,以为写出一行等式就完事了,机器只是说“这是个命题,不是个定理”。
3.2 定理怎么写:Theorem 与类型论
写成定理的语法是这样的:
lean复制theorem two_plus_two : 2 + 2 = 4 := by
norm_num
它由三部分构成:定理名 two_plus_two、命题表达 2 + 2 = 4、以及后半部分的证明过程。后半部分可以是直接给出的证明项,也可以是 by 加策略组合。
在 Lean 4 里,theorem、lemma、example 都用来声明断言,区别只在语义上:theorem 是主定理,lemma 是辅助定理,example 表示“我只是举例说明这个命题成立,不需要命名”。
3.3 证明怎么写:逐步演算与 tactic
如果你只想跟机器证明一件事:要么直接写出证明项(term proof),要么用策略(tactic)一步步改造目标,直到目标变成显而易见的等式。
很多人刚接触 Lean 时会觉得“证明”像在弹钢琴——你不是一次性把整个谱子扔给机器,而是按出每一个音符,机器实时告诉你这个音对不对。
看一个战术示例:
lean复制example (a b c : Nat) (h1 : a = b) (h2 : b = c) : a = c := by
calc
a = b := h1
_ = c := h2
calc 策略让你以“链式等式”的方式书写推理过程。h1、h2 是已知条件的名字,最后一行 a = c 是要证明的结论。机器会逐个检查等式的传递,逻辑链条完整才算通过。
还有一个高频策略是 rw,用于“改写”。比如你知道 a = b,那在目标里凡是出现 a 的地方都可以替换成 b:
lean复制example (a b : Nat) (h : a = b) : a + 1 = b + 1 := by
rw [h]
写完 rw [h] 之后,目标变成 b + 1 = b + 1,这就非常简单了,甚至可以再补一个 rfl(自反性,即左右完全相同)。
3.4 实战:证明一个简单的数学命题
上个综合点的例子。我们想证明:对于任意自然数 n,n + 0 = n 成立。这个命题在数学里叫做“加法右单位元”,直接看是显然的,但 Lean 需要你把“显然”拆解成机器能识别的步骤。
lean复制theorem add_zero_right (n : Nat) : n + 0 = n := by
induction n with
| zero =>
simp
| succ k ih =>
simp [Nat.add_succ, ih]
我用的是数学归纳法。induction n 把目标拆成两条:
zero情形:0 + 0 = 0,simp能直接算出来。succ k情形:假设k + 0 = k成立,要证明(k + 1) + 0 = k + 1。simp [Nat.add_succ, ih]会把递归定义展开,再套用归纳假设,最终绕回结论。
写完以后 Lean 面板里出现绿色对勾,这个定理就被机器“接受”了。你会发现整个过程与其说是数学推理,不如说你在帮助机器拆解递归定义和条件传递。
3.5 常用 tactic 速查表
写证明时你会频繁用到下面这些策略,我把它们整理成了速查表,方便你随时回来翻。
| 策略 | 作用 | 典型使用场景 |
|---|---|---|
rfl |
证明两侧定义上完全相等 | 证明 1 + 1 = 2 但不需要算法 |
simp |
化简表达式,自动使用定义和部分定理 | 处理递归结构、简化目标 |
rw [h] |
用等式 h 替换目标中的项 |
借助已知恒等式改写目标 |
calc |
链式等式推理 | 分步展示证明过程 |
induction n |
对自然数或归纳类型做数学归纳 | 证明与自然数有关的命题 |
cases h |
对命题或类型做分情况讨论 | 处理布尔条件、析取命题 |
omega |
线性算术自动化证明 | 证明一阶自然数或整数等式/不等式 |
exact h |
直接给出现成的证明项 | 当已知条件与目标完全一致时 |
4. AI 如何帮你写 Lean 证明:真正能落地的用法
4.1 让大模型生成 Lean 语句的可用套路
既然话题里有 AI,那这部分得多说几句。说实话,AI 目前在 Lean 里不是万能的,但它确实能在几个环节帮上大忙:帮你把非正式数学语言翻译成 Lean 语法、给你补全 tactic 序列、以及在报错后帮你调整证明策略。
不过你要记住一个关键点:大模型写出来的 Lean 代码有时根本编译不过,而且它自己不知道错在哪。所以正确姿势是“AI 生成 + 人工验证”:让 AI 给一个候选证明,你把代码扔进 Lean 环境里看结果,如果不通过再把错误信息反馈给 AI 让它重写。
我给一个常用提示词模板,你可以直接复制去用:
text复制你是一名 Lean 4 证明助手专家。请帮我把下面的数学命题形式化,并给出可编译的 theorem 证明。
命题:对于任意自然数 n,n * 1 = n 成立。
要求:使用 Lean 4 语法,避免使用 Mathlib 以外的自定义公理,证明尽量简洁。
模型通常会给出一版代码,里面可能用了 omega、ring 或者 simp。你复制到本地跑一遍,看有没有错误。这一步真的能节省大量查语法的时间。
4.2 目前社区常用辅助工具
现在已经有一些专门为 Lean 设计的 AI 工具和插件。
lean_explorer是一个 web 工具,能可视化 Lean 证明状态,AI 生成的 tactic 是否正确一看便知。- VS Code 里的
github copilot也可以直接补全 Lean 代码,虽然它不专门针对 Lean 训练,但因为你身边有大量证明示例,它在局部补全上偶尔表现不错。 - 社区里还有
repl和proofwidgets等交互工具,主要是辅助调试证明状态,AI 生成结果后可以借助它们检查中间状态。
工具虽多,核心还是那件事:AI 负责“猜”,Lean 负责“验证”。你作为人的工作,就是不断把验证结果反馈给 AI,让它修正猜测。
4.3 人人都能跑起来:用 LLM 辅助写一个证明
举个例子。我让一个通用大模型“证明自然数加法结合律”,也就是 (a + b) + c = a + (b + c)。它给了我这样一段代码:
lean复制theorem add_assoc_test (a b c : Nat) : (a + b) + c = a + (b + c) := by
induction a with
| zero =>
simp
| succ a ih =>
simp [Nat.add_succ, ih]
我复制到 Lean 里,报错提示 simp 无法完成 all goals。然后我把错误信息粘给它,它改成:
lean复制theorem add_assoc_test (a b c : Nat) : (a + b) + c = a + (b + c) := by
induction a with
| zero =>
rfl
| succ a ih =>
rw [Nat.add_succ, Nat.add_succ, ← ih, Nat.add_succ, Nat.add_succ]
这次通过了。整个过程五分钟之内,我一个人查文档可能要半小时。这就是 AI 在这个领域的真实价值:它不一定直接给你终极答案,但能把检索、尝试、报错迭代的循环极大缩短。
4.4 AI 输出和 Lean 的适配性:为什么生成结果经常报错
我总结了几类高频报错。
第一类是版本混淆。如果你喂给模型的示例是 Lean 3 语法,它会像模像样生成 by exact add_comm,但 Lean 4 里许多定理名已经变了。所以提示词里必须强调“Lean 4,不要用 Lean 3 语法”。
第二类是命名空间问题。数学库里很多定理全名很长,比如 Nat.mul_comm,AI 可能漏了 Nat. 前缀。报错时你检查一下命名空间,补齐就能过编译。
第三类是策略顺序不对。AI 经常把两个策略拼成一行,但其中一个策略执行后目标变化了,后一个策略就不适用。遇到这种情况,可以把目标切细,逐步按 by 块分步验证。
记住一个原则:AI 生成的证明不是成品,而是半成品。它最大的价值在于帮你突破“不知道从哪下手”的空白,但最终的可靠性一定由 Lean 内核把关。
5. 实操过程与核心环节实现:完整示例从定义到证明
5.1 实操案例:证明“自然数加法交换律”
加法交换律的形式化写法是:
lean复制theorem add_comm_nat (a b : Nat) : a + b = b + a := by
induction a with
| zero =>
simp
| succ a ih =>
simp [Nat.add_succ, ih]
这里偷偷用了 simp 帮忙处理了大量展开工作。但为了看清过程,你也可以手写详细步骤:
lean复制theorem add_comm_nat' (a b : Nat) : a + b = b + a := by
induction a with
| zero =>
symm
show b + 0 = b
simp
| succ a ih =>
simp [Nat.add_succ, ih]
rw [Nat.succ_eq_add_one]
ring
后者用到了 symm 反转等式方向,再用 show 改写待证明目标,最后用 ring 解决环运算。这么写的好处是你能看到每一步到底在干什么,适合初学阶段理解策略行为。
5.2 实操案例:一个 AI 辅助闭环
假设我现在要证明“如果 a 是偶数,那么 a^2 也是偶数”。
先写定义:
lean复制def IsEven (n : Nat) : Prop := ∃ k, n = 2 * k
然后让 AI 帮我生成:
lean复制theorem even_square {a : Nat} (h : IsEven a) : IsEven (a^2) := by
rcases h with ⟨k, rfl⟩
use 2 * k^2
ring
这条代码能直接跑过。你注意看,rcases h with ⟨k, rfl⟩ 是把 ∃ k 拆出来,得到 a = 2 * k,然后 rfl 直接告诉 Lean:目标里的 a 就是 2 * k。ring 策略专门处理多项式等式,最后一步直接完成整型运算。
这就是 AI 辅助证明的一个典型闭环:人给出命题,AI 给出初步证明,Lean 验证,如果失败则人机协作迭代。等到通过,这个证明就是一个可复现的数学工件,任何人、任何机器看过它都能确认命题成立。
5.3 实操心得:如何阅读 Lean 错误信息
Lean 报错往往很抽象,我见过新手对着 unsolved goals 看半天不知道从何改起。我的经验是,先把鼠标悬停在红色波浪线上,右侧信息面板会显示当前目标和已知条件。然后多看最底下一条,错误通常出现在第一个未完成的目标上。
如果提示 unsolved goals,意思是某条证明路径没有完全闭合,你还需要继续提供步骤。如果提示 unexpected token,很大概率是括号或逗号写错了位置。如果提示 type mismatch,意味着你给出的表达式类型与目标不一致,这时看看是不是定理名拼错或者漏了参数。
调试 Lean 证明有一个很实用的技巧:在 by 后面只写一个策略,然后看目标变化,再逐步加下一个策略。这样出了问题你能精准定位到是哪个策略搞不定,而不是一团乱麻里找线索。
6. 常见问题与排查技巧实录
6.1 编译环境相关
如果你运行 lake build 提示找不到 mathlib,先确认 lakefile.lean 里有没有正确声明依赖。还有种情况是 lake 自带的 manifest 文件记录了旧依赖地址,此时可以删掉 lake-manifest.json 重新 lake update。
如果是 VS Code 里一直不提示错误,先看右下角是不是有 Lean 的语言服务状态。如果卡在 Building 状态,说明语言服务器还在加载依赖。大型依赖首次加载可能十几分钟,这不是卡死,是在干活,耐心等一下。
6.2 证明卡住了怎么办
证明写到一半,simp 化简不动了,这是常态。我会按这个优先级排查:
- 先看目标里有没有需要展开的定义,有的话用
unfold或者simp [定义名]展开。 - 再看已知条件里有没有可用的等式,用
rw [条件]改写目标。 - 如果目标包含结构类型或存在量词,试试
constructor或use来构造。 - 实在不行,放大招:用
omega或者ring试一把,这两个自动化策略能解决大量线性算术和环等式问题。
记住一条原则:证明卡住不等于思路错了,很可能只是某个符号没有被展开成机器易处理的形式。你把定义展开、条件用上,路往往就通了。
6.3 速查表:常见报错与解决
| 报错信息 | 含义 | 常见解法 |
|---|---|---|
unsolved goals |
还有目标没证完 | 继续补策略,或检查目标是否被误改 |
type mismatch |
表达式类型不一致 | 检查定理名、参数顺序、是否漏写前缀 |
unknown identifier |
编译器没见过某个名字 | 补全导入或包依赖 |
simp made no progress |
化简策略失效 | 换 rw、unfold,或考虑对目标做归纳 |
unexpected token |
语法错误 | 检查括号、关键字拼写 |
cannot synthesize |
无法推断类型/实例 | 显式标注类型,或添加类型类实例 |
maximum recursion depth exceeded |
死循环或过度递归 | 避免直接使用目标本身做递归假设 |
我之前在写一个集合论练习时,连续报错十几分钟,最后发现是自己在 rw 里用错了等式方向。rw [← h] 表示反向重写,如果不小心把方向搞反,机器自然卡住。这种细节最浪费生命,但也最容易通过查看局部目标来排除。
6.4 与 AI 协作时的排错顺序
如果你让 AI 帮忙写代码,不要直接把答案整段粘贴到 Lean 里然后瞪大眼睛。我的顺序是:
- 先让 AI 把目标类型和证明开头写出来,我拿去让 Lean 判断这个“开头”合法吗。
- 如果开头合法,再一步步让 AI 补全策略,每补一步我就在 Lean 里验证一次。
- 遇到错误,把完整错误信息发给 AI,附带当前目标截图或者文本,让它根据实际目标重新推荐策略。
- 如果两次迭代都没过,考虑是命题本身表达有问题,这时候先检查定义是否合理,局部条件是否充分,而不是继续盲试策略。
这样做的好处是,你能很清楚地区分“问题出在 Ideation(思路)”还是“问题出在 Coding(实现)”。AI 更适合处理 Coding 层面的调整,而数学思路的方向性判断,你最好自己先把关。
结尾
我在实际操作里的体会是,Lean 确实颠覆了我以前对“数学证明”的理解。以前我写证明,觉得自己说清楚、逻辑讲通就够了,但机器告诉我光有逻辑直觉远远不够——每一条命题都必须被严格拆解为机器承认的推理步骤。这个过程很虐,但也很上瘾,因为它迫使你把“理所当然”这四个字彻底从脑子里剔除。
最后分享一个小技巧:如果你刚开始学,别贪多。每天只挑一个小命题,比如“自然数加法的结合律”“乘法对加法的分配律”,用 Lean 把它完整证明一遍,然后回过头用 AI 辅助重构一版更简洁的证明。坚持一周,你会发现再看数学书的定理时,脑海里会自动浮现“如果写成 Lean,我需要拆成几步”这种奇怪但高效的习惯。
这个方向的后续扩展空间也很大,比如用 Lean 验证程序正确性、参与形式化数学库的建设,或者结合 AI 做自动定理证明研究。你在学会基础语法之后,打开 Mathlib 的源码逛一圈,会发现自己站在一条完全不同的数学进路上——不是人脑单打独斗,而是人机协作、逐步形式化的新路径。想深入的话,Lean 社区官方的《Theorem Proving in Lean》是一份值得精读的免费文档。现在你环境都搭好了,去写你的第一行 theorem 吧。
