学习进度 · 一目了然
数据存在本机浏览器里,换设备会从零开始。
成就 · 里程碑
边学边解锁,全部存在本机浏览器。
课程地图 · 从零到第一行证明
7 节课,每节都有知识点、可运行的示例和本课小练习。按顺序学,学完一节自动打勾。
学习路线 · 一镜到底
7 节课按顺序递进,每节都建立在上一节之上。
四大工具 · 随取随用
练习场、练习册、战术速查、AI 助手——遇到问题不用退出本站。
符号速查 · 打字不卡壳
练习场编辑器里输入反斜杠开头的代号、按空格,自动变成符号。点卡片复制代号。
命令速查 · 写证明的骨架
战术负责「怎么证」,命令负责「声明什么」。示例代码全部经官方编译器核验。
import Mathlib
引入标准数学库,norm_num / ring / omega 等战术都在里面。
import Mathlibexample
直接声明并证明一个命题,用完即弃。
example : 1 + 1 = 2 := by
norm_numtheorem
给证明起名字,之后可以在别处引用。
theorem my_add_zero (n : Nat) : n + 0 = n := by
rfllemma
短定理,作用和 theorem 一样。
lemma prime_two_le (p : Nat) (hp : Nat.Prime p) : 2 ≤ p := by
exact hp.two_ledef
定义自己的对象、函数、概念。
def double (n : Nat) : Nat := n + n#check
查任何名字的类型——记不住定理名就靠它。
#check Nat.Prime#eval
真算一遍表达式,验证你的直觉——右侧 Lean Infoview 信息栏会显示结果(如 5)。
#eval 2 + 3:= 与 by
:= 把名字绑定到定义;by 之后进入战术证明模式。
example : 2 ∣ 10 := by
norm_num认识 Lean:一个会检查证明的程序
本课目标
- 知道定理证明器是干什么的——一个检查数学证明的程序
- 认识三件套:
import Mathlib、example、by - 在练习场跑通第一个证明
1 + 1 = 2
目标:知道定理证明器是什么,跑通你的第一个证明,学会看报错。
01定理证明器是什么
编译器会检查你的代码有没有类型错误;Lean 干的是同一件事,但检查对象是数学证明。你写下一个命题和一个证明,Lean 的内核逐行验证:这个证明到底证没证出这个命题。写对了机器放行,写错了机器报错——不需要人眼复核,也不存在"看起来对"。
这套思路叫形式化验证(formal verification)。近年数学界最热的新闻都跟它有关:陶哲轩团队把多项式 Freiman–Ruzsa 猜想的证明形式化进了 Lean,谷歌 AlphaProof 基于 Lean 在 IMO 2024 达到银牌水平(DeepMind 官方口径)。
02第一个证明:example 与 by
Lean 代码的基本句式:example : 命题 := by 战术。意思是我声明一个命题,然后用一串战术把它证掉。下面这行就是"证明 1+1=2"——战术 norm_num 负责直接计算数值等式。注意第一行 import Mathlib:它引入数学库,norm_num、ring、omega 这些战术都在里面。练习场默认文件本来就有这一行,站内示例也都自带——直接覆盖粘贴就能跑。
import Mathlib example : 1 + 1 = 2 := by norm_num
运行后右侧应显示证明通过(No goals to display)。把 2 改成 3 再跑一次,你会看到报错——恭喜,你见到了 Lean 的拒绝。
03两种运行方式
- 网页版:live.lean-lang.org(本站练习场已内嵌),浏览器直接跑,零安装,适合入门。
- 本地版:VS Code + elan 工具链,加载 mathlib 后跑得飞快,适合长期深入。
大一阶段用网页版就够了;等你想做自己的数学项目,再装本地版不迟。
04报错是老师,不是敌人
初学 Lean,一半的时间在看报错。报错会精确到哪一行、哪个战术、期望什么得到什么。学会把报错读完整,你的进步会快一倍——读不懂就贴给 AI 助手。
本课小练习
本课小结
- 证明交给 Lean 内核逐行检查:写对放行,写错报错
norm_num直接计算数字等式,是第一个偷懒战术
下一步:去练习册做入门 01–02(rfl / exact),然后开始第 1 课。
命题即类型:exact 与 rfl
本课目标
- 理解「命题是类型,证明是值」这句话
- 会用
exact把已有证明交出去 - 弄懂为什么
n + 0 = n能 rfl、0 + n = n不能
目标:理解"证明是值",学会两个最基础的战术 exact 和 rfl。
01命题是类型,证明是值
Lean 里有一句核心口号:命题是类型,证明是它的元素。就像 Nat 这个类型的元素是自然数 0、1、2……一样,一个命题 P : Prop 的"元素"就是 P 的证明。
所以 h : P 读作"h 是 P 的一个证明"。证明不是玄学,是一个可以传递、组合、存储的对象。
02exact:用现成的证明收尾
当目标是一个命题、而你手里正好有它的证明时,exact h 直接完工。这是最朴素的战术。
import Mathlib example (P : Prop) (h : P) : P := by exact h
注意括号里的 h : P 是前提:假设 P 成立,我们要证明 P 成立——把 h 原样交出去就行。
试一个失败的例子:把 example : 1 + 1 = 3 := by norm_num 粘进练习场跑一次——右侧会报错。第一次看到失败是好事:说明机器真的在检查,而不是装样子。
03rfl:定义上相等就用它
当等式两边"按定义就是同一个东西"时,rfl 一步到位。比如加法在 Lean 里按第二个参数递归定义,第一条就是 n + 0 = n,所以:
import Mathlib example (n : Nat) : n + 0 = n := by rfl
但注意:0 + n = n 就不能用 rfl——0 在递归参数之外,0 + n 无法按定义展开。怎么证它?第 4 课揭晓(答案:归纳)。
本课小练习
本课小结
rfl只证「两边定义上相同」,需要推理的等式得换战术exact h把 h 当证明交出去——证明就是值
下一步:练习册入门 03–04(intro / apply),然后开始第 2 课。
战术工具箱:intro / apply / constructor
本课目标
intro:面对P → Q,先接住前提apply:把目标「退回」成已有前提constructor:拆开∧目标
目标:掌握三个最高频的战术,能独立证明简单的逻辑命题。
01intro:把前提接进手里
P → Q 的证明,本质是一个函数:给它 P 的证明,它还你 Q 的证明。intro hP 就是"把参数接住"——之后你的手里多了一个假设 hP : P,目标变成 Q。
import Mathlib example (P Q : Prop) : P → Q → P := by intro hP hQ exact hP
两个 → 就连着 intro 两次。最后手里有 hP、hQ 两个假设,目标 P——exact hP 收工。
用函数的话说:intro hP 是「定义这个函数的参数」,exact hP 是「返回参数」。把这个类比刻进脑子里,后面 apply 的逆向用法就顺理成章。
02apply:逆向使用蕴含
目标和 intro 相反:目标已经是 Q,而假设里有 h : P → Q。这时 apply h 把目标"退回"成 P——如果你能证明 P,就顺着 h 得到 Q。
import Mathlib example (P Q : Prop) (h : P → Q) (hP : P) : Q := by apply h exact hP
03constructor:拆开"且"
目标形如 P ∧ Q(P 且 Q)时,constructor 把它拆成两个子目标 P 和 Q,分别证明即可。子目标之间用 · 分行,代码更清晰。
import Mathlib example (P Q : Prop) (hP : P) (hQ : Q) : P ∧ Q := by constructor · exact hP · exact hQ
本课小练习
本课小结
- 蕴含就是函数:
intro接参数,exact返回结果 apply倒着拆目标,constructor拆合取
下一步:练习册入门 05–08,然后开始第 3 课(逻辑联结词)。
逻辑联结词:∧ ∨ ¬
本课目标
- 构造
∧用constructor,使用用h.1/h.2 - 构造
∨用left/right,使用用cases - 记住
¬P的本质是P → False
目标:会用三个逻辑联结词的"构造"和"使用"两种方向。
01合取 ∧:构造用 constructor,使用用 h.1 / h.2
要证明 P ∧ Q:constructor 拆成两个子目标。反过来,手里有 h : P ∧ Q 时,h.1 是 P 的证明,h.2 是 Q 的证明——像取元组的第一个、第二个分量。
import Mathlib example (P Q : Prop) : P ∧ Q → Q ∧ P := by intro h constructor · exact h.2 · exact h.1
02析取 ∨:构造用 left / right
要证明 P ∨ Q,只需证明其中一边成立:left 把目标变成 P,right 把目标变成 Q。
import Mathlib example (P Q : Prop) : P → P ∨ Q := by intro hP left exact hP
03析取 ∨ 的使用:cases 分情况
手里有 h : P ∨ Q 时,用 cases h with | inl hp => ... | inr hq => ... 分成两支:inl 分支假设 P 成立(得到 hp : P),inr 分支假设 Q 成立(得到 hq : Q)。两支都证完,整个目标才算完。
import Mathlib example (P Q : Prop) (h : P ∨ Q) : Q ∨ P := by cases h with | inl hp => right; exact hp | inr hq => left; exact hq
注意:构造 ∨ 用 left / right,使用 ∨ 用 cases——这是「两个方向」的又一例。
04否定 ¬:本质是"导致矛盾"
在 Lean 里,¬ P 的定义就是 P → False:P 推出矛盾。所以处理否定 = 处理蕴含:要证 ¬ P 就 intro;手里有 hn : ¬ P 就把 P 的证明喂给它。
import Mathlib example (P : Prop) : P → ¬ ¬ P := by intro hP hnn exact hnn hP
(¬ ¬ P 就是 (P → False) → False,所以 intro 两次。)
本课小练习
本课小结
- 逻辑词分「构造」和「使用」两个方向,战术各司其职
cases是析取的关键:分两支逐个击破
下一步:练习册进阶 13–15(cases / 排中律 / 反证),然后开始第 4 课。
数学归纳:自然数的真相
本课目标
- 理解自然数归纳法原理:基例 + 递推
- 会写
induction n with的两个分支 - 会用归纳假设
ih收尾
目标:理解归纳原理,亲手证明 0 + n = n。
01自然数是怎么定义的
自然数只有两种:0,或某个数的后继 succ n(也就是 n+1)。加法按第二个参数递归定义,两条规则:
n + 0 = n(定义第一条,直接成立)n + succ m = succ (n + m)(先算 n+m 再加一)
这就是第 1 课里 n + 0 = n 能用 rfl 的原因——它是定义本身;而 0 + n = n 不在定义里,必须用归纳证明。
02归纳原理:两步走
要证明"对所有 n,P(n) 成立",只需两步:
- 基础步(base):证明 P(0);
- 归纳步(step):假设 P(n) 成立(这叫归纳假设 ih),证明 P(succ n) 也成立。
因为每个自然数要么是 0、要么是某个数的后继,这两步就覆盖了全部自然数。Lean 的 induction n with | zero => ... | succ n ih => ... 精确对应这两步。
03实战:0 + n = n
import Mathlib example (n : Nat) : 0 + n = n := by induction n with | zero => rfl | succ n ih => simp [ih]
拆开看:
zero情形:目标0 + 0 = 0,定义直接成立,rfl。succ n ih情形:目标0 + succ n = succ n。按加法定义化简成succ (0 + n) = succ n,再用归纳假设 ih 把0 + n换成 n——simp [ih]一步做完。
这就是数学归纳在 Lean 里的全部:每一步机器都盯着。想把这个结论存成定理,把 example 换成 theorem 名字 即可——mathlib 里它叫 Nat.zero_add。
小贴士:上面的 simp 叫「化简战术」,会自动尝试一堆常见化简规则;simp [ih] 表示「允许它使用归纳假设 ih 来重写」。第 5 课会正式讲它,现在只要记住:simp 方括号里放的是「允许它使用的假设」。
本课小练习
本课小结
- 归纳两步:
zero分支是基础,succ n ih分支是递推 - 递推步骤两把常用刷子:
simp [ih](化简)或omega(线性算术 + ih)
下一步:练习册进阶 12 与挑战 25–26(整除 / gcd),然后开始第 5 课。
计算战术四兄弟:norm_num / ring / omega / simp
本课目标
norm_num:具体数字;ring:多项式;omega:线性不等式rw:用等式重写目标,←反向simp:自动化简,simp [h]用上假设
目标:让机器替你算数,学会偷懒的正确姿势。
01norm_num:数字计算、整除、素数
凡是"具体数字"的等式、不等式、整除、素数判定,norm_num 基本都能直接算出来。
import Mathlib example : 7 * 6 = 42 := by norm_num
02ring:多项式等式
含变量、需要展开或合并的多项式等式(交换律、结合律、分配律、平方展开),ring 全包。
import Mathlib example (x y : Nat) : (x + y) ^ 2 = x ^ 2 + 2 * x * y + y ^ 2 := by ring
这就是你熟悉的完全平方公式——机器替你展开并核对两边。
03omega:线性等式与不等式
涉及加法、减法、乘常数、大小比较的线性问题,omega 能自动推理,还能用上已有的假设。
import Mathlib example (a b : Nat) (h : a ≤ b) : a + 1 ≤ b + 1 := by omega
04办案顺序
遇到代数目标,按这个顺序试:norm_num → ring → omega → simp。它们解决不了的时候,才需要你亲自动手想思路。
05rw:用等式重写目标
手里有等式 h : a = b,目标里出现 a 时,rw [h] 把目标里所有 a 替换成 b;要反向替换用 rw [← h]。
import Mathlib example (a b : Nat) (h : a = b) : a + 1 = b + 1 := by rw [h]
rw 是最常用的战术之一——练习册挑战 22 用的就是它。
06simp:自动化简
simp 自动应用一批"化简规则"(如 0 + n = n、n + 0 = n、n * 0 = 0),适合清理"显然但啰嗦"的代数式。
import Mathlib example (n : Nat) : 0 + n + n = n + n := by simp
import Mathlib example (a b : Nat) (h : a = b) : a + 0 = b := by simp [h]
区别:simp 只做定义化简,simp [h] 还会用你给的假设 h 替换。数学证明里"加 0、乘 0、0 加"这类小尾巴,都交给它。
本课小练习
本课小结
- 代数目标按
norm_num → ring → omega → simp的顺序试,剩下的才自己证 rw [h]用等式重写,← h反向,一次可接多个
下一步:练习册挑战 17–24,然后开始第 6 课(初等数论)。
初等数论实战:整除与素数
本课目标
- 整除
∣与现成定理dvd_refl/dvd_trans - 素数
Nat.Prime与性质two_le - 最大公约数
Nat.gcd与gcd_comm
目标:用 mathlib 现成的数论概念做证明,体验"站在巨人的肩膀上"。
01整除 ∣:norm_num 直接算
2 ∣ 10 读作"2 整除 10"。对具体数字,norm_num 会做除法验证;对符号,mathlib 提供了整套整除理论。
import Mathlib example : 2 ∣ 10 := by norm_num
02dvd_refl:任何数整除自己
整除的"自反性"——n ∣ n——不需要计算,mathlib 里就有定理 dvd_refl,直接用 exact 引用。
import Mathlib example (n : Nat) : n ∣ n := by exact dvd_refl n
这就是用现成定理的姿势:知道名字,exact 引过来。
03素数判定:Nat.Prime
mathlib 里的素数概念是 Nat.Prime。对具体数字,norm_num 内置了素数判定。
import Mathlib example : Nat.Prime 7 := by norm_num
04最大公约数:Nat.gcd
import Mathlib example : Nat.gcd 12 18 = 6 := by norm_num
接下来怎么走:把 Natural Number Game 完整通关,或用 #check / #find 探索 mathlib 里的数论定理库。学到这里,你已经具备自己查资料推进的能力了。
05自查工具箱:sorry / #check / #eval
sorry:临时占位符,告诉 Lean「这一步先跳过」。用它把证明骨架搭起来,再逐个填坑。正式提交前必须全部消掉。#check 名字:查看一个定理/函数的完整类型。例:#check Nat.add_comm。#eval 表达式:把可计算的东西直接算给你看。例:#eval Nat.gcd 12 18得到 6。
学会这三个,你就从「等人喂答案」升级为「自己查资料」。
本课小练习
本课小结
- 数论概念在 Mathlib 里都是现成的:
dvd_trans、Nat.gcd_comm、hp.two_le - 记不住定理名?
#check在练习场里查类型
下一步:练习册挑战 25–30 收尾,翻一遍战术速查,去 lean4.dev 打毕业挑战。
练习场 · 浏览器里跑真 Lean
下方内嵌官方在线编译器(live.lean-lang.org)。点击加载后即可在浏览器里运行完整 Lean 4 + Mathlib:左边写代码,右边实时显示检查结果。首次加载数学库约需一两分钟,按需加载更省流量。
内嵌官方编译器需要下载较大资源包,按需加载更省流量。
加载不了?直接打开官方编辑器 ↗在左栏编辑器中全选(Ctrl+A),直接粘贴覆盖。示例自带 import Mathlib 行,覆盖后就能跑。
点下方示例的「复制」,把代码粘进左栏。
右栏出现「No goals to display / 证明通过」就是成功;有报错就复制去问 AI 助手。
示例代码 · 从入门到挑战
import Mathlib example : 1 + 1 = 2 := by norm_num
import Mathlib example (P Q : Prop) (hP : P) (hQ : Q) : P ∧ Q := by constructor · exact hP · exact hQ
import Mathlib example (n : Nat) : 0 + n = n := by induction n with | zero => rfl | succ n ih => simp [ih]
import Mathlib example (a b : Nat) : a + b = b + a := by omega
练习册 · 30 道手写证明
从入门写到挑战。每道题先在练习场里自己试,卡住了再看提示,最后对答案。30 道全部经官方在线编译器(live.lean-lang.org,Lean 4 + Mathlib)真实编译核验(2026-10-03),复制答案可直接运行。
入门 · 热身
example (n : Nat) : n + 0 = n := by -- 你试试怎么写
提示与答案
提示:n + 0 是加法定义的第一条——第二个参数是 0 时直接返回 n,两边定义上相同。这正是第 1 课 rfl 的用武之地。
import Mathlib example (n : Nat) : n + 0 = n := by rfl
example (P Q : Prop) (h : P → Q) (hp : P) : Q := by -- 你试试怎么写
提示与答案
提示:exact 直接把一个已有的证明交出去。h hp 是什么?把 hp 喂给 h(函数应用),就得到 Q 的证明。
import Mathlib example (P Q : Prop) (h : P → Q) (hp : P) : Q := by exact h hp
example (P Q : Prop) : P → Q → P := by -- 你试试怎么写
提示与答案
提示:蕴含就是函数:intro 依次接住两个前提 hp 和 hq。证明 P 用不到 hq,直接 exact hp 交回。
import Mathlib example (P Q : Prop) : P → Q → P := by intro hp hq exact hp
example (P Q R : Prop) (hPQ : P → Q) (hQR : Q → R) (hp : P) : R := by -- 你试试怎么写
提示与答案
提示:目标 R,apply hQR 把它"退回"成 Q,再 apply hPQ 退回成 P,最后 exact hp 收尾。
import Mathlib example (P Q R : Prop) (hPQ : P → Q) (hQR : Q → R) (hp : P) : R := by apply hQR apply hPQ exact hp
example (a b : Nat) (h : b = a) : a = b := by -- 你试试怎么写
提示与答案
提示:rw 不在乎等式写在哪个方向——rw [h] 把目标里所有 b 替换成 a,目标立刻变成 a = a。
import Mathlib example (a b : Nat) (h : b = a) : a = b := by rw [h]
example (a b c : Nat) : a + (b + c) = (a + b) + c := by -- 你试试怎么写
提示与答案
提示:加法结合律对 omega 来说只是"括号怎么摆不影响"的线性事实,一行收工。
import Mathlib example (a b c : Nat) : a + (b + c) = (a + b) + c := by omega
example (P Q R : Prop) (hP : P) (hQ : Q) (hR : R) : P ∧ Q ∧ R := by -- 你试试怎么写
提示与答案
提示:∧ 右结合,P ∧ Q ∧ R 是 P ∧ (Q ∧ R)。constructor 一次拆一层,第二层再 constructor 一次。点号 · 表示按顺序处理子目标。
import Mathlib
example (P Q R : Prop) (hP : P) (hQ : Q) (hR : R) : P ∧ Q ∧ R := by
constructor
· exact hP
· constructor
· exact hQ
· exact hR
example (P Q : Prop) (h : P → Q) : P → Q := by -- 你试试怎么写
提示与答案
提示:intro 接住前提 P 的证明 hp,然后 exact h hp——把 hp 当参数交给 h,这就是"原样返回"。
import Mathlib example (P Q : Prop) (h : P → Q) : P → Q := by intro hp exact h hp
进阶 · 组合逻辑与归纳
example (P Q : Prop) (h : P ∧ Q) : Q ∧ P := by -- 你试试怎么写
提示与答案
提示:constructor 把 Q ∧ P 拆成两个子目标;h.1 是 P 的证明,h.2 是 Q 的证明,对调着用。
import Mathlib example (P Q : Prop) (h : P ∧ Q) : Q ∧ P := by constructor · exact h.2 · exact h.1
example (P Q : Prop) (h : P) : P ∨ Q := by -- 你试试怎么写
提示与答案
提示:析取只需一边成立:left 选择证明左边 P,right 则选右边。手头有 h : P,选 left。
import Mathlib example (P Q : Prop) (h : P) : P ∨ Q := by left exact h
example (P : Prop) (h : P) : ¬ ¬ P := by -- 你试试怎么写
提示与答案
提示:¬P 的本质就是 P → False。intro 接住"¬P 的证明" hn,再把 h 喂给它得到矛盾。
import Mathlib example (P : Prop) (h : P) : ¬ ¬ P := by intro hn exact hn h
example (n : Nat) : n + n = 2 * n := by -- 你试试怎么写
提示与答案
提示:对 n 归纳:zero 情形 rfl;succ 情形展开后是线性等式,omega 会用上归纳假设 ih 收尾。
import Mathlib example (n : Nat) : n + n = 2 * n := by induction n with | zero => rfl | succ n ih => omega
example (P Q : Prop) (h : P ∨ Q) : Q ∨ P := by -- 你试试怎么写
提示与答案
提示:手里有析取就分情况:cases h 拆成 inl(P 成立)和 inr(Q 成立)两支,各自选 right/left 收尾。战术见「战术速查」。
import Mathlib example (P Q : Prop) (h : P ∨ Q) : Q ∨ P := by cases h with | inl hp => right; exact hp | inr hq => left; exact hq
example (P : Prop) : ¬ (¬ P ∧ P) := by -- 你试试怎么写
提示与答案
提示:intro 接住假设 h : ¬P ∧ P。h.1 是"P 的否定"(即 P → False),把 h.2(P 的证明)喂给它,就得到 False。
import Mathlib example (P : Prop) : ¬ (¬ P ∧ P) := by intro h exact h.1 h.2
example (P : Prop) : P ∨ ¬ P := by -- 你试试怎么写
提示与答案
提示:by_cases h : P 按"P 成立与否"分两支:成立就 left,不成立(h 就是 ¬P)就 right。这正是排中律的用法。
import Mathlib
example (P : Prop) : P ∨ ¬ P := by
by_cases h : P
· left
exact h
· right
exact h
example (P Q R : Prop) : (P → Q) → (Q → R) → P → R := by -- 你试试怎么写
提示与答案
提示:三个前提全部 intro 接住,然后像套函数一样组合:hQR (hPQ hP)。和挑战 15 是同一条路,只是假设写法不同。
import Mathlib example (P Q R : Prop) : (P → Q) → (Q → R) → P → R := by intro hPQ hQR hP exact hQR (hPQ hP)
挑战 · 计算与数论
example (a b : Nat) : (a + b)^2 = a^2 + 2 * a * b + b^2 := by -- 你试试怎么写
提示与答案
提示:含变量的多项式等式交给 ring——它负责展开、合并、消项。
import Mathlib example (a b : Nat) : (a + b)^2 = a^2 + 2 * a * b + b^2 := by ring
example (a b c : Nat) (h1 : a ≤ b) (h2 : b ≤ c) : a ≤ c := by -- 你试试怎么写
提示与答案
提示:把两条不等式假设都放进上下文,omega 对线性不等式做自动推理。
import Mathlib example (a b c : Nat) (h1 : a ≤ b) (h2 : b ≤ c) : a ≤ c := by omega
example : 2^10 = 1024 := by -- 你试试怎么写
提示与答案
提示:具体数字的算术统一交给 norm_num。
import Mathlib example : 2^10 = 1024 := by norm_num
example : Nat.gcd 12 18 = 6 := by -- 你试试怎么写
提示与答案
提示:最大公约数对具体数字是可计算的,norm_num 直接算出 6。
import Mathlib example : Nat.gcd 12 18 = 6 := by norm_num
example (n : Nat) : n ∣ n := by -- 你试试怎么写
提示与答案
提示:整除的自反性在 Mathlib 里是现成定理 dvd_refl,直接 exact。
import Mathlib example (n : Nat) : n ∣ n := by exact dvd_refl n
example (a b : Nat) (h : a = b) : a + 1 = b + 1 := by -- 你试试怎么写
提示与答案
提示:rw [h] 用假设 h : a = b 把左边的 a 重写为 b,两边定义上相同后自动收尾。
import Mathlib example (a b : Nat) (h : a = b) : a + 1 = b + 1 := by rw [h]
example (P Q : Prop) : ¬ (P ∨ Q) → ¬ P ∧ ¬ Q := by -- 你试试怎么写
提示与答案
提示:目标是蕴含:intro h 接住 ¬ (P ∨ Q),再 constructor 拆成 ¬ P 和 ¬ Q 两个子目标;每个子目标再 intro 拿到假设,最后把 Or.inl hp(或 Or.inr hq)喂给 h 制造矛盾。体会「¬P 就是 P → False」。
import Mathlib example (P Q : Prop) : ¬ (P ∨ Q) → ¬ P ∧ ¬ Q := by intro h constructor · intro hp; exact h (Or.inl hp) · intro hq; exact h (Or.inr hq)
example (a b c : Nat) (h1 : a = b) (h2 : b = c) : a = c := by -- 你试试怎么写
提示与答案
提示:rw 可以一次接多个等式:rw [h1, h2] 先用 h1 把 a 换成 b,再用 h2 把 b 换成 c,目标自动收尾。
import Mathlib example (a b c : Nat) (h1 : a = b) (h2 : b = c) : a = c := by rw [h1, h2]
example (a b c : Nat) (h1 : a ∣ b) (h2 : b ∣ c) : a ∣ c := by -- 你试试怎么写
提示与答案
提示:若 a 整除 b、b 整除 c,则 a 整除 c——Mathlib 已备好定理 dvd_trans,像挑战 21 的 dvd_refl 一样直接 exact。
import Mathlib example (a b c : Nat) (h1 : a ∣ b) (h2 : b ∣ c) : a ∣ c := by exact dvd_trans h1 h2
example (a b : Nat) : Nat.gcd a b = Nat.gcd b a := by -- 你试试怎么写
提示与答案
提示:gcd 的交换律在 Mathlib 里叫 Nat.gcd_comm。学会"搜定理名":记不住就 #check Nat.gcd_comm 在练习场里试。
import Mathlib example (a b : Nat) : Nat.gcd a b = Nat.gcd b a := by exact Nat.gcd_comm a b
example (n : Nat) : n * n + n = n * (n + 1) := by -- 你试试怎么写
提示与答案
提示:把右边的括号展开:n*(n+1) = n*n + n。这是分配律,ring 自动处理多项式。
import Mathlib example (n : Nat) : n * n + n = n * (n + 1) := by ring
example (a b c : Nat) (h1 : a = b) (h2 : c = b) : a = c := by -- 你试试怎么写
提示与答案
提示:两条等式都指向 b,方向不同。rw [h1] 先把 a 换成 b;再用 ← h2 反向把 b 换成 c,收尾。← 是 Lean 的"反向重写"符号。
import Mathlib example (a b c : Nat) (h1 : a = b) (h2 : c = b) : a = c := by rw [h1, ← h2]
example (P Q : Prop) (h : ¬ P → Q) (hnq : ¬ Q) : P := by -- 你试试怎么写
提示与答案
提示:反证法:假设 ¬P(by_contra hnP),推出矛盾。h hnP 给出 Q,与 ¬Q 的 hnq 冲突,矛盾即证。
import Mathlib example (P Q : Prop) (h : ¬ P → Q) (hnq : ¬ Q) : P := by by_contra hnP exact hnq (h hnP)
example (p : Nat) (hp : Nat.Prime p) : 2 ≤ p := by -- 你试试怎么写
提示与答案
提示:Nat.Prime 的定义自带一条性质 two_le(素数 ≥ 2)。用点号取字段:hp.two_le。这是从定义里"挖定理"的第一课。
import Mathlib example (p : Nat) (hp : Nat.Prime p) : 2 ≤ p := by exact hp.two_le
战术速查 · 24 个常用招
写证明卡住时先来这里翻。每个战术:什么时候用、怎么用。下面是语法示意,想跑真实例子去对应课程或练习册。
一步收尾
目标两边定义上完全一样时,直接收尾。
example (n : Nat) : n + 0 = n := by
rfl
你手上正好有一个目标类型的证明,直接交出去。
exact h -- h 的类型就是目标
exact h hp -- 把 hp 喂给 h,得到目标
目标恰好是上下文里的某个假设,让 Lean 自己找。
example (P : Prop) (h : P) : P := by
assumption
证明写在一个 by 块里,后面的行都是战术。
example : 1 + 1 = 2 := by
norm_num
处理蕴含 →
目标是 P → Q 时,把 P 的证明当作参数接进来,继续证 Q。
example (P Q : Prop) : P → Q → P := by
intro hp hq
exact hp
目标恰好是某个函数/定理的结论,把它"退回"成前提。
apply hQR -- 目标 R,退成 Q
apply hPQ -- 目标 Q,退成 P
逻辑构造与分解
目标是 P ∧ Q,拆成两个子目标分别证。
constructor
· exact hP
· exact hQ
目标是 P ∨ Q,只要证一边成立。
left -- 改证左边 P
right -- 改证右边 Q
手里有 P ∨ Q,按两边分别讨论。
cases h with
| inl hp => ...
| inr hq => ...
cases 的加强版:同时拆多个假设并自动命名。
rcases h with ⟨hp, hq⟩ -- 拆 h : P ∧ Q
对命题 P 分"成立/不成立"两支(依赖排中律)。
by_cases h : P
· left; exact h
· right; exact h
假设结论的否定,推出矛盾。依赖经典逻辑(排中律),在直觉主义构造里不成立。
by_contra h -- 得到 h : ¬ 目标
-- 推出 False 即可
有多个子目标时,用点号逐个击破,顺序清晰。
constructor
· exact hP
· exact hQ
归纳与化简
对自然数 n 归纳:基础情形 + 归纳步骤(ih 是归纳假设)。
induction n with
| zero => rfl
| succ n ih => omega
在证明中途先证一个小结论,给它起名字,后面引用。
have h1 : a + 0 = a := by rfl
-- 之后可用 h1
把目标弱化成更简单的中间结论,先证它,再用它推出原目标。
suffices h : a ≤ b by omega
-- 原目标归约为证 a ≤ b
应用化简规则(0+n=n 之类),也能用给定假设。
simp -- 只用化简规则
simp [h] -- 顺便用假设 h 替换
用等式把目标里的表达式换掉,← 是反向。
rw [h] -- 目标里所有 a → b(h : a = b)
rw [h1, ← h2] -- 依次重写,可接多个
像草稿纸一样逐步推导:目标 = 中间式 = … = 结论。
calc
a + 1 = b + 1 := by rw [h]
_ = c + 1 := by rw [h2]
计算自动化
具体数字的四则运算、幂、gcd、素数判断,一步算清。
example : 2^10 = 1024 := by
norm_num
交换律、结合律、分配律、平方展开,含变量也能算。
example (x y : Nat) : (x + y)^2 = x^2 + 2*x*y + y^2 := by
ring
加、减、乘常数、大小比较的线性命题,能用上下文假设。
example (a b c : Nat) (h1 : a ≤ b) (h2 : b ≤ c) : a ≤ c := by
omega
查看与排错
查一个名字的类型/声明,忘了定理长什么样用它。
#check Nat.gcd_comm -- Nat.gcd a b = Nat.gcd b a
让 Lean 真的算一个表达式并显示结果。
#eval 17 % 5 -- 2
#eval Nat.gcd 12 18 -- 6
先跳过这一步(会警告)。只用来占位置,交作业前必须补上。
example : P := by
sorry
让 Lean 在当前目标上搜索一个刚好能闭合它的现成定理,找到后在信息栏给出建议,直接写进代码即可。
by exact? -- 若目标能用现成引理直接证,会给出提示
常见报错 · 什么意思怎么办
| 报错 | 含义与对策 |
|---|---|
unknown identifier 'foo' | Lean 不认识这个名字:拼写错了,或没 import Mathlib,或这个名字根本不存在。用 #check 验证。 |
type mismatch | 你给的东西类型和目标对不上。把两边的类型都写出来对一遍,常见是把 a = b 错用成 b = a 方向。 |
unsolved goals | 战术用完了但目标没证完。看目标区还剩什么,补最后一步(rfl / exact / assumption)。 |
tactic 'rfl' failed | 两边定义上不一样。别硬 rfl,换 simp / norm_num / ring / omega,或先用 rw 化简。 |
failed to synthesize | Lean 找不到要的东西,最常见是忘了 import Mathlib;要的定理可能要用 #check 先搜出来再 exact。 |
AI 助手 · 报错看不懂?贴进来
把 Lean 代码或报错粘到下面,点「生成提问并复制」,再到豆包粘贴发送。AI 会帮你解释报错、补全证明、讲清概念。当前为免配置过渡版:不接 API、不花钱,复制即用。
把内容贴进来
支持粘贴代码、报错信息,或两者一起。不确定问什么,先点下方场景按钮。
三种问法,照着来
贴上代码和报错,问:"解释哪里错了,给出修改后的完整代码"。
贴上题目和已写的部分,问:"帮我补全证明,给完整可运行的 Lean 代码"。
贴上一段代码,问:"用大一新生能懂的方式讲这段代码用到的 Lean 概念"。