Coq/Lean 交互式教程
从零开始学习 Coq 和 Lean 证明助手
第一章:入门基础
1.1 计算机辅助证明:为什么需要 Coq 和 Lean?
我们为什么需要形式化验证?
在关键系统中,软件或硬件的错误可能带来灾难性后果:
- 1994年奔腾浮点除法错误:Intel 奔腾处理器的浮点运算单元存在 bug,导致某些除法运算结果错误,Intel 损失数亿美元。
- 1996年阿丽亚娜5号火箭爆炸:由于软件整数溢出错误,价值5亿美元的火箭在发射37秒后自毁。
- 复杂数学证明的挑战:四色定理、开普勒猜想等现代数学证明极其复杂,人工审查几乎不可能完全验证其正确性。
Coq 和 Lean 就是解决这类问题的工具。它们不仅能发现错误,更能从数学上保证代码或证明的正确性。
什么是交互式定理证明?
想象你是一位老师,指导一个非常聪明但缺乏创造力的学生(Coq/Lean)证明数学定理:
- 你告诉它每一步该做什么(应用哪个定理、如何化简表达式)
- 它会忠实地执行并告诉你当前的证明状态
- 它保证每一步的逻辑都是绝对正确的
这就是"交互式"的含义——人提供创造性的证明思路,机器保证逻辑的严格性。
Coq:久经考验的形式化先驱
Coq 诞生于1984年,由法国国家信息与自动化研究所(INRIA)开发。它的名字来源于其理论基础的提出者 Thierry Coquand。
- 理论基础:归纳构造演算(Calculus of Inductive Constructions, CIC),这是一种强大的类型论
- 核心哲学:"命题即类型,证明即程序"——每个数学命题对应一个类型,证明就是构造该类型的一个项
- 成熟应用:CompCert(完全验证的C编译器)、四色定理机器证明、密码学协议验证等
- 特色:拥有强大的策略语言 Ltac,支持从证明中提取可执行程序
Lean:现代数学家的新宠
Lean 由微软研究院的 Leonardo de Moura 于2013年发起。Lean 4(2021年发布)是一次重大升级,将证明助手和编程语言完美融合。
- 设计理念:不仅是证明助手,更是功能完备的编程语言,性能媲美传统编译语言
- 元编程能力:可以用 Lean 自身编写新的证明策略,扩展性极强
- 数学社区:拥有 Mathlib,可能是世界上最大的形式化数学库,涵盖本科到研究生级别的数学内容
- 现代化体验:语法更接近现代编程语言,VS Code 集成体验优秀
核心思想:命题即类型
理解 Coq 和 Lean 的关键是理解"命题即类型"(Curry-Howard 同构):
- 在普通编程中,
int、string是类型,42、"hello"是这些类型的值 - 在 Coq/Lean 中,命题(如
2 + 2 = 4)也是一个类型 - 该命题的证明就是这个类型的一个值
- 如果能构造出这个值,就证明了命题;构造不出,就无法证明
这个革命性的思想将"证明数学定理"转化为"编写类型正确的程序"。
Coq vs Lean:快速对比
| 特性 | Coq | Lean |
|---|---|---|
| 发布时间 | 1984年 | 2013年(Lean 4: 2021年) |
| 主要应用领域 | 软件验证、编译器验证 | 数学形式化、教学 |
| 语法风格 | ML/OCaml 风格 | 类似 Haskell/Scala |
| IDE 支持 | CoqIDE、VS Code、Emacs | VS Code(强烈推荐) |
| 社区规模 | 成熟、学术导向 | 快速增长、数学家活跃 |
| 核心优势 | 成熟稳定、工业应用多 | 现代化、性能好、Mathlib |
开始交互式证明
在开始之前,让我们先启动 Coq 的交互式环境。打开终端并输入:
$ coqtop
Welcome to Coq 8.18.0
Coq <
现在你可以输入 Coq 命令了。如果使用 CoqIDE 或 VS Code + Coq 插件,可以获得更好的交互体验。
示例:第一个证明对比
让我们证明一个简单的定理:对于任意自然数 n,n + 0 = n。这个例子展示了两种语言的基本证明结构。
Coq 版本
Theorem plus_n_O : forall n : nat, n + 0 = n.
Proof.
intros n. (* 引入任意的 n *)
induction n. (* 对 n 进行归纳 *)
- (* n = 0 *)
reflexivity. (* 0 + 0 = 0 *)
- (* n = S n' *)
simpl. (* S n' + 0 = S (n' + 0) *)
rewrite IHn. (* 使用归纳假设: n' + 0 = n' *)
reflexivity. (* S n' = S n' *)
Qed. (* 证明完成 *)
Lean 版本
theorem plus_n_zero : ∀ n : Nat, n + 0 = n := by
intro n -- 引入任意的 n
simp -- 简化并完成证明
关键差异:
- Coq 使用
Proof...Qed包围证明,Lean 使用:= by - Coq 的
introsvs Lean 的intro(注意单复数) - Lean 的
simp更智能,可以自动完成许多 Coq 需要simpl+reflexivity的证明
习题 1.1.1
解释"命题即类型"的含义。如果命题 1 + 1 = 2 是一个类型,那么什么是这个类型的值?
习题 1.1.2
比较 Coq 和 Lean 在以下方面的不同:(1) 主要应用领域 (2) 语法风格 (3) 社区特点
习题 1.1.3
为什么说形式化验证能提供"绝对的正确性"?它与传统的软件测试有什么本质区别?
1.2 安装与环境配置
定理证明器的学习和开发是在 IDE 中交互式完成的。用户不是一次性写完整个文件再编译,而是逐步编写并实时查看证明状态。因此,正确配置开发环境至关重要。
为什么需要 IDE?
交互式定理证明的核心特征:
- 逐步执行:将光标移动到某处,系统执行到该位置并显示当前状态
- 实时反馈:立即看到当前的证明目标(Proof Goals)和上下文
- 类型信息:鼠标悬停即可查看任何表达式的类型
- 错误定位:精确显示哪一行出现了什么错误
没有 IDE,几乎无法进行任何非平凡的证明开发。
第一步:安装 Coq
使用 opam(推荐方法)
opam 是 OCaml 的包管理器,也是管理 Coq 及其库的标准方式:
# 1. 安装 opam
# macOS
brew install opam
# Linux
sudo apt install opam # 或其他包管理器
# 2. 初始化 opam
opam init
eval $(opam env)
# 3. 安装 Coq
opam install coq
直接安装
# macOS (Homebrew)
brew install coq
# Linux (Ubuntu/Debian)
sudo apt-get update
sudo apt-get install coq
# Windows
# 从 https://github.com/coq/platform/releases 下载 Coq Platform 安装器
# Coq Platform 包含了 Coq 和常用库
第二步:配置 Coq 开发环境
选项 A:VS Code + Coq LSP(推荐)
这是目前最现代化的 Coq 开发环境:
- 安装 Visual Studio Code
- 在 VS Code 扩展市场搜索 "Coq LSP" 并安装
- 重启 VS Code
特点:语法高亮、自动补全、实时错误提示、集成终端
选项 B:CoqIDE
Coq 官方提供的轻量级 IDE,安装 Coq 时通常会一起安装:
# 启动 CoqIDE
coqide
特点:开箱即用、界面简洁、适合初学者
选项 C:Emacs + Proof General
传统但功能强大的选择,适合 Emacs 用户。
第三步:安装 Lean 4
使用 elan(必需)
elan 是 Lean 的版本管理器,类似于 Rust 的 rustup:
# macOS/Linux
curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh
# Windows
# 从 https://github.com/leanprover/elan/releases 下载安装器
# 安装后,重启终端或运行
source ~/.profile
配置 VS Code(必需)
重要:Lean 4 的开发体验与 VS Code 深度绑定,这是唯一推荐的开发环境。
- 安装 Visual Studio Code
- 在扩展市场搜索 "lean4" 并安装(发布者:leanprover)
- 重启 VS Code
理解 lean-toolchain
每个 Lean 项目根目录都有一个 lean-toolchain 文件,指定项目使用的 Lean 版本。VS Code 会自动读取并使用正确的版本,解决了版本兼容性问题。
项目管理
Coq 项目:_CoqProject 文件
Coq 使用 _CoqProject 文件管理项目结构:
# 创建项目目录
mkdir my_coq_project
cd my_coq_project
# 创建 _CoqProject 文件
echo "-R . MyProject" > _CoqProject
# 创建源文件
echo "Definition hello := \"world\"." > Hello.v
_CoqProject 文件示例:
-R . MyProject
-arg -w -notation-overridden
theories/Basic.v
theories/Advanced.v
Lean 项目:lake 构建系统
Lean 4 使用 lake 作为项目构建和包管理器:
# 创建新项目
lake new my_lean_project
cd my_lean_project
# 项目结构
# ├── lakefile.lean # 项目配置文件
# ├── lean-toolchain # Lean 版本
# ├── MyLeanProject.lean # 主文件
# └── MyLeanProject/ # 源代码目录
# 构建项目
lake build
添加 Mathlib 依赖(lakefile.lean):
require mathlib from git
"https://github.com/leanprover-community/mathlib4.git"
习题 1.2.1:验证你的完整环境
让我们创建一个简单的项目来验证整个工具链是否正确配置。
Coq 环境验证
- 创建一个新目录和文件:
mkdir test_coq cd test_coq echo "-R . TestCoq" > _CoqProject - 创建文件
Test.v:Definition my_first_definition : nat := 42. Check my_first_definition. Compute my_first_definition. - 在 VS Code 中打开这个文件
- 将光标放在每一行上,观察右侧面板的输出
Lean 环境验证
- 创建一个新的 Lean 项目:
lake new test_lean cd test_lean - 编辑
TestLean.lean:def myFirstDefinition : Nat := 42 #check myFirstDefinition #eval myFirstDefinition - 在 VS Code 中打开项目
- 查看 Lean Infoview 面板的输出
常见问题与解决方案
PATH 问题
症状:command not found: coqc/lean/lake
解决方案:
# Coq (使用 opam)
eval $(opam env)
# 将上述命令添加到 ~/.bashrc 或 ~/.zshrc
# Lean
# 确保 ~/.elan/bin 在 PATH 中
echo 'export PATH="$HOME/.elan/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
VS Code 扩展无法启动
解决步骤:
- 重启 VS Code
- 查看输出面板(View → Output),选择 "Coq LSP" 或 "Lean 4"
- 检查是否有错误信息
- 确保先安装了 Coq/Lean,再安装 VS Code 扩展
Lean 版本不匹配
症状:打开项目时提示版本错误
解决:这是正常的。lean-toolchain 文件指定了项目需要的版本,elan 会自动下载并使用正确版本。
网络问题
如果在中国大陆,可能需要配置镜像:
# opam 镜像
opam repository add mirrors https://mirrors.tuna.tsinghua.edu.cn/opam
# 对于 Lean,考虑使用代理或等待自动重试
1.3 基本交互与语法
在依赖类型语言中,一切皆有类型:变量有类型,类型本身也有类型。理解这一点是掌握 Coq 和 Lean 的关键。
核心理念:万物皆类型
在 Coq 和 Lean 中:
42的类型是nat/Natnat/Nat的类型是Set/Type- 命题(如
2 + 2 = 4)也是类型,属于Prop - 这就是"命题即类型"思想的体现
注释与文档
Coq 注释
(* 这是一个单行注释 *)
(* 这是一个
多行注释 *)
(** 这是一个文档注释,
可以被 coqdoc 工具提取生成文档 *)
(* ========== 分隔线 ========== *)
(* 常用于在代码中创建视觉分隔 *)
Lean 注释
-- 这是一个单行注释
/- 这是一个
多行注释 -/
/-- 这是一个文档注释
可以被提取生成文档 -/
/-! 模块级文档注释
通常放在文件开头 -/
最佳实践:为所有公开的定义和定理添加文档注释,说明其用途和参数含义。
基本交互命令
命令 1:查看类型 (Check / #check)
Coq
Check nat. (* nat : Set *)
Check 3. (* 3 : nat *)
Check list. (* list : Type -> Type *)
Check @list. (* @list : Type -> Type (显示所有参数) *)
Check List.map. (* 显示时可能包含 ?A 等隐式参数 *)
Lean
#check Nat -- Nat : Type
#check 3 -- 3 : Nat
#check List -- List.{u} (α : Type u) : Type u
#check @List.map -- 显示所有参数,包括隐式的
理解隐式参数:当你看到 @ 符号或 ?A、.{u} 等,这表示存在隐式参数。通常这些参数可以被自动推断,无需手动提供。
命令 2:查看定义 (Print / #print)
Coq
Print nat. (* 显示 nat 的归纳定义 *)
Print Nat.add. (* 显示加法的定义 *)
Print plus. (* plus = Nat.add *)
Definition mydef := 42.
Print mydef. (* mydef = 42 : nat *)
Lean
#print Nat -- 显示 Nat 的归纳定义
#print Nat.add -- 显示加法的定义
def mydef := 42
#print mydef -- def mydef : Nat := 42
用途:理解库函数的实现、调试自己的定义、学习标准库的写法。
命令 3:计算表达式 (Compute / #eval)
Coq
Compute 3 + 4. (* = 7 : nat *)
Compute negb true. (* = false : bool *)
(* Compute 会完全展开计算 *)
Definition big := 1000.
Compute big + big. (* = 2000 : nat *)
Lean
#eval 3 + 4 -- 7
#eval not true -- false
-- #eval 执行编译后的代码,速度很快
def big := 1000
#eval big + big -- 2000
注意:对于复杂或无限的对象,Compute 可能很慢或无法显示有意义的结果。
命令 4:搜索定理 (Search / #find)
Coq
(* 按名称搜索 *)
Search "add" "comm". (* 查找名称包含 add 和 comm 的定理 *)
(* 按模式搜索 *)
Search (_ + _ = _ + _). (* 查找加法交换律相关定理 *)
Search (0 + _). (* 查找关于 0 + _ 的定理 *)
(* 在特定模块中搜索 *)
Search nat inside Nat.
Lean
-- 按模式搜索
#find _ + _ = _ + _ -- 查找加法交换律
#find Nat → Nat → Nat -- 查找类型为 Nat → Nat → Nat 的函数
-- 使用名称搜索(需要导入)
import Std.Data.List.Basic
#check List.length_append -- 找到后可以查看
技巧:不确定定理名称时,先用模式搜索,找到后用 Print/#print 查看详细定义。
习题 1.3.1
查看以下表达式的类型:
Coq 版本
Check (true && false).
Check (3 =? 4).
Check (if true then 3 else 5).
Lean 版本
#check (true && false)
#check (3 == 4)
#check (if true then 3 else 5)
习题 1.3.2
计算以下表达式:
Coq 版本
Compute 2 + 3 * 4.
Compute negb (negb true).
Compute if (3 =? 3) then 100 else 200.
Lean 版本
#eval 2 + 3 * 4
#eval not (not true)
#eval if (3 == 3) then 100 else 200
导入模块
Coq - Require Import
(* 导入列表模块 *)
Require Import List.
Import ListNotations. (* 导入 [1; 2; 3] 语法 *)
(* 导入多个模块 *)
Require Import Nat Bool List.
(* 导入但不打开命名空间 *)
Require Import Arith.
Check Nat.add. (* 需要使用全名 *)
Lean - import
-- 导入基本模块
import Std.Data.List.Basic
-- 导入 Mathlib(如果项目包含)
import Mathlib.Data.Nat.Basic
import Mathlib.Tactic
-- 打开命名空间
open List -- 现在可以直接使用 map 而非 List.map
定义:常量和函数
基本定义
Coq
(* 推荐:始终写明类型签名 *)
Definition my_number : nat := 42.
(* 函数定义 *)
Definition double (n : nat) : nat := n + n.
(* 类型推断(不推荐) *)
Definition bad_double n := n + n. (* 类型不明确 *)
Lean
-- 推荐:始终写明类型签名
def myNumber : Nat := 42
-- 函数定义
def double (n : Nat) : Nat := n + n
-- 类型推断(不推荐)
def badDouble n := n + n -- 类型不明确
局部定义 (let ... in)
Coq
Definition complex_calc (x y : nat) : nat :=
let sum := x + y in
let prod := x * y in
sum + prod.
(* 在证明中使用 let *)
Theorem example : 10 + 20 = 30.
Proof.
let x := 10 in
let y := 20 in
reflexivity.
Qed.
Lean
def complexCalc (x y : Nat) : Nat :=
let sum := x + y
let prod := x * y
sum + prod
-- 更简洁的写法
def complexCalc' (x y : Nat) : Nat :=
let sum := x + y; let prod := x * y; sum + prod
递归函数
Coq - 使用 Fixpoint
Fixpoint factorial (n : nat) : nat :=
match n with
| 0 => 1
| S n' => n * factorial n'
end.
Fixpoint sum_list (l : list nat) : nat :=
match l with
| [] => 0
| x :: xs => x + sum_list xs
end.
Lean - 自动终止性检查
def factorial : Nat → Nat
| 0 => 1
| n + 1 => (n + 1) * factorial n
def sumList : List Nat → Nat
| [] => 0
| x :: xs => x + sumList xs
关键点:所有递归函数必须是可终止的。Coq 使用 Fixpoint 明确标记递归,Lean 自动检测并验证终止性。
习题 1.3.3
练习使用 Print/#print 和 Search/#find 命令:
- 查看自然数类型的定义
- 搜索关于加法交换律的定理
- 查看列表的 append (++) 操作的定义
习题 1.3.4
定义一个递归函数 sum_to_n,计算从 0 到 n 的所有自然数之和。
习题 1.3.5
使用 let 定义一个函数,计算一个数的平方加上这个数本身。
1.4 基本类型
Coq 和 Lean 都提供了丰富的类型系统。理解这些类型的本质——它们都是通过归纳定义构造的——是掌握定理证明器的关键。
自然数 (nat / Nat)
归纳定义的本质
Coq
(* 查看 nat 的定义 *)
Print nat.
(* Inductive nat : Set :=
| O : nat (* 零 *)
| S : nat -> nat (* 后继函数 *)
*)
(* 自然数的构造 *)
Check O. (* O : nat *)
Check S O. (* S O : nat,表示 1 *)
Check S (S O). (* S (S O) : nat,表示 2 *)
Lean
-- 查看 Nat 的定义
#print Nat
-- inductive Nat : Type
-- | zero : Nat
-- | succ : Nat → Nat
-- 自然数的构造
#check Nat.zero -- Nat.zero : Nat
#check Nat.succ 0 -- Nat.succ 0 : Nat (表示 1)
#check Nat.succ 1 -- Nat.succ 1 : Nat (表示 2)
使用和运算
Coq
(* 字面量 *)
Check 42. (* 42 : nat *)
(* 运算 *)
Compute 3 + 4. (* = 7 : nat *)
Compute 5 - 2. (* = 3 : nat *)
Compute 3 * 4. (* = 12 : nat *)
Compute 5 - 8. (* = 0 : nat (饱和减法) *)
Compute 10 / 3. (* = 3 : nat (整数除法) *)
Compute 10 mod 3. (* = 1 : nat (取模) *)
Lean
-- 字面量
#check 42 -- 42 : Nat
-- 运算
#eval 3 + 4 -- 7
#eval 5 - 2 -- 3
#eval 3 * 4 -- 12
#eval 5 - 8 -- 0 (饱和减法)
#eval 10 / 3 -- 3 (整数除法)
#eval 10 % 3 -- 1 (取模)
布尔值 (bool / Bool)
定义和使用
Coq
(* 查看定义 *)
Print bool.
(* Inductive bool : Set :=
| true : bool
| false : bool *)
(* 布尔运算 *)
Compute negb true. (* = false : bool *)
Compute true && false. (* = false : bool *)
Compute true || false. (* = true : bool *)
Compute xorb true false. (* = true : bool *)
Lean
-- 查看定义
#print Bool
-- inductive Bool : Type
-- | false : Bool
-- | true : Bool
-- 布尔运算
#eval not true -- false
#eval true && false -- false
#eval true || false -- true
#eval xor true false -- true
列表 (list / List)
定义和构造
Coq
Require Import List.
Import ListNotations.
(* 查看定义 *)
Print list.
(* Inductive list (A : Type) : Type :=
| nil : list A
| cons : A -> list A -> list A *)
(* 列表构造 *)
Check @nil nat. (* nil : list nat *)
Check cons 1 (cons 2 nil). (* cons 1 (cons 2 nil) : list nat *)
Check [1; 2; 3]. (* 语法糖 *)
Lean
-- 查看定义
#print List
-- inductive List (α : Type u) : Type u
-- | nil : List α
-- | cons : α → List α → List α
-- 列表构造
#check @List.nil Nat -- List.nil : List Nat
#check List.cons 1 List.nil -- List.cons 1 List.nil : List Nat
#check [1, 2, 3] -- 语法糖(注意:逗号而非分号)
列表操作
Coq
(* 列表操作 *)
Compute length [1; 2; 3; 4]. (* = 4 : nat *)
Compute [1; 2] ++ [3; 4]. (* = [1; 2; 3; 4] : list nat *)
Compute rev [1; 2; 3]. (* = [3; 2; 1] : list nat *)
Compute hd 0 [1; 2; 3]. (* = 1 : nat *)
Compute tl [1; 2; 3]. (* = [2; 3] : list nat *)
Lean
-- 列表操作
#eval List.length [1, 2, 3, 4] -- 4
#eval [1, 2] ++ [3, 4] -- [1, 2, 3, 4]
#eval List.reverse [1, 2, 3] -- [3, 2, 1]
#eval List.head! [1, 2, 3] -- 1
#eval List.tail! [1, 2, 3] -- [2, 3]
更多常用类型
字符串 (String)
Coq
Require Import String.
Check "Hello". (* "Hello" : string *)
Compute "Hello" ++ " " ++ "World". (* = "Hello World" : string *)
Compute String.length "Coq". (* = 3 : nat *)
Lean
#check "Hello" -- "Hello" : String
#eval "Hello" ++ " " ++ "World" -- "Hello World"
#eval String.length "Lean" -- 4
积类型 (Product Types)
Coq
(* 有序对 *)
Check (3, true). (* (3, true) : nat * bool *)
Check (1, "hello", false). (* 三元组 *)
(* 解构 *)
Definition fst_of_pair (p : nat * bool) : nat :=
match p with
| (x, y) => x
end.
Lean
-- 有序对
#check (3, true) -- (3, true) : Nat × Bool
#check (1, "hello", false) -- 三元组
-- 解构
def fstOfPair (p : Nat × Bool) : Nat :=
match p with
| (x, y) => x
和类型 (Sum Types)
Coq
(* Either nat 或 bool *)
Check inl 42 : nat + bool. (* 左注入 *)
Check inr true : nat + bool. (* 右注入 *)
(* 模式匹配 *)
Definition sum_to_nat (x : nat + bool) : nat :=
match x with
| inl n => n
| inr b => if b then 1 else 0
end.
Lean
-- Sum 类型
#check Sum.inl 42 : Sum Nat Bool -- 左注入
#check Sum.inr true : Sum Nat Bool -- 右注入
-- 模式匹配
def sumToNat (x : Sum Nat Bool) : Nat :=
match x with
| Sum.inl n => n
| Sum.inr b => if b then 1 else 0
习题 1.4.1
预测以下表达式的结果,然后验证:
Coq
Compute S (S (S O)) + S (S O).
Compute 10 - 15.
Compute negb (true && false).
Compute [1; 2] ++ [3; 4].
Lean
#eval Nat.succ 2 + 2
#eval 10 - 15
#eval not (true && false)
#eval [1, 2] ++ [3, 4]
习题 1.4.2
定义一个函数 is_zero,检查一个自然数是否为零。
习题 1.4.3
编写一个函数,交换有序对的两个元素。
习题 1.4.4
使用 Print/#print 查看列表的 map 函数定义,然后使用它将列表中每个元素加倍。
1.5 简单证明
证明是 Coq 和 Lean 的核心功能。本节介绍最基本的证明技术,展示交互式证明的工作流程。
证明结构对比
Coq 的证明结构
Theorem theorem_name : proposition.
Proof.
(* 策略1 *)
(* 策略2 *)
(* ... *)
Qed.
(* 或者使用 Definition 直接给出证明项 *)
Definition theorem_name' : proposition := proof_term.
Lean 的证明结构
-- 策略模式
theorem theorem_name : proposition := by
-- 策略1
-- 策略2
-- ...
-- 项模式(直接给出证明)
theorem theorem_name' : proposition := proof_term
基本策略对比
策略 1:反射性 (reflexivity / rfl)
用于证明形如 x = x 的等式。
Coq
Theorem zero_equals_zero : 0 = 0.
Proof.
reflexivity.
Qed.
Theorem computation : 2 + 2 = 4.
Proof.
reflexivity. (* 自动计算后应用反射性 *)
Qed.
Lean
theorem zero_equals_zero : 0 = 0 := by
rfl
theorem computation : 2 + 2 = 4 := by
rfl -- 自动计算后应用反射性
策略 2:引入假设 (intros / intro)
用于处理全称量词(∀)和蕴含(→)。
Coq
Theorem identity_function : forall x : nat, x = x.
Proof.
intros x. (* 引入任意 x *)
reflexivity.
Qed.
Theorem implication_intro : forall P Q : Prop, P -> Q -> P.
Proof.
intros P Q. (* 引入命题 P 和 Q *)
intros HP HQ. (* 引入假设 HP : P 和 HQ : Q *)
exact HP. (* 使用假设 HP *)
Qed.
Lean
theorem identity_function : ∀ x : Nat, x = x := by
intro x -- 引入任意 x
rfl
theorem implication_intro : ∀ P Q : Prop, P → Q → P := by
intro P Q -- 引入命题 P 和 Q
intro HP HQ -- 引入假设 HP : P 和 HQ : Q
exact HP -- 使用假设 HP
策略 3:化简 (simpl / simp)
简化表达式,展开定义。
Coq
Definition double (n : nat) : nat := n + n.
Theorem double_zero : double 0 = 0.
Proof.
simpl. (* 展开 double 的定义:0 + 0 *)
reflexivity.
Qed.
Lean
def double (n : Nat) : Nat := n + n
theorem double_zero : double 0 = 0 := by
simp [double] -- 展开 double 的定义
-- 目标自动被简化为 0 = 0,simp 完成证明
注意:Lean 的 simp 比 Coq 的 simpl 更强大,经常可以自动完成证明。
策略 4:重写 (rewrite / rw)
使用等式重写目标或假设。
Coq
Theorem rewrite_example : forall a b c : nat,
a = b -> b = c -> a = c.
Proof.
intros a b c H1 H2.
rewrite H1. (* 将目标中的 a 替换为 b *)
rewrite H2. (* 将目标中的 b 替换为 c *)
reflexivity.
Qed.
Lean
theorem rewrite_example : ∀ a b c : Nat,
a = b → b = c → a = c := by
intro a b c H1 H2
rw [H1] -- 将目标中的 a 替换为 b
rw [H2] -- 将目标中的 b 替换为 c
-- 或者一次性:rw [H1, H2]
完整的证明示例
证明:0 + n = n
这是一个简单但经典的例子,展示了完整的证明流程。
Coq
Theorem zero_plus_n : forall n : nat, 0 + n = n.
Proof.
intros n. (* 目标:0 + n = n *)
simpl. (* 根据加法定义,0 + n 简化为 n *)
reflexivity. (* n = n *)
Qed.
Lean
theorem zero_plus_n : ∀ n : Nat, 0 + n = n := by
intro n -- 目标:0 + n = n
simp -- 自动简化并完成证明
-- 或者更明确地:
theorem zero_plus_n' : ∀ n : Nat, 0 + n = n := by
intro n
rfl -- 0 + n 定义上就等于 n
习题 1.5.1
证明:2 + 3 = 5
习题 1.5.2
证明布尔值的双重否定定律:∀ b : Bool, not (not b) = b
习题 1.5.3
证明简单的逻辑蕴含:如果 P 成立,那么 P 或 Q 成立。
习题 1.5.4
使用 rewrite 证明等式的传递性(已在示例中展示)。
习题 1.5.5
证明:∀ n : Nat, n = n + 0(注意:这与 n + 0 = n 不同!)
第二章:核心概念
2.1 归纳类型
归纳类型是 Coq 和 Lean 类型系统的核心。通过归纳定义,我们可以构造新的数据类型。
归纳定义基础对比
Coq 版本
(* 定义一个简单的归纳类型:日期 *)
Inductive day : Type :=
| monday
| tuesday
| wednesday
| thursday
| friday
| saturday
| sunday.
Lean 版本
-- 定义一个简单的归纳类型:日期
inductive Day where
| monday
| tuesday
| wednesday
| thursday
| friday
| saturday
| sunday
使用归纳类型
Coq 版本
(* 定义函数处理归纳类型 *)
Definition next_weekday (d : day) : day :=
match d with
| monday => tuesday
| tuesday => wednesday
| wednesday => thursday
| thursday => friday
| friday => monday
| saturday => monday
| sunday => monday
end.
(* 测试 *)
Compute next_weekday friday. (* = monday : day *)
Compute next_weekday saturday. (* = monday : day *)
Lean 版本
-- 定义函数处理归纳类型
def nextWeekday (d : Day) : Day :=
match d with
| .monday => .tuesday
| .tuesday => .wednesday
| .wednesday => .thursday
| .thursday => .friday
| .friday => .monday
| .saturday => .monday
| .sunday => .monday
-- 测试
#eval nextWeekday .friday -- .monday
#eval nextWeekday .saturday -- .monday
带参数的归纳类型
(* RGB 颜色类型 *)
Inductive rgb : Type :=
| red
| green
| blue.
(* 带有一个颜色的归纳类型 *)
Inductive color : Type :=
| black
| white
| primary (p : rgb).
习题 2.1.1
定义一个归纳类型 bool3,它有三个值:yes、no 和 maybe。
递归归纳类型
(* 自然数的归纳定义 *)
Inductive nat : Type :=
| O : nat
| S : nat -> nat.
(* 二叉树的归纳定义 *)
Inductive bin_tree : Type :=
| leaf : bin_tree
| node : nat -> bin_tree -> bin_tree -> bin_tree.
列表的归纳定义
Inductive natlist : Type :=
| nil
| cons (n : nat) (l : natlist).
(* 使用记号让列表更易读 *)
Notation "x :: l" := (cons x l)
(at level 60, right associativity).
Notation "[ ]" := nil.
Notation "[ x ; .. ; y ]" := (cons x .. (cons y nil) ..).
(* 现在可以这样写列表 *)
Definition mylist := [1; 2; 3].
(* 列表函数 *)
Fixpoint length (l : natlist) : nat :=
match l with
| nil => 0
| h :: t => S (length t)
end.
习题 2.1.2
定义一个函数 repeat,接受一个自然数 n 和一个次数 count,返回包含 count 个 n 的列表。
习题 2.1.3
定义一个归纳类型 natprod 表示自然数对,并定义函数 fst 和 snd 来提取第一个和第二个元素。
2.2 函数定义
Coq 和 Lean 都支持多种函数定义方式,包括简单函数、递归函数和高阶函数。
递归函数对比
Coq - Fixpoint
(* 阶乘函数 *)
Fixpoint factorial (n : nat) : nat :=
match n with
| O => 1
| S n' => n * factorial n'
end.
(* 斐波那契数列 *)
Fixpoint fib (n : nat) : nat :=
match n with
| O => O
| S O => 1
| S (S n'') => fib (S n'') + fib n''
end.
Lean - def/rec
-- 阶乘函数
def factorial : Nat → Nat
| 0 => 1
| n + 1 => (n + 1) * factorial n
-- 斐波那契数列
def fib : Nat → Nat
| 0 => 0
| 1 => 1
| n + 2 => fib (n + 1) + fib n
列表上的递归函数
Fixpoint append (l1 l2 : natlist) : natlist :=
match l1 with
| nil => l2
| h :: t => h :: (append t l2)
end.
Fixpoint rev (l : natlist) : natlist :=
match l with
| nil => nil
| h :: t => append (rev t) [h]
end.
(* 使用记号 *)
Notation "x ++ y" := (append x y)
(right associativity, at level 60).
模式匹配的高级用法
(* 同时匹配多个值 *)
Definition both_zero (n m : nat) : bool :=
match n, m with
| O, O => true
| _, _ => false
end.
(* 嵌套模式匹配 *)
Definition silly_fun (n : nat) : nat :=
match n with
| O => O
| S (S (S O)) => S (S O) (* 特殊处理 3 *)
| S n' => n'
end.
习题 2.2.1
定义一个函数 sum_list,计算列表中所有元素的和。
习题 2.2.2
定义一个函数 nth_default,返回列表的第 n 个元素,如果 n 超出范围则返回默认值。
高阶函数基础
(* 函数作为参数 *)
Definition do_twice (f : nat -> nat) (n : nat) : nat :=
f (f n).
(* 测试 *)
Definition plus3 (n : nat) : nat := n + 3.
Compute do_twice plus3 0. (* = 6 : nat *)
(* 返回函数的函数 *)
Definition plus_n : nat -> (nat -> nat) :=
fun n => fun m => n + m.
(* 柯里化 *)
Definition plus_3 := plus_n 3.
Compute plus_3 4. (* = 7 : nat *)
习题 2.2.3
定义一个高阶函数 filter,它接受一个谓词函数和一个列表,返回满足谓词的元素列表。
2.3 证明策略(基础)
本节介绍 Coq 和 Lean 中最常用的证明策略(tactics)。
引入变量对比
Coq - intros/intro
Theorem example_intros : forall n m : nat, n + m = m + n.
Proof.
intros n m. (* 引入两个任意自然数 n 和 m *)
(* 现在目标变成了证明 n + m = m + n *)
Admitted.
Theorem example_intro : forall n : nat, n = n.
Proof.
intro n. (* intro 只引入一个变量 *)
reflexivity.
Qed.
Lean - intro
theorem example_intros : ∀ n m : Nat, n + m = m + n := by
intro n m -- 引入两个任意自然数 n 和 m
-- 现在目标变成了证明 n + m = m + n
sorry
theorem example_intro : ∀ n : Nat, n = n := by
intro n -- intro 引入一个变量
rfl -- reflexivity
apply 和 exact
(* 假设我们已经证明了一个引理 *)
Theorem plus_0_r : forall n : nat, n + 0 = n.
Proof. (* 证明省略 *) Admitted.
(* 使用 apply *)
Theorem apply_example : forall n : nat, (n + 0) + 0 = n.
Proof.
intros n.
apply plus_0_r. (* 使用 plus_0_r 简化目标 *)
apply plus_0_r. (* 再次使用 *)
Qed.
(* 使用 exact *)
Theorem exact_example : 42 = 42.
Proof.
exact (eq_refl 42). (* 直接给出证明项 *)
Qed.
destruct 分情况讨论
对布尔值分情况
Theorem bool_fn_applied_twice :
forall (f : bool -> bool) (b : bool),
f (f (f (f b))) = f (f b).
Proof.
intros f b.
destruct b.
- (* b = true *)
destruct (f true) eqn:Hft.
+ (* f true = true *)
rewrite Hft. rewrite Hft. reflexivity.
+ (* f true = false *)
destruct (f false) eqn:Hff.
* rewrite Hff. reflexivity.
* rewrite Hft. rewrite Hff. reflexivity.
- (* b = false *)
destruct (f false) eqn:Hff.
+ (* f false = true *)
destruct (f true) eqn:Hft.
* rewrite Hft. reflexivity.
* rewrite Hff. rewrite Hft. reflexivity.
+ (* f false = false *)
rewrite Hff. rewrite Hff. reflexivity.
Qed.
rewrite 重写
Theorem rewrite_example : forall n m : nat,
n = m -> n + n = m + m.
Proof.
intros n m H.
rewrite H. (* 使用假设 H: n = m 重写目标 *)
reflexivity.
Qed.
(* 反向重写 *)
Theorem rewrite_backwards : forall n m : nat,
n = m -> m + m = n + n.
Proof.
intros n m H.
rewrite <- H. (* 反向使用 H *)
reflexivity.
Qed.
习题 2.3.1
证明:forall b c : bool, b = c -> negb b = negb c
习题 2.3.2
证明:forall b : bool, b && true = b
习题 2.3.3
使用 destruct 证明:forall n : nat, n =? n = true
高级策略:inversion
inversion 是一个强大的策略,用于分析归纳类型的构造子。它可以:
- 分析等式中的构造子
- 推导出隐含的等式
- 排除不可能的情况
(* inversion 基本用法 *)
Theorem zero_not_succ : forall n : nat,
0 <> S n.
Proof.
intros n H.
inversion H. (* 矛盾:0 和 S n 有不同的构造子 *)
Qed.
(* 从构造子等式推导参数等式 *)
Theorem succ_injective : forall n m : nat,
S n = S m -> n = m.
Proof.
intros n m H.
inversion H. (* 从 S n = S m 推导出 n = m *)
reflexivity.
Qed.
(* 分析列表 *)
Theorem cons_injective : forall (A : Type) (x y : A) (l1 l2 : list A),
x :: l1 = y :: l2 -> x = y /\ l1 = l2.
Proof.
intros A x y l1 l2 H.
inversion H.
split; reflexivity.
Qed.
高级策略:injection 和 discriminate
injection 从构造子的等式中提取参数的等式,discriminate 证明不同构造子的不等式。
(* injection: 从构造子等式提取参数等式 *)
Theorem injection_ex : forall n m : nat,
S n = S m -> n = m.
Proof.
intros n m H.
injection H. (* 得到 n = m *)
intros H'.
assumption.
Qed.
(* discriminate: 证明不同构造子不相等 *)
Theorem discriminate_ex : forall n : nat,
0 = S n -> False.
Proof.
intros n H.
discriminate H. (* 0 和 S n 有不同构造子 *)
Qed.
(* 组合使用 *)
Theorem nat_cases : forall n : nat,
n = 0 \/ exists m, n = S m.
Proof.
intros n.
destruct n.
- left. reflexivity.
- right. exists n. reflexivity.
Qed.
归纳策略的高级用法
(* 对任意变量进行归纳 *)
Theorem plus_comm : forall n m : nat,
n + m = m + n.
Proof.
intros n m.
induction n as [| n' IHn'].
- (* n = 0 *)
simpl.
(* 需要 m + 0 = m 的引理 *)
admit.
- (* n = S n' *)
simpl.
rewrite IHn'.
(* 需要 S (m + n') = m + S n' 的引理 *)
admit.
Admitted.
(* 同时对多个变量归纳 *)
Theorem eq_dec : forall n m : nat,
{n = m} + {n <> m}.
Proof.
intros n.
induction n as [| n' IHn'];
intros m;
destruct m as [| m'].
- (* n = 0, m = 0 *)
left. reflexivity.
- (* n = 0, m = S m' *)
right. discriminate.
- (* n = S n', m = 0 *)
right. discriminate.
- (* n = S n', m = S m' *)
destruct (IHn' m') as [H | H].
+ left. rewrite H. reflexivity.
+ right. intros Heq.
injection Heq. intros.
contradiction.
Qed.
(* 归纳假设的命名 *)
Theorem named_induction : forall n : nat,
n * 0 = 0.
Proof.
intros n.
induction n as [| n' IH_n_times_0].
- reflexivity.
- simpl.
exact IH_n_times_0.
Qed.
Lean 中的对应策略
-- Lean 中的 cases (类似 destruct)
example (b : Bool) : b = true ∨ b = false := by
cases b
· left; rfl
· right; rfl
-- Lean 中的 induction
example (n : Nat) : n + 0 = n := by
induction n with
| zero => rfl
| succ n' ih =>
simp [Nat.add_succ]
exact ih
-- Lean 中的 injection
example (n m : Nat) (h : n.succ = m.succ) : n = m := by
injection h
-- Lean 中的矛盾
example (h : 0 = 1) : False := by
contradiction
习题 2.3.4
使用 inversion 证明:forall n m : nat, S n = S m -> n = m
习题 2.3.5
使用 discriminate 证明:forall n : nat, true <> false
2.4 命题逻辑
Coq 中的命题逻辑包括合取、析取、蕴含、否定和量词。
合取(与)
(* 合取的定义 *)
Inductive and (P Q : Prop) : Prop :=
| conj : P -> Q -> and P Q.
(* 使用符号 /\ *)
Notation "P /\ Q" := (and P Q) : type_scope.
(* 证明合取 *)
Theorem and_intro : forall P Q : Prop,
P -> Q -> P /\ Q.
Proof.
intros P Q HP HQ.
split. (* 将目标 P /\ Q 分解为两个子目标 *)
- assumption. (* 证明 P *)
- assumption. (* 证明 Q *)
Qed.
(* 使用合取 *)
Theorem and_proj1 : forall P Q : Prop,
P /\ Q -> P.
Proof.
intros P Q H.
destruct H as [HP HQ]. (* 分解假设 *)
assumption.
Qed.
析取(或)
(* 析取的定义 *)
Inductive or (P Q : Prop) : Prop :=
| or_introl : P -> or P Q
| or_intror : Q -> or P Q.
(* 使用符号 \/ *)
Notation "P \/ Q" := (or P Q) : type_scope.
(* 证明析取 *)
Theorem or_intro_l : forall P Q : Prop,
P -> P \/ Q.
Proof.
intros P Q HP.
left. (* 选择左边 *)
assumption.
Qed.
(* 对析取分情况 *)
Theorem or_commut : forall P Q : Prop,
P \/ Q -> Q \/ P.
Proof.
intros P Q H.
destruct H as [HP | HQ].
- (* P 成立 *)
right. assumption.
- (* Q 成立 *)
left. assumption.
Qed.
蕴含和否定
(* 蕴含就是函数类型 *)
Theorem modus_ponens : forall P Q : Prop,
P -> (P -> Q) -> Q.
Proof.
intros P Q HP HPQ.
apply HPQ.
assumption.
Qed.
(* 否定的定义 *)
Definition not (P : Prop) : Prop := P -> False.
Notation "~ P" := (not P) : type_scope.
(* 矛盾律 *)
Theorem contradiction : forall P : Prop,
P -> ~P -> False.
Proof.
intros P HP HnP.
apply HnP.
assumption.
Qed.
量词
全称量词
Theorem forall_example :
forall n : nat, n + 0 = n -> S n + 0 = S n.
Proof.
intros n H.
simpl.
rewrite H.
reflexivity.
Qed.
存在量词
Theorem exists_example :
exists n : nat, n + 3 = 5.
Proof.
exists 2. (* 给出见证 *)
reflexivity.
Qed.
(* 使用存在量词 *)
Theorem exists_proj : forall P : nat -> Prop,
(exists n, P n) -> exists m, P m.
Proof.
intros P H.
destruct H as [n Hn]. (* 获取见证和证明 *)
exists n.
assumption.
Qed.
习题 2.4.1
证明德摩根定律:forall P Q : Prop, ~(P \/ Q) -> ~P /\ ~Q
习题 2.4.2
证明:forall n m : nat, n = m \/ n <> m(需要引入排中律)
2.5 归纳证明
归纳法是 Coq 中证明关于递归数据类型性质的核心技术。
对自然数的归纳
Theorem plus_n_O : forall n : nat,
n + 0 = n.
Proof.
intros n.
induction n as [| n' IHn'].
- (* n = 0 *)
reflexivity.
- (* n = S n' *)
simpl.
rewrite IHn'. (* 使用归纳假设 *)
reflexivity.
Qed.
加法交换律的证明
(* 首先需要一些辅助引理 *)
Theorem plus_0_n : forall n : nat,
0 + n = n.
Proof.
intros n.
reflexivity. (* 根据加法的定义,0 + n 直接计算为 n *)
Qed.
Theorem plus_n_Sm : forall n m : nat,
S (n + m) = n + (S m).
Proof.
intros n m.
induction n as [| n' IHn'].
- reflexivity.
- simpl. rewrite IHn'. reflexivity.
Qed.
(* 现在可以证明交换律 *)
Theorem plus_comm : forall n m : nat,
n + m = m + n.
Proof.
intros n m.
induction n as [| n' IHn'].
- (* n = 0: 需要证明 0 + m = m + 0 *)
rewrite plus_0_n. (* 0 + m = m *)
rewrite <- plus_n_O. (* m + 0 = m *)
reflexivity.
- (* n = S n': 需要证明 S n' + m = m + S n' *)
simpl. (* S n' + m = S (n' + m) *)
rewrite IHn'. (* S (n' + m) = S (m + n') *)
rewrite plus_n_Sm. (* S (m + n') = m + S n' *)
reflexivity.
Qed.
对列表的归纳
Theorem app_assoc : forall l1 l2 l3 : natlist,
(l1 ++ l2) ++ l3 = l1 ++ (l2 ++ l3).
Proof.
intros l1 l2 l3.
induction l1 as [| h l1' IHl1'].
- (* l1 = nil *)
reflexivity.
- (* l1 = h :: l1' *)
simpl.
rewrite IHl1'.
reflexivity.
Qed.
Theorem rev_app_distr : forall l1 l2 : natlist,
rev (l1 ++ l2) = rev l2 ++ rev l1.
Proof.
intros l1 l2.
induction l1 as [| h l1' IHl1'].
- (* l1 = nil *)
simpl.
rewrite app_nil_r. (* 假设已证明 *)
reflexivity.
- (* l1 = h :: l1' *)
simpl.
rewrite IHl1'.
rewrite app_assoc.
reflexivity.
Qed.
强归纳法
(* 强归纳原理的例子 *)
Theorem strong_induction :
forall P : nat -> Prop,
(forall n : nat, (forall m : nat, m < n -> P m) -> P n) ->
forall n : nat, P n.
Proof.
(* 证明略,这是一个高级话题 *)
Admitted.
习题 2.5.1
证明:forall n : nat, n + n = 2 * n
习题 2.5.2
证明列表反转的恒等性:forall l : natlist, rev (rev l) = l
2.6 证明自动化
Coq 提供了多种自动化策略,可以大大简化证明过程。这些策略能够自动尝试应用各种证明技术。
auto 策略
auto 是最基本的自动化策略,它尝试使用 assumption、reflexivity、apply 等基本策略的组合。
(* auto 的基本用法 *)
Theorem auto_example : forall P Q R : Prop,
(P -> Q) -> (Q -> R) -> P -> R.
Proof.
auto. (* 自动完成证明 *)
Qed.
(* auto 使用深度参数 *)
Theorem auto_depth : forall P Q R S : Prop,
(P -> Q) -> (Q -> R) -> (R -> S) -> P -> S.
Proof.
auto 4. (* 搜索深度为 4 *)
Qed.
(* auto 使用提示数据库 *)
Hint Resolve plus_comm : core.
Hint Resolve plus_assoc : core.
Theorem auto_with_hints : forall n m p : nat,
n + (m + p) = (m + n) + p.
Proof.
auto. (* 使用提示数据库中的定理 *)
Qed.
eauto 策略
eauto 是 auto 的增强版本,它还会尝试使用存在量词实例化。
(* eauto 处理存在量词 *)
Theorem eauto_example : forall P : nat -> Prop,
P 42 -> exists n, P n.
Proof.
eauto. (* 自动找到见证 42 *)
Qed.
(* eauto 的高级用法 *)
Theorem eauto_complex : forall P Q : nat -> Prop,
(forall n, P n -> Q (S n)) ->
P 5 ->
exists m, Q m.
Proof.
eauto. (* 自动构造 Q 6 *)
Qed.
tauto 策略
tauto 是专门用于命题逻辑的完备决策过程。
(* tauto 解决命题逻辑 *)
Theorem tauto_example : forall P Q : Prop,
P /\ (P -> Q) -> Q.
Proof.
tauto.
Qed.
(* 复杂的命题逻辑 *)
Theorem tauto_complex : forall P Q R : Prop,
(P \/ Q) /\ (~P \/ R) /\ (~Q \/ R) -> R.
Proof.
tauto.
Qed.
(* tauto 不能处理量词 *)
Theorem tauto_limitation : forall n : nat,
n = n.
Proof.
(* tauto. *) (* 会失败 *)
auto. (* 但 auto 可以 *)
Qed.
omega/lia 策略
omega(现在推荐使用 lia)用于解决线性整数算术。
Require Import Lia.
(* lia 解决算术不等式 *)
Theorem lia_example : forall n m : nat,
n < m -> n + 10 < m + 11.
Proof.
intros.
lia.
Qed.
(* 复杂的算术约束 *)
Theorem lia_complex : forall x y z : nat,
x + y = 5 ->
2 * x + y = 7 ->
x = 2 /\ y = 3.
Proof.
intros.
lia.
Qed.
(* lia 处理多个约束 *)
Theorem lia_system : forall a b c : nat,
a + b + c = 10 ->
a = 2 * b ->
b = c + 1 ->
a = 4 /\ b = 2 /\ c = 1.
Proof.
intros.
lia.
Qed.
firstorder 策略
firstorder 是一个一阶逻辑的自动证明器。
(* firstorder 处理量词 *)
Theorem firstorder_example : forall P Q : nat -> Prop,
(forall x, P x -> Q x) ->
(exists x, P x) ->
exists x, Q x.
Proof.
firstorder.
Qed.
(* 复杂的一阶逻辑 *)
Theorem firstorder_complex :
forall P Q R : nat -> nat -> Prop,
(forall x y, P x y -> Q y x) ->
(forall x y, Q x y -> R x y) ->
(forall x y, P x y -> R y x).
Proof.
firstorder.
Qed.
组合使用自动化策略
(* 尝试多种策略 *)
Ltac solve_goal :=
auto || tauto || lia || firstorder.
Theorem combined_1 : forall n : nat,
n + 0 = n.
Proof.
solve_goal.
Qed.
Theorem combined_2 : forall P Q : Prop,
P /\ Q -> Q /\ P.
Proof.
solve_goal.
Qed.
(* 更复杂的组合 *)
Ltac crush :=
intros;
repeat match goal with
| [ H : _ /\ _ |- _ ] => destruct H
| [ |- _ /\ _ ] => split
| [ H : _ \/ _ |- _ ] => destruct H
| [ H : exists _, _ |- _ ] => destruct H
end;
auto.
Theorem crush_example : forall P Q R : Prop,
(P /\ Q) \/ (P /\ R) -> P /\ (Q \/ R).
Proof.
crush.
Qed.
ring 和 field 策略
用于解决环和域上的等式。
Require Import Ring.
Require Import Field.
(* ring 处理多项式等式 *)
Theorem ring_example : forall x y : nat,
(x + y) * (x + y) = x * x + 2 * x * y + y * y.
Proof.
intros.
ring.
Qed.
(* 更复杂的多项式 *)
Theorem ring_complex : forall a b c : nat,
(a + b + c) * (a + b + c) =
a * a + b * b + c * c + 2 * a * b + 2 * a * c + 2 * b * c.
Proof.
intros.
ring.
Qed.
Lean 中的自动化策略
-- Lean 的 simp 策略
example (x y : Nat) : x + y = y + x := by
simp [Nat.add_comm]
-- Lean 的 omega 策略
example (x y : Nat) (h : x < y) : x + 1 ≤ y := by
omega
-- Lean 的 aesop 策略(类似 auto)
example (p q r : Prop) (h1 : p → q) (h2 : q → r) (h3 : p) : r := by
aesop
-- Lean 的 ring 策略
example (x y : ℤ) : (x + y)^2 = x^2 + 2*x*y + y^2 := by
ring
习题 2.6.1
使用自动化策略证明:forall x y z : nat, x + (y + z) = y + (x + z)
习题 2.6.2
使用 firstorder 证明:forall P Q : nat -> Prop, (exists x, P x /\ Q x) -> (exists x, P x) /\ (exists x, Q x)
习题 2.5.3
定义一个函数 count 统计列表中某个元素的出现次数,并证明:count n (l1 ++ l2) = count n l1 + count n l2
第三章:进阶主题
3.1 依赖类型
依赖类型是 Coq 和 Lean 类型系统的核心特性,允许类型依赖于值。这使得我们可以编写更精确的规范。
依赖函数类型对比
Coq 版本
(* 依赖函数类型的例子 *)
Definition identity : forall (A : Type), A -> A :=
fun A x => x.
(* 类型依赖于参数 *)
Definition zero_or_succ : forall (b : bool), if b then nat else bool :=
fun b => if b then 0 else false.
Lean 版本
-- 依赖函数类型的例子
def identity : (A : Type) → A → A :=
fun A x => x
-- 类型依赖于参数
def zeroOrSucc : (b : Bool) → if b then Nat else Bool :=
fun b => if b then 0 else false
依赖对(Σ类型)
(* 使用 sig 定义依赖对 *)
Definition positive_nat : Type :=
{ n : nat | n > 0 }.
(* 构造依赖对 *)
Definition one_positive : positive_nat.
Proof.
exists 1.
auto.
Defined.
(* 另一种写法 *)
Definition two_positive : positive_nat :=
exist _ 2 (gt_Sn_O 1).
(* 提取值和证明 *)
Definition get_value (p : positive_nat) : nat :=
match p with
| exist _ n _ => n
end.
Definition get_proof (p : positive_nat) : get_value p > 0 :=
match p with
| exist _ _ proof => proof
end.
向量(长度索引列表)
向量的定义
Inductive vec (A : Type) : nat -> Type :=
| vnil : vec A 0
| vcons : forall n, A -> vec A n -> vec A (S n).
Arguments vnil {A}.
Arguments vcons {A n}.
(* 向量的例子 *)
Definition vec_123 : vec nat 3 :=
vcons 1 (vcons 2 (vcons 3 vnil)).
(* 安全的头函数 *)
Definition vhead {A : Type} {n : nat} (v : vec A (S n)) : A :=
match v with
| vcons _ h _ => h
end.
(* 安全的尾函数 *)
Definition vtail {A : Type} {n : nat} (v : vec A (S n)) : vec A n :=
match v with
| vcons _ _ t => t
end.
习题 3.1.1
定义一个函数 vmap,对向量中的每个元素应用一个函数。
习题 3.1.2
定义一个函数 vappend,连接两个向量。
依赖模式匹配
(* convoy 模式 *)
Definition vhead' {A : Type} {n : nat} (v : vec A n) :
match n with
| 0 => unit
| S _ => A
end :=
match v in vec _ m return
match m with
| 0 => unit
| S _ => A
end
with
| vnil => tt
| vcons _ h _ => h
end.
(* 使用依赖类型确保安全性 *)
Definition safe_div : forall (n m : nat), m <> 0 -> nat.
intros n m H.
exact (n / m).
Defined.
习题 3.1.3
定义一个类型 bounded_nat n,表示小于 n 的自然数,并定义相关操作。
3.2 高阶函数与证明
高阶函数是函数式编程的核心,在 Coq 和 Lean 中它们也是证明的重要工具。
Map、Filter 和 Fold 对比
Coq 版本
Require Import List.
Import ListNotations.
(* map 的定义和性质 *)
Fixpoint map {A B : Type} (f : A -> B) (l : list A) : list B :=
match l with
| [] => []
| h :: t => (f h) :: (map f t)
end.
Lean 版本
-- map 的定义和性质
def map {α β : Type} (f : α → β) : List α → List β
| [] => []
| h :: t => f h :: map f t
函数组合
(* 函数组合 *)
Definition compose {A B C : Type} (g : B -> C) (f : A -> B) : A -> C :=
fun x => g (f x).
Notation "g ∘ f" := (compose g f) (at level 40, left associativity).
(* 组合的性质 *)
Theorem compose_assoc : forall A B C D (h : C -> D) (g : B -> C) (f : A -> B),
(h ∘ g) ∘ f = h ∘ (g ∘ f).
Proof.
intros. reflexivity.
Qed.
(* map 与组合的关系 *)
Theorem map_compose : forall A B C (g : B -> C) (f : A -> B) (l : list A),
map g (map f l) = map (g ∘ f) l.
Proof.
intros A B C g f l.
induction l.
- reflexivity.
- simpl. rewrite IHl. reflexivity.
Qed.
高阶谓词
(* 所有元素满足谓词 *)
Fixpoint all {A : Type} (P : A -> bool) (l : list A) : bool :=
match l with
| [] => true
| h :: t => P h && all P t
end.
(* 存在元素满足谓词 *)
Fixpoint any {A : Type} (P : A -> bool) (l : list A) : bool :=
match l with
| [] => false
| h :: t => P h || any P t
end.
(* 高阶谓词的性质 *)
Theorem all_forall : forall A (P : A -> bool) (l : list A),
all P l = true <-> forall x, In x l -> P x = true.
Proof.
intros A P l.
split.
- (* -> *)
intros H x Hin.
induction l.
+ inversion Hin.
+ simpl in H. apply andb_prop in H.
destruct H as [Ha Ht].
destruct Hin.
* subst. assumption.
* apply IHl; assumption.
- (* <- *)
intros H.
induction l.
+ reflexivity.
+ simpl. apply andb_true_intro.
split.
* apply H. left. reflexivity.
* apply IHl. intros x Hin.
apply H. right. assumption.
Qed.
习题 3.2.1
证明:filter P (l1 ++ l2) = filter P l1 ++ filter P l2
习题 3.2.2
定义 partition 函数,将列表分成满足和不满足谓词的两个列表。
函数外延性
(* 函数外延性公理 *)
Axiom functional_extensionality : forall A B (f g : A -> B),
(forall x, f x = g x) -> f = g.
(* 使用函数外延性的例子 *)
Theorem compose_id_left : forall A B (f : A -> B),
(fun x => x) ∘ f = f.
Proof.
intros A B f.
apply functional_extensionality.
intros x. reflexivity.
Qed.
3.3 类型类(Type Classes)
类型类提供了一种组织和使用多态函数的方式,类似于 Haskell 的类型类。
定义类型类
(* 定义 Eq 类型类 *)
Class Eq (A : Type) := {
eqb : A -> A -> bool;
eqb_refl : forall x, eqb x x = true;
eqb_sym : forall x y, eqb x y = eqb y x;
eqb_trans : forall x y z,
eqb x y = true -> eqb y z = true -> eqb x z = true
}.
(* 定义 Show 类型类 *)
Class Show (A : Type) := {
show : A -> string
}.
(* 定义 Monoid 类型类 *)
Class Monoid (A : Type) := {
mempty : A;
mappend : A -> A -> A;
mappend_assoc : forall x y z,
mappend x (mappend y z) = mappend (mappend x y) z;
mappend_id_l : forall x, mappend mempty x = x;
mappend_id_r : forall x, mappend x mempty = x
}.
实例声明
(* nat 的 Eq 实例 *)
Instance nat_eq : Eq nat := {
eqb := Nat.eqb
}.
Proof.
- apply Nat.eqb_refl.
- apply Nat.eqb_sym.
- intros x y z H1 H2.
apply Nat.eqb_eq in H1.
apply Nat.eqb_eq in H2.
subst. apply Nat.eqb_refl.
Defined.
(* list 的 Monoid 实例 *)
Instance list_monoid {A : Type} : Monoid (list A) := {
mempty := [];
mappend := @app A
}.
Proof.
- apply app_assoc.
- reflexivity.
- apply app_nil_r.
Defined.
(* nat 的 Show 实例 *)
Require Import String.
Open Scope string_scope.
Instance nat_show : Show nat := {
show := fun n =>
match n with
| 0 => "0"
| 1 => "1"
| 2 => "2"
| _ => "..."
end
}.
使用类型类
(* 泛型函数使用类型类 *)
Definition elem {A : Type} `{Eq A} (x : A) (l : list A) : bool :=
fold_right (fun y acc => eqb x y || acc) false l.
(* 上下文约束 *)
Definition show_pair {A B : Type} `{Show A} `{Show B}
(p : A * B) : string :=
"(" ++ show (fst p) ++ ", " ++ show (snd p) ++ ")".
(* 多个约束 *)
Definition show_if_equal {A : Type} `{Eq A} `{Show A}
(x y : A) : string :=
if eqb x y then show x else "not equal".
(* 使用 Monoid *)
Definition concat_all {A : Type} `{Monoid A} (l : list A) : A :=
fold_right mappend mempty l.
习题 3.3.1
定义一个 Ord 类型类,包含比较操作,并为 nat 实现它。
习题 3.3.2
使用类型类定义一个泛型的 sort 函数。
3.4 自动化证明
Coq 提供了多种自动化策略来简化证明过程。
auto 和 eauto
(* auto 使用基本的证明搜索 *)
Theorem auto_example : forall P Q R : Prop,
(P -> Q) -> (Q -> R) -> P -> R.
Proof.
auto. (* 自动完成证明 *)
Qed.
(* auto 的深度控制 *)
Theorem auto_depth : forall P Q R S : Prop,
(P -> Q) -> (Q -> R) -> (R -> S) -> P -> S.
Proof.
auto 4. (* 搜索深度为 4 *)
Qed.
(* eauto 使用存在量词 *)
Theorem eauto_example : forall (A : Type) (P : A -> Prop),
(exists x, P x) -> exists y, P y.
Proof.
eauto.
Qed.
omega 和 lia
Require Import Omega.
Require Import Lia.
(* omega 解决线性算术 *)
Theorem omega_example : forall n m : nat,
n + m = m + n.
Proof.
intros. omega.
Qed.
(* lia 是 omega 的现代版本 *)
Theorem lia_example : forall x y z : nat,
x < y -> y < z -> x + 2 < z + 1.
Proof.
intros. lia.
Qed.
(* 复杂的线性不等式 *)
Theorem complex_ineq : forall a b c : nat,
a + b < c -> 2 * a + 2 * b < 2 * c.
Proof.
intros. lia.
Qed.
ring 和 field
Require Import Ring.
Require Import Field.
(* ring 解决环上的等式 *)
Theorem ring_example : forall x y : nat,
(x + y) * (x + y) = x * x + 2 * x * y + y * y.
Proof.
intros. ring.
Qed.
(* 对于有理数域 *)
Require Import QArith.
Open Scope Q_scope.
Theorem field_example : forall x y : Q,
y <> 0 -> (x + y) / y = x / y + 1.
Proof.
intros. field. assumption.
Qed.
Hint 数据库
(* 创建自定义 Hint 数据库 *)
Create HintDb mybase.
Theorem my_lemma1 : forall n : nat, n + 0 = n.
Proof. auto. Qed.
Theorem my_lemma2 : forall n : nat, 0 + n = n.
Proof. auto. Qed.
(* 添加到数据库 *)
Hint Resolve my_lemma1 : mybase.
Hint Resolve my_lemma2 : mybase.
(* 使用数据库 *)
Theorem use_hints : forall n m : nat,
(n + 0) + (0 + m) = n + m.
Proof.
intros. auto with mybase.
Qed.
(* Hint 的不同模式 *)
Hint Immediate my_lemma1 : mybase. (* 立即应用 *)
Hint Unfold plus : mybase. (* 展开定义 *)
Hint Constructors nat : mybase. (* 使用构造器 *)
Ltac 编程基础
(* 定义自定义策略 *)
Ltac my_auto :=
intros;
repeat (simpl; auto).
(* 条件策略 *)
Ltac break_match :=
match goal with
| [ |- context[match ?X with _ => _ end] ] => destruct X
end.
(* 重复应用 *)
Ltac crush :=
intros;
repeat (break_match; simpl; auto).
(* 使用自定义策略 *)
Theorem custom_tactic_example : forall b1 b2 : bool,
(if b1 then true else false) = b1.
Proof.
crush.
Qed.
(* 更复杂的 Ltac *)
Ltac inv H := inversion H; clear H; subst.
Ltac find_rewrite :=
match goal with
| [ H : ?X = ?Y |- context[?X] ] => rewrite H
end.
Ltac solve_by_inversion :=
intros;
match goal with
| [ H : _ |- _ ] => inv H; auto
end.
习题 3.4.1
编写一个 Ltac 策略 solve_bool,自动解决简单的布尔等式。
习题 3.4.2
创建一个 Hint 数据库来自动证明列表的基本性质。
3.5 归纳-递归类型
本节探讨更复杂的归纳类型定义,包括嵌套归纳、相互归纳和归纳-递归定义。
嵌套归纳类型
(* 嵌套列表 *)
Inductive nested_list (A : Type) : Type :=
| NNil : nested_list A
| NCons : A -> nested_list A -> nested_list A
| NNest : nested_list (nested_list A) -> nested_list A.
(* 展平嵌套列表 *)
Fixpoint flatten {A : Type} (nl : nested_list A) : list A :=
match nl with
| NNil _ => []
| NCons _ x xs => x :: flatten xs
| NNest _ nls =>
flat_map flatten (flatten_nested nls)
end
with flatten_nested {A : Type} (nl : nested_list (nested_list A))
: list (nested_list A) :=
match nl with
| NNil _ => []
| NCons _ x xs => x :: flatten_nested xs
| NNest _ _ => [] (* 防止无限嵌套 *)
end.
相互归纳类型
(* 偶数和奇数的相互定义 *)
Inductive even : nat -> Prop :=
| even_O : even 0
| even_S : forall n, odd n -> even (S n)
with odd : nat -> Prop :=
| odd_S : forall n, even n -> odd (S n).
(* 相互递归函数 *)
Fixpoint evenb (n : nat) : bool :=
match n with
| 0 => true
| S n' => oddb n'
end
with oddb (n : nat) : bool :=
match n with
| 0 => false
| S n' => evenb n'
end.
(* 证明它们的关系 *)
Theorem even_evenb : forall n,
even n <-> evenb n = true.
Proof.
intros n.
split.
- intros H. induction H.
+ reflexivity.
+ simpl. assumption.
- intros H.
(* 需要相互归纳,这里省略 *)
Admitted.
归纳族(Inductive Families)
(* 有限集合的表示 *)
Inductive fin : nat -> Type :=
| F1 : forall n, fin (S n)
| FS : forall n, fin n -> fin (S n).
(* fin n 有恰好 n 个元素 *)
Definition fin_to_nat {n : nat} (f : fin n) : nat :=
match f with
| F1 _ => 0
| FS _ f' => S (fin_to_nat f')
end.
(* 类型安全的列表索引 *)
Fixpoint nth_fin {A : Type} {n : nat}
(l : vec A n) (i : fin n) : A :=
match i in fin m return vec A m -> A with
| F1 n' => fun l =>
match l with
| vcons _ h _ => h
end
| FS n' i' => fun l =>
match l with
| vcons _ _ t => nth_fin t i'
end
end l.
归纳-递归定义
(* 良基树:每个节点的子树列表是有限的 *)
Inductive tree (A : Type) : Type :=
| Leaf : A -> tree A
| Node : list (tree A) -> tree A.
(* 树的大小 *)
Fixpoint tree_size {A : Type} (t : tree A) : nat :=
match t with
| Leaf _ => 1
| Node ts => S (trees_size ts)
end
with trees_size {A : Type} (ts : list (tree A)) : nat :=
match ts with
| [] => 0
| t :: ts' => tree_size t + trees_size ts'
end.
(* 更复杂的例子:带约束的归纳类型 *)
Inductive sorted_list : nat -> Type :=
| sorted_nil : sorted_list 0
| sorted_cons : forall (n : nat) (l : sorted_list n),
n <= (hd n l) -> sorted_list n
with hd (default : nat) {n : nat} (l : sorted_list n) : nat :=
match l with
| sorted_nil => default
| sorted_cons x _ _ => x
end.
终止性证明
(* 使用 Program 处理复杂的终止性 *)
Require Import Program.
Program Fixpoint merge (l1 l2 : list nat)
{measure (length l1 + length l2)} : list nat :=
match l1, l2 with
| [], _ => l2
| _, [] => l1
| h1 :: t1, h2 :: t2 =>
if h1 <=? h2
then h1 :: merge t1 l2
else h2 :: merge l1 t2
end.
Next Obligation.
simpl. lia.
Defined.
Next Obligation.
simpl. lia.
Defined.
(* 使用 Function 定义 *)
Require Import Recdef.
Function ack (n m : nat) {wf lt n} : nat :=
match n with
| 0 => S m
| S n' =>
match m with
| 0 => ack n' 1
| S m' => ack n' (ack n m')
end
end.
Proof.
- intros. simpl. auto.
- intros. simpl. auto.
- apply lt_wf.
Defined.
习题 3.5.1
定义一个表示 JSON 值的归纳类型。
习题 3.5.2
定义一个归纳谓词 balanced,表示二叉树是平衡的。
习题 3.5.3
使用 Program 定义快速排序,并证明其终止性。
第四章:Coq 实践应用
4.1 数据结构验证
本节展示如何在 Coq 中实现和验证常见的数据结构。
平衡二叉搜索树
(* 二叉搜索树的定义 *)
Inductive BST (A : Type) : Type :=
| Empty : BST A
| Node : A -> BST A -> BST A -> BST A.
(* 插入操作 *)
Fixpoint insert {A : Type} `{Ord A} (x : A) (t : BST A) : BST A :=
match t with
| Empty _ => Node _ x (Empty _) (Empty _)
| Node _ y l r =>
if le x y then Node _ y (insert x l) r
else Node _ y l (insert x r)
end.
(* BST 性质的归纳定义 *)
Inductive is_bst {A : Type} `{Ord A} : BST A -> Prop :=
| bst_empty : is_bst (Empty A)
| bst_node : forall x l r,
is_bst l ->
is_bst r ->
(forall y, In_bst y l -> le y x = true) ->
(forall y, In_bst y r -> le x y = true) ->
is_bst (Node A x l r)
with In_bst {A : Type} : A -> BST A -> Prop :=
| in_here : forall x l r, In_bst x (Node A x l r)
| in_left : forall x y l r, In_bst x l -> In_bst x (Node A y l r)
| in_right : forall x y l r, In_bst x r -> In_bst x (Node A y l r).
(* 插入保持 BST 性质 *)
Theorem insert_preserves_bst : forall A `{Ord A} (x : A) (t : BST A),
is_bst t -> is_bst (insert x t).
Proof.
intros A H x t Hbst.
induction Hbst.
- simpl. constructor; try constructor; intros y Hin; inversion Hin.
- simpl. destruct (le x x0) eqn:Hle.
+ constructor.
* apply IHHbst1.
* assumption.
* intros y Hin. (* 证明略 *)
Admitted.
红黑树
(* 颜色定义 *)
Inductive color : Type := Red | Black.
(* 红黑树定义 *)
Inductive RBTree (A : Type) : Type :=
| Leaf : RBTree A
| Node : color -> A -> RBTree A -> RBTree A -> RBTree A.
(* 红黑树不变量 *)
Inductive is_red_black {A : Type} : nat -> RBTree A -> Prop :=
| rb_leaf : is_red_black 1 (Leaf A)
| rb_red : forall n x l r,
is_red_black n l ->
is_red_black n r ->
is_red_black n (Node A Red x l r)
| rb_black : forall n x l r,
is_red_black n l ->
is_red_black n r ->
is_red_black (S n) (Node A Black x l r).
(* 没有连续的红节点 *)
Inductive no_red_red {A : Type} : RBTree A -> Prop :=
| nrr_leaf : no_red_red (Leaf A)
| nrr_black : forall x l r,
no_red_red l ->
no_red_red r ->
no_red_red (Node A Black x l r)
| nrr_red : forall x l r,
no_red_red l ->
no_red_red r ->
is_black l ->
is_black r ->
no_red_red (Node A Red x l r)
with is_black {A : Type} : RBTree A -> Prop :=
| black_leaf : is_black (Leaf A)
| black_node : forall x l r,
is_black (Node A Black x l r).
函数式队列
双列表队列实现
(* 使用两个列表实现队列 *)
Record queue (A : Type) : Type := mk_queue {
front : list A;
rear : list A
}.
(* 队列操作 *)
Definition empty_queue {A : Type} : queue A :=
mk_queue A [] [].
Definition enqueue {A : Type} (x : A) (q : queue A) : queue A :=
mk_queue A (front A q) (x :: rear A q).
Definition dequeue {A : Type} (q : queue A) : option (A * queue A) :=
match front A q with
| [] =>
match rev (rear A q) with
| [] => None
| h :: t => Some (h, mk_queue A t [])
end
| h :: t => Some (h, mk_queue A t (rear A q))
end.
(* 队列的抽象表示 *)
Definition queue_to_list {A : Type} (q : queue A) : list A :=
front A q ++ rev (rear A q).
(* 正确性定理 *)
Theorem enqueue_correct : forall A (x : A) (q : queue A),
queue_to_list (enqueue x q) = queue_to_list q ++ [x].
Proof.
intros. unfold queue_to_list, enqueue. simpl.
rewrite rev_cons. rewrite app_assoc. reflexivity.
Qed.
项目 4.1.1
实现一个验证过的最小堆(Min-Heap)数据结构。
4.2 算法正确性
本节展示如何验证经典算法的正确性。
排序算法验证
(* 排序的规范 *)
Definition sorted (l : list nat) : Prop :=
forall i j, i < j < length l ->
nth i l 0 <= nth j l 0.
(* 排列关系 *)
Definition permutation (l1 l2 : list nat) : Prop :=
forall x, count_occ Nat.eq_dec l1 x = count_occ Nat.eq_dec l2 x.
(* 插入排序的实现 *)
Fixpoint insert_sorted (x : nat) (l : list nat) : list nat :=
match l with
| [] => [x]
| h :: t =>
if x <=? h then x :: h :: t
else h :: insert_sorted x t
end.
Fixpoint insertion_sort (l : list nat) : list nat :=
match l with
| [] => []
| h :: t => insert_sorted h (insertion_sort t)
end.
(* 正确性证明 *)
Theorem insertion_sort_correct : forall l : list nat,
sorted (insertion_sort l) /\
permutation l (insertion_sort l).
Proof.
intros l.
split.
- (* 排序性 *)
induction l.
+ unfold sorted. intros. simpl in H. lia.
+ simpl. (* 证明需要关于 insert_sorted 的引理 *)
Admitted.
(* 归并排序 *)
Fixpoint split {A : Type} (l : list A) : list A * list A :=
match l with
| [] => ([], [])
| [x] => ([x], [])
| x :: y :: t =>
let (l1, l2) := split t in
(x :: l1, y :: l2)
end.
Fixpoint merge (l1 l2 : list nat) : list nat :=
match l1, l2 with
| [], _ => l2
| _, [] => l1
| h1 :: t1, h2 :: t2 =>
if h1 <=? h2
then h1 :: merge t1 l2
else h2 :: merge l1 t2
end.
Function merge_sort (l : list nat) {measure length l} : list nat :=
match l with
| [] => []
| [x] => [x]
| _ =>
let (l1, l2) := split l in
merge (merge_sort l1) (merge_sort l2)
end.
Proof.
(* 终止性证明 *)
- intros. (* split 产生更小的列表 *)
Admitted.
搜索算法验证
(* 二分搜索 *)
Fixpoint binary_search_aux (l : list nat) (x : nat)
(lo hi : nat) (fuel : nat) : option nat :=
match fuel with
| O => None
| S fuel' =>
if hi <=? lo then None
else
let mid := (lo + hi) / 2 in
match nth_error l mid with
| None => None
| Some y =>
if x =? y then Some mid
else if x <? y
then binary_search_aux l x lo mid fuel'
else binary_search_aux l x (S mid) hi fuel'
end
end.
Definition binary_search (l : list nat) (x : nat) : option nat :=
binary_search_aux l x 0 (length l) (length l).
(* 正确性规范 *)
Theorem binary_search_correct : forall l x,
sorted l ->
match binary_search l x with
| Some i => nth_error l i = Some x
| None => ~ In x l
end.
Proof.
(* 需要对 binary_search_aux 进行归纳 *)
Admitted.
图算法
(* 图的表示 *)
Definition graph := nat -> list nat.
(* 深度优先搜索 *)
Fixpoint dfs_visit (g : graph) (visited : list nat)
(v : nat) (fuel : nat) : list nat :=
match fuel with
| O => visited
| S fuel' =>
if existsb (Nat.eqb v) visited
then visited
else
let visited' := v :: visited in
fold_left (fun acc u => dfs_visit g acc u fuel')
(g v) visited'
end.
(* 路径存在性 *)
Inductive path (g : graph) : nat -> nat -> list nat -> Prop :=
| path_nil : forall v, path g v v []
| path_cons : forall u v w p,
In w (g u) ->
path g w v p ->
path g u v (u :: p).
(* DFS 的正确性 *)
Theorem dfs_finds_path : forall g u v fuel,
fuel >= length (nodes g) ->
(exists p, path g u v p) <->
In v (dfs_visit g [] u fuel).
Proof.
(* 证明略 *)
Admitted.
项目 4.2.1
验证 Dijkstra 最短路径算法。
4.3 编译器验证基础
本节介绍如何构建和验证一个简单的编程语言。
简单表达式语言
(* 算术表达式 *)
Inductive aexp : Type :=
| ANum : nat -> aexp
| APlus : aexp -> aexp -> aexp
| AMinus : aexp -> aexp -> aexp
| AMult : aexp -> aexp -> aexp.
(* 布尔表达式 *)
Inductive bexp : Type :=
| BTrue : bexp
| BFalse : bexp
| BEq : aexp -> aexp -> bexp
| BLe : aexp -> aexp -> bexp
| BNot : bexp -> bexp
| BAnd : bexp -> bexp -> bexp.
(* 求值函数 *)
Fixpoint aeval (a : aexp) : nat :=
match a with
| ANum n => n
| APlus a1 a2 => aeval a1 + aeval a2
| AMinus a1 a2 => aeval a1 - aeval a2
| AMult a1 a2 => aeval a1 * aeval a2
end.
Fixpoint beval (b : bexp) : bool :=
match b with
| BTrue => true
| BFalse => false
| BEq a1 a2 => aeval a1 =? aeval a2
| BLe a1 a2 => aeval a1 <=? aeval a2
| BNot b1 => negb (beval b1)
| BAnd b1 b2 => beval b1 && beval b2
end.
(* 优化:常量折叠 *)
Fixpoint fold_constants_aexp (a : aexp) : aexp :=
match a with
| ANum n => ANum n
| APlus a1 a2 =>
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 => ANum (n1 + n2)
| a1', a2' => APlus a1' a2'
end
| AMinus a1 a2 =>
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 => ANum (n1 - n2)
| a1', a2' => AMinus a1' a2'
end
| AMult a1 a2 =>
match fold_constants_aexp a1, fold_constants_aexp a2 with
| ANum n1, ANum n2 => ANum (n1 * n2)
| a1', a2' => AMult a1' a2'
end
end.
(* 优化的正确性 *)
Theorem fold_constants_aexp_sound : forall a,
aeval (fold_constants_aexp a) = aeval a.
Proof.
intros a.
induction a; simpl; try reflexivity.
- (* APlus *)
destruct (fold_constants_aexp a1) eqn:Ha1;
destruct (fold_constants_aexp a2) eqn:Ha2;
rewrite <- IHa1, <- IHa2; simpl; reflexivity.
- (* AMinus 和 AMult 类似 *)
Admitted.
命令式语言
(* 命令语言 *)
Inductive com : Type :=
| CSkip : com
| CAss : string -> aexp -> com
| CSeq : com -> com -> com
| CIf : bexp -> com -> com -> com
| CWhile : bexp -> com -> com.
(* 程序状态 *)
Definition state := string -> nat.
(* 更新状态 *)
Definition update (st : state) (x : string) (n : nat) : state :=
fun y => if string_dec x y then n else st y.
(* 大步操作语义 *)
Inductive ceval : com -> state -> state -> Prop :=
| E_Skip : forall st,
ceval CSkip st st
| E_Ass : forall st a1 n x,
aeval st a1 = n ->
ceval (CAss x a1) st (update st x n)
| E_Seq : forall c1 c2 st st' st'',
ceval c1 st st' ->
ceval c2 st' st'' ->
ceval (CSeq c1 c2) st st''
| E_IfTrue : forall st st' b c1 c2,
beval st b = true ->
ceval c1 st st' ->
ceval (CIf b c1 c2) st st'
| E_IfFalse : forall st st' b c1 c2,
beval st b = false ->
ceval c2 st st' ->
ceval (CIf b c1 c2) st st'
| E_WhileFalse : forall b st c,
beval st b = false ->
ceval (CWhile b c) st st
| E_WhileTrue : forall st st' st'' b c,
beval st b = true ->
ceval c st st' ->
ceval (CWhile b c) st' st'' ->
ceval (CWhile b c) st st''.
类型系统
(* 简单类型 *)
Inductive ty : Type :=
| TNat : ty
| TBool : ty.
(* 类型环境 *)
Definition context := string -> option ty.
(* 表达式的类型判断 *)
Inductive has_type_aexp : context -> aexp -> ty -> Prop :=
| T_ANum : forall Γ n,
has_type_aexp Γ (ANum n) TNat
| T_APlus : forall Γ a1 a2,
has_type_aexp Γ a1 TNat ->
has_type_aexp Γ a2 TNat ->
has_type_aexp Γ (APlus a1 a2) TNat
(* 其他规则类似 *).
(* 类型安全 *)
Theorem type_safety_aexp : forall Γ a T,
has_type_aexp Γ a T ->
(T = TNat /\ exists n, aeval a = n).
Proof.
intros Γ a T H.
induction H.
- split. reflexivity. exists n. reflexivity.
- destruct IHhas_type_aexp1, IHhas_type_aexp2.
split. reflexivity.
destruct H2, H3.
exists (x + x0). simpl. rewrite H2, H3. reflexivity.
Qed.
项目 4.3.1
为简单命令语言实现一个类型检查器并证明其正确性。
4.4 数学定理证明
本节展示如何在 Coq 中形式化和证明数学定理。
皮亚诺算术
(* 自然数的皮亚诺公理 *)
Module Peano.
Parameter N : Type.
Parameter O : N.
Parameter S : N -> N.
(* 公理 1:O 不是任何数的后继 *)
Axiom peano1 : forall n : N, S n <> O.
(* 公理 2:S 是单射 *)
Axiom peano2 : forall n m : N, S n = S m -> n = m.
(* 公理 3:归纳原理 *)
Axiom peano_ind : forall P : N -> Prop,
P O ->
(forall n, P n -> P (S n)) ->
forall n, P n.
(* 定义加法 *)
Fixpoint plus (n m : N) : N :=
match n with
| O => m
| S n' => S (plus n' m)
end.
(* 证明加法的性质 *)
Theorem plus_O_r : forall n : N, plus n O = n.
Proof.
intros n.
pattern n.
apply peano_ind.
- reflexivity.
- intros n' IH.
simpl. rewrite IH. reflexivity.
Qed.
End Peano.
群论基础
(* 群的定义 *)
Class Group (G : Type) := {
op : G -> G -> G;
e : G;
inv : G -> G;
assoc : forall a b c, op a (op b c) = op (op a b) c;
id_l : forall a, op e a = a;
id_r : forall a, op a e = a;
inv_l : forall a, op (inv a) a = e;
inv_r : forall a, op a (inv a) = e
}.
Notation "a * b" := (op a b) (at level 40, left associativity).
(* 群的基本定理 *)
Section GroupTheorems.
Context {G : Type} `{Group G}.
(* 单位元唯一 *)
Theorem unique_identity : forall e',
(forall a, e' * a = a) -> e' = e.
Proof.
intros e' H0.
rewrite <- (id_r e').
rewrite <- (H0 e).
reflexivity.
Qed.
(* 逆元唯一 *)
Theorem unique_inverse : forall a b c,
b * a = e -> a * c = e -> b = c.
Proof.
intros a b c Hba Hac.
rewrite <- (id_r b).
rewrite <- Hac.
rewrite <- assoc.
rewrite Hba.
rewrite id_l.
reflexivity.
Qed.
(* 消去律 *)
Theorem left_cancel : forall a b c,
a * b = a * c -> b = c.
Proof.
intros a b c H0.
rewrite <- (id_l b).
rewrite <- (inv_l a).
rewrite assoc.
rewrite H0.
rewrite <- assoc.
rewrite inv_l.
rewrite id_l.
reflexivity.
Qed.
End GroupTheorems.
(* 整数模 n 群 *)
Instance Zn_group (n : nat) (Hn : n > 0) : Group (fin n) := {
op := fun a b => fin_add a b;
e := fin_zero;
inv := fun a => fin_neg a
}.
Proof.
(* 证明群公理 *)
Admitted.
实数构造
(* 柯西序列 *)
Definition cauchy_sequence := nat -> Q.
Definition is_cauchy (s : cauchy_sequence) : Prop :=
forall ε : Q, ε > 0 -> exists N : nat,
forall m n : nat, m >= N -> n >= N ->
Qabs (s m - s n) < ε.
(* 等价关系 *)
Definition cauchy_equiv (s1 s2 : cauchy_sequence) : Prop :=
forall ε : Q, ε > 0 -> exists N : nat,
forall n : nat, n >= N ->
Qabs (s1 n - s2 n) < ε.
(* 实数定义为柯西序列的等价类 *)
Record R : Type := mk_real {
seq : cauchy_sequence;
seq_cauchy : is_cauchy seq
}.
(* 实数运算 *)
Definition Rplus (x y : R) : R.
destruct x as [sx Hx], y as [sy Hy].
exists (fun n => sx n + sy n).
(* 证明和仍是柯西序列 *)
Admitted.
(* 完备性 *)
Theorem R_complete : forall s : nat -> R,
(forall ε : Q, ε > 0 -> exists N : nat,
forall m n : nat, m >= N -> n >= N ->
R_dist (s m) (s n) < ε) ->
exists L : R, forall ε : Q, ε > 0 ->
exists N : nat, forall n : nat, n >= N ->
R_dist (s n) L < ε.
Proof.
(* 完备性的证明 *)
Admitted.
拓扑学基础
(* 拓扑空间 *)
Record TopologicalSpace : Type := {
carrier : Type;
open : (carrier -> Prop) -> Prop;
open_univ : open (fun _ => True);
open_empty : open (fun _ => False);
open_inter : forall U V, open U -> open V ->
open (fun x => U x /\ V x);
open_union : forall F : (carrier -> Prop) -> Prop,
(forall U, F U -> open U) ->
open (fun x => exists U, F U /\ U x)
}.
(* 连续函数 *)
Definition continuous {X Y : TopologicalSpace}
(f : carrier X -> carrier Y) : Prop :=
forall V, open Y V -> open X (fun x => V (f x)).
(* 同胚 *)
Definition homeomorphism {X Y : TopologicalSpace}
(f : carrier X -> carrier Y) (g : carrier Y -> carrier X) : Prop :=
continuous f /\ continuous g /\
(forall x, g (f x) = x) /\ (forall y, f (g y) = y).
(* 紧致性 *)
Definition compact (X : TopologicalSpace) : Prop :=
forall F : (carrier X -> Prop) -> Prop,
(forall U, F U -> open X U) ->
(forall x, exists U, F U /\ U x) ->
exists G : (carrier X -> Prop) -> Prop,
(forall U, G U -> F U) /\
(exists l : list (carrier X -> Prop),
(forall U, In U l <-> G U) /\
(forall x, exists U, In U l /\ U x)).
项目 4.4.1
证明康托尔定理:对任意集合 A,不存在从 A 到其幂集的满射。
4.5 程序提取
Coq 可以从证明中提取可执行程序。
提取到 OCaml
(* 定义一个验证过的排序函数 *)
Definition sort_with_proof (l : list nat) :
{ l' : list nat | sorted l' /\ permutation l l' }.
Proof.
exists (insertion_sort l).
apply insertion_sort_correct.
Defined.
(* 提取排序函数 *)
Definition verified_sort (l : list nat) : list nat :=
proj1_sig (sort_with_proof l).
(* 提取指令 *)
Require Extraction.
Extraction Language OCaml.
(* 基本类型映射 *)
Extract Inductive nat => "int"
[ "0" "(fun x -> x + 1)" ]
"(fun zero succ n -> if n = 0 then zero () else succ (n-1))".
Extract Inductive list => "list" [ "[]" "(::)" ].
(* 提取到文件 *)
Extraction "verified_sort.ml" verified_sort.
(* 生成的 OCaml 代码示例:
let rec insert_sorted x = function
| [] -> [x]
| h :: t ->
if x <= h then x :: h :: t
else h :: insert_sorted x t
let rec verified_sort = function
| [] -> []
| h :: t -> insert_sorted h (verified_sort t)
*)
提取到 Haskell
(* 切换到 Haskell *)
Extraction Language Haskell.
(* Haskell 特定的映射 *)
Extract Inductive nat => "Prelude.Integer"
[ "0" "\\x -> x Prelude.+ 1" ]
"\\zero succ n -> if n Prelude.== 0 then zero () else succ (n Prelude.- 1)".
Extract Inductive list => "[]" [ "[]" "(:)" ].
Extract Inductive prod => "(,)" [ "(,)" ].
Extract Inductive option => "Prelude.Maybe" [ "Prelude.Nothing" "Prelude.Just" ].
(* 提取纯函数式的二叉搜索树 *)
Extraction "BST.hs" BST insert lookup.
(* 生成的 Haskell 代码示例:
data BST a = Empty | Node a (BST a) (BST a)
insert :: Ord a => a -> BST a -> BST a
insert x Empty = Node x Empty Empty
insert x (Node y l r) =
if x <= y
then Node y (insert x l) r
else Node y l (insert x r)
*)
提取优化
(* 优化提取:去除证明项 *)
Definition gcd_with_proof (a b : nat) :
{ d : nat | d > 0 /\
(exists q1, a = q1 * d) /\
(exists q2, b = q2 * d) /\
(forall d', d' > 0 ->
(exists q1, a = q1 * d') ->
(exists q2, b = q2 * d') ->
d' <= d) }.
Proof.
(* 欧几里得算法的验证实现 *)
Admitted.
(* 提取时只保留计算部分 *)
Definition gcd (a b : nat) : nat :=
proj1_sig (gcd_with_proof a b).
(* 内联优化 *)
Extraction Inline gcd_with_proof.
Extraction NoInline gcd.
(* 避免提取某些定义 *)
Extract Constant mult => "( * )".
Extract Constant plus => "( + )".
(* 提取整个模块 *)
Recursive Extraction Library List.
Recursive Extraction Library Arith.
与原生代码集成
(* 外部函数接口 *)
Parameter read_file : string -> option string.
Parameter write_file : string -> string -> bool.
(* 提取到实际的文件操作 *)
Extract Constant read_file =>
"(fun filename ->
try
let ic = open_in filename in
let n = in_channel_length ic in
let s = Bytes.create n in
really_input ic s 0 n;
close_in ic;
Some (Bytes.to_string s)
with _ -> None)".
Extract Constant write_file =>
"(fun filename content ->
try
let oc = open_out filename in
output_string oc content;
close_out oc;
true
with _ -> false)".
(* 验证的文件处理程序 *)
Definition process_file (f : string -> string)
(input output : string) : bool :=
match read_file input with
| None => false
| Some content => write_file output (f content)
end.
(* 使用验证过的转换函数 *)
Definition verified_transform (s : string) : string :=
(* 某个经过验证的字符串转换 *)
s.
Definition main : bool :=
process_file verified_transform "input.txt" "output.txt".
Extraction "file_processor.ml" main.
项目 4.5.1
提取一个经过验证的 JSON 解析器并集成到实际应用中。
第五章:Lean 实践应用
5.1 数学形式化
Lean 在数学形式化方面有着强大的生态系统,特别是 Mathlib 库。
使用 Mathlib
import Mathlib.Data.Real.Basic
import Mathlib.Analysis.SpecialFunctions.Trigonometric.Basic
-- 证明三角恒等式
theorem sin_squared_plus_cos_squared (x : ℝ) :
Real.sin x ^ 2 + Real.cos x ^ 2 = 1 := by
simp [Real.sin_sq_add_cos_sq]
群论示例
import Mathlib.Algebra.Group.Defs
-- 定义群的性质
class MyGroup (G : Type*) extends Mul G, One G, Inv G where
mul_assoc : ∀ a b c : G, (a * b) * c = a * (b * c)
one_mul : ∀ a : G, 1 * a = a
mul_one : ∀ a : G, a * 1 = a
inv_mul_cancel : ∀ a : G, a⁻¹ * a = 1
-- 证明逆元的唯一性
theorem inv_unique {G : Type*} [MyGroup G] (a b : G)
(h : a * b = 1) : b = a⁻¹ := by
have h1 : a⁻¹ * (a * b) = a⁻¹ * 1 := by rw [h]
rw [MyGroup.mul_one] at h1
rw [← MyGroup.mul_assoc, MyGroup.inv_mul_cancel, MyGroup.one_mul] at h1
exact h1
习题 5.1.1
证明群中的左消去律:如果 a * b = a * c,则 b = c。
5.2 程序验证与优化
Lean 4 作为一个功能完整的编程语言,可以编写高效的验证程序。
验证排序算法
-- 定义有序列表
def Sorted : List Nat → Prop
| [] => True
| [_] => True
| x :: y :: xs => x ≤ y ∧ Sorted (y :: xs)
-- 插入排序
def insertSorted (x : Nat) : List Nat → List Nat
| [] => [x]
| y :: ys => if x ≤ y then x :: y :: ys
else y :: insertSorted x ys
def insertionSort : List Nat → List Nat
| [] => []
| x :: xs => insertSorted x (insertionSort xs)
-- 证明插入保持有序性
theorem insertSorted_preserves_sorted (x : Nat) (l : List Nat) :
Sorted l → Sorted (insertSorted x l) := by
intro h
induction l with
| nil => simp [insertSorted, Sorted]
| cons y ys ih =>
simp [insertSorted]
split_ifs with h1
· cases ys with
| nil => simp [Sorted, h1]
| cons z zs =>
simp [Sorted] at h ⊢
exact ⟨h1, h⟩
· cases ys with
| nil => simp [Sorted, le_of_not_le h1]
| cons z zs =>
simp [Sorted] at h ⊢
exact ⟨h.1, ih h.2⟩
使用依赖类型优化
-- 带长度信息的向量
inductive Vec (α : Type) : Nat → Type where
| nil : Vec α 0
| cons : α → Vec α n → Vec α (n + 1)
-- 安全的头部函数
def head {α : Type} {n : Nat} : Vec α (n + 1) → α
| Vec.cons x _ => x
-- 向量拼接
def append {α : Type} {m n : Nat} :
Vec α m → Vec α n → Vec α (m + n)
| Vec.nil, ys => ys
| Vec.cons x xs, ys => Vec.cons x (append xs ys)
习题 5.2.1
实现向量的 map 函数,保持长度不变。
5.3 元编程与自动化
Lean 4 提供了强大的元编程能力,可以编写自定义策略和代码生成器。
自定义策略
import Lean
open Lean Meta Elab Tactic
-- 定义一个简单的策略
def myTrivial : TacticM Unit := do
let goal ← getMainGoal
let goalType ← goal.getType
-- 尝试 rfl
try
let expr ← mkAppM ``Eq.refl #[← mkFreshExprMVar goalType]
goal.assign expr
return
catch _ => pure ()
-- 尝试 trivial
evalTactic (← `(tactic| trivial))
-- 注册策略
elab "my_trivial" : tactic => myTrivial
-- 使用示例
example : 2 + 2 = 4 := by my_trivial
宏和语法扩展
-- 定义新的语法
syntax "prove_by_cases" term "with" cases:term,* : tactic
macro_rules
| `(tactic| prove_by_cases $e with $[$cases],*) =>
`(tactic| cases $e <;> [$[$cases],*])
-- 使用新语法
example (p q : Prop) : p ∨ q → q ∨ p := by
intro h
prove_by_cases h with
(right; assumption),
(left; assumption)
习题 5.3.1
编写一个策略,自动处理简单的算术等式。
5.4 交互式定理证明
Lean 的交互式证明环境提供了丰富的工具支持。
结构化证明
-- 使用 have 和 show 的结构化证明
theorem sqrt_two_irrational : ¬ ∃ (p q : ℕ), q ≠ 0 ∧ p * p = 2 * q * q := by
intro ⟨p, q, hq, h_eq⟩
-- 假设 p 和 q 互质
have h_coprime : Nat.gcd p q = 1 := by sorry -- 简化处理
-- p² 是偶数,所以 p 是偶数
have p_even : Even p := by
have : Even (p * p) := by
use q * q
rw [h_eq]
ring
exact even_of_even_sq this
-- 设 p = 2k
obtain ⟨k, hk⟩ := p_even
-- 代入原式得到 q² 也是偶数
have q_even : Even q := by
have : 2 * k * (2 * k) = 2 * q * q := by
rw [← hk] at h_eq
exact h_eq
have : 2 * (2 * k * k) = 2 * q * q := by ring_nf at this ⊢; exact this
have : 2 * k * k = q * q := by linarith
have : Even (q * q) := by use k * k; exact this.symm
exact even_of_even_sq this
-- 但这与互质矛盾
sorry -- 完整证明需要更多细节
计算证明(calc)
-- 使用 calc 进行链式推理
example (a b c d : ℝ) (h1 : a = b) (h2 : b < c) (h3 : c ≤ d) :
a < d := by
calc a = b := h1
_ < c := h2
_ ≤ d := h3
习题 5.4.1
使用结构化证明方法证明:如果 n 是偶数,那么 n² 也是偶数。
5.5 Lean 项目实践
本节介绍如何组织和管理 Lean 项目。
项目结构
# 创建新项目
lake new my_project
cd my_project
# 项目结构
my_project/
├── MyProject.lean # 主文件
├── MyProject/
│ ├── Basic.lean # 基础定义
│ ├── Theorems.lean # 定理证明
│ └── Examples.lean # 示例
├── lakefile.lean # 构建配置
└── lean-toolchain # Lean 版本
使用 Lake 构建系统
-- lakefile.lean
import Lake
open Lake DSL
package «my_project» where
-- 添加依赖
dependencies := #[
{
name := `mathlib
src := Source.git
"https://github.com/leanprover-community/mathlib4.git"
"main"
}
]
@[default_target]
lean_lib «MyProject» where
-- 库配置
roots := #[`MyProject]
文档和测试
/-!
# My Project
这是项目文档的主页。
## 主要内容
- 基础定义
- 核心定理
- 应用示例
-/
-- 文档字符串
/-- 计算列表的长度 -/
def myLength {α : Type} : List α → Nat
| [] => 0
| _ :: xs => 1 + myLength xs
-- 单元测试
#eval myLength [1, 2, 3] -- 期望输出: 3
-- 属性测试
theorem myLength_correct {α : Type} (l : List α) :
myLength l = l.length := by
induction l with
| nil => rfl
| cons x xs ih => simp [myLength, List.length, ih]
习题 5.5.1
创建一个简单的 Lean 包,实现并验证一个栈数据结构。
附录
A. Coq 标准库参考
常用模块
Arith- 自然数算术List- 列表操作Bool- 布尔运算Logic- 逻辑连接词Relations- 关系理论Sorting- 排序算法Wellfounded- 良基关系
重要策略速查
(* 基本策略 *)
reflexivity (* 证明反身性 *)
symmetry (* 交换等式两边 *)
transitivity (* 传递性 *)
assumption (* 使用假设 *)
exact H (* 精确应用 *)
(* 引入和应用 *)
intros (* 引入全称量词 *)
apply H (* 应用假设或定理 *)
eapply H (* 延迟实例化 *)
(* 分解 *)
destruct (* 分情况讨论 *)
induction (* 归纳法 *)
inversion (* 反演 *)
(* 重写 *)
rewrite H (* 使用等式重写 *)
rewrite <- H (* 反向重写 *)
subst (* 替换变量 *)
(* 简化 *)
simpl (* 简化表达式 *)
unfold f (* 展开定义 *)
fold f (* 折叠定义 *)
(* 自动化 *)
auto (* 自动证明 *)
eauto (* 扩展自动证明 *)
lia (* 线性算术 *)
ring (* 环运算 *)
field (* 域运算 *)
B. Coq 常见错误与调试
类型错误
错误:The term "x" has type "nat" while it is expected to have type "bool"
解决:检查类型是否匹配,可能需要类型转换函数。
未完成的证明
错误:Error: Attempt to save an incomplete proof
解决:使用 Admitted 暂时承认,或完成所有子目标。
策略失败
错误:Error: No applicable tactic
解决:检查当前目标,可能需要先做一些准备工作。
C. Lean 标准库参考
常用模块
Init- 基础定义和符号Std- 标准库扩展Mathlib- 数学库Mathlib.Data.Nat- 自然数Mathlib.Data.List- 列表操作Mathlib.Logic.Basic- 基础逻辑Mathlib.Tactic- 策略库Mathlib.Algebra- 代数结构Mathlib.Analysis- 分析学Mathlib.Topology- 拓扑学
重要策略速查
-- 基本策略
rfl -- 证明反身性
symm -- 交换等式两边
trans -- 传递性
assumption -- 使用假设
exact h -- 精确应用
-- 引入和应用
intro -- 引入假设
intros -- 引入多个假设
apply h -- 应用假设或定理
refine -- 精细化目标
-- 分解
cases -- 分情况讨论
induction -- 归纳法
match -- 模式匹配
obtain -- 解构存在量词
-- 重写
rw [h] -- 使用等式重写
simp -- 简化器
simp only -- 受限简化
conv -- 转换模式
-- 简化
dsimp -- 定义简化
unfold f -- 展开定义
change -- 改变目标形式
-- 自动化
simp -- 简化器
ring -- 环策略
linarith -- 线性算术
norm_num -- 数值计算
omega -- Presburger 算术
aesop -- 自动证明搜索
-- 数学专用
field_simp -- 域简化
ring_nf -- 环标准形式
group -- 群论策略
abel -- 阿贝尔群策略
类型类实例
-- 常用类型类
instance : Add Nat := ⟨Nat.add⟩
instance : Mul Nat := ⟨Nat.mul⟩
instance : OfNat Nat n := ⟨n⟩
-- 代数结构
instance : Monoid α where
mul := (· * ·)
one := 1
mul_assoc := mul_assoc
one_mul := one_mul
mul_one := mul_one
-- 自定义实例
instance : ToString MyType where
toString x := s!"MyType: {x.field}"
元编程基础
-- 自定义策略
syntax "my_tactic" : tactic
macro_rules
| `(tactic| my_tactic) => `(tactic| simp; rfl)
-- 宏定义
macro "prove_by" t:tactic : tactic =>
`(tactic| first | $t | simp | rfl)
-- 元变量操作
open Lean Meta in
def myTactic : TacticM Unit := do
let goal ← getMainGoal
let type ← goal.getType
trace[myTactic] "Goal type: {type}"
D. Lean 常见错误与调试
类型错误
错误:type mismatch
-- 错误示例
def f (n : Nat) : Bool := n -- 错误:期望 Bool,得到 Nat
-- 修正
def f (n : Nat) : Bool := n > 0
类型推断失败
错误:failed to synthesize instance
-- 错误示例
#check (2 : ℝ) + (3 : ℕ) -- 错误:无法推断加法实例
-- 修正
#check (2 : ℝ) + (3 : ℝ) -- 显式类型标注
#check (2 : ℝ) + ↑3 -- 使用强制转换
策略失败
错误:tactic failed
-- 调试技巧
theorem my_theorem : P := by
trace_state -- 显示当前目标
sorry -- 暂时跳过
-- 使用 try 和 first
theorem my_theorem : P := by
try simp -- 尝试简化
first -- 尝试多个策略
| rfl
| assumption
| sorry
终止性检查
错误:failed to prove termination
-- 错误示例
def badFib : Nat → Nat
| 0 => 0
| 1 => 1
| n => badFib (n - 1) + badFib (n - 2) -- 错误:n-2 可能不减小
-- 修正1:使用 termination_by
def fib : Nat → Nat
| 0 => 0
| 1 => 1
| n + 2 => fib n + fib (n + 1)
-- 修正2:显式终止证明
def fib' (n : Nat) : Nat :=
match n with
| 0 => 0
| 1 => 1
| n + 2 => fib' n + fib' (n + 1)
termination_by n
Universe 级别错误
错误:universe level mismatch
-- 错误示例
def bad : Type → Type := fun α => List α -- 错误:universe 不匹配
-- 修正
def good : Type u → Type u := fun α => List α
-- 或
def good' (α : Type u) : Type u := List α
调试工具
-- 跟踪选项
set_option trace.Meta.synthInstance true -- 跟踪实例合成
set_option trace.simp true -- 跟踪简化器
set_option pp.all true -- 显示所有隐式参数
-- 检查表达式
#check @List.map -- 查看完整类型
#print List.map -- 查看定义
#reduce 2 + 3 -- 计算表达式
-- 目标检查
example : P := by
trace_target -- 显示目标
trace_state -- 显示完整状态
sorry
-- 性能分析
set_option profiler true
#eval expensive_computation
包管理问题
Lake 构建错误处理:
# 清理缓存
lake clean
# 更新依赖
lake update
# 详细输出
lake build -v
# 检查 lean-toolchain
cat lean-toolchain
E. Ltac:Coq 的策略语言
Ltac (Language of Tactics) 是 Coq 的策略编程语言,允许用户编写自定义策略、组合现有策略,并实现证明自动化。
为什么需要 Ltac?
Ltac 存在的核心原因是证明的重复性模式需要自动化:
- 手动证明的痛点:很多证明包含大量重复的步骤模式
- 领域特定性:不同领域的证明有不同的模式,需要定制化
- 可组合性:基本策略需要灵活组合来解决复杂问题
本质上,Ltac 就是一个模式匹配的状态机,它的设计目标是:
- 对证明状态(goals 和 hypotheses)进行模式匹配
- 根据匹配结果选择和组合策略
- 提供回溯机制处理失败情况
什么是 Ltac?
Ltac 是一种领域特定语言,专门用于:
- 组合和编排基本策略
- 编写可重用的证明模式
- 实现证明自动化
- 处理证明中的重复性工作
基础语法
定义简单的 Ltac 策略
(* 定义一个简单的策略 *)
Ltac my_simpl := simpl; reflexivity.
(* 使用自定义策略 *)
Theorem test : 2 + 2 = 4.
Proof.
my_simpl. (* 相当于执行 simpl; reflexivity *)
Qed.
(* 带参数的策略 *)
Ltac apply_n_times tac n :=
match n with
| O => idtac (* 什么都不做 *)
| S ?n' => tac; apply_n_times tac n'
end.
策略组合器
顺序组合 (;)
(* tac1; tac2 - 先执行 tac1,然后对所有子目标执行 tac2 *)
Ltac intro_and_simpl := intros; simpl.
(* 选择性组合 *)
Ltac solve_simple :=
simpl;
first [ reflexivity | assumption | trivial ].
并行组合 (||)
(* tac1 || tac2 - 尝试 tac1,如果失败则尝试 tac2 *)
Ltac auto_solve :=
reflexivity || assumption || auto.
(* try - 尝试执行,失败也不报错 *)
Ltac safe_rewrite H :=
try rewrite H.
模式匹配
对目标进行模式匹配
Ltac break_and :=
match goal with
| [ |- ?A /\ ?B ] => split
end.
(* 更复杂的模式匹配 *)
Ltac find_eq_and_rewrite :=
match goal with
| [ H : ?x = ?y |- context[?x] ] => rewrite H
| [ H : ?x = ?y |- context[?y] ] => rewrite <- H
end.
对假设进行模式匹配
Ltac contradict :=
match goal with
| [ H : False |- _ ] => contradiction
| [ H : ?P, H' : ~ ?P |- _ ] => contradiction
end.
(* 查找并使用等式 *)
Ltac use_eq :=
match goal with
| [ H : _ = _ |- _ ] => rewrite H || rewrite <- H
end.
实用 Ltac 策略示例
自动化简单证明
(* 解决简单的算术等式 *)
Ltac crush_arith :=
intros;
repeat (simpl in *;
try ring;
try lia;
auto).
(* 自动分解合取和存在量词 *)
Ltac decompose_ex :=
repeat match goal with
| [ H : exists x, _ |- _ ] => destruct H
| [ H : _ /\ _ |- _ ] => destruct H
end.
调试辅助策略
(* 打印当前目标状态 *)
Ltac print_goal :=
match goal with
| [ |- ?G ] => idtac "Current goal:" G
end.
(* 标记证明步骤 *)
Ltac step msg :=
idtac "Step:" msg;
print_goal.
高级特性
递归策略
(* 递归地应用简化 *)
Ltac simpl_all :=
simpl;
repeat match goal with
| [ H : _ |- _ ] => simpl in H
end.
(* 深度优先搜索证明 *)
Ltac depth_first_proof n :=
match n with
| O => fail
| S ?n' =>
first [ reflexivity
| assumption
| (progress simpl; depth_first_proof n')
| (progress auto; depth_first_proof n') ]
end.
上下文操作
(* 清理无用假设 *)
Ltac cleanup :=
repeat match goal with
| [ H : ?X = ?X |- _ ] => clear H
| [ H : True |- _ ] => clear H
end.
(* 生成新假设 *)
Ltac remember_all :=
repeat match goal with
| [ |- context[?f ?x] ] =>
let y := fresh "y" in
remember (f x) as y
end.
Ltac2:下一代策略语言
Coq 正在开发 Ltac2,这是 Ltac 的继任者,提供:
- 静态类型系统
- 更好的性能
- 更清晰的语义
- 与 OCaml 更好的互操作性
Ltac2 示例
From Ltac2 Require Import Ltac2.
(* Ltac2 策略定义 *)
Ltac2 my_auto () :=
first [ reflexivity ()
| assumption ()
| auto () ].
(* 使用 Ltac2 策略 *)
Goal forall x : nat, x = x.
Proof.
ltac2:(my_auto ()).
Qed.
最佳实践
- 命名规范:使用描述性名称,如
solve_by_induction而非tac1 - 错误处理:使用
try和first来处理可能失败的策略 - 模块化:将复杂策略分解为小的、可重用的组件
- 文档:为复杂的 Ltac 策略添加注释说明其用途
- 调试:使用
idtac和fail来调试策略
练习:编写 Ltac 策略
编写一个 Ltac 策略 solve_bool_eq,自动解决关于布尔值的简单等式。
高级自动化技术
智能假设管理
(* 自动应用相关假设 *)
Ltac smart_apply :=
match goal with
| [ H : forall x, ?P x -> ?Q x, H' : ?P ?a |- ?Q ?a ] =>
apply (H a H')
| [ H : ?P -> ?Q, H' : ?P |- ?Q ] =>
apply (H H')
| [ H : forall x, ?P x |- ?P ?a ] =>
apply H
end.
(* 传递性链式推理 *)
Ltac chain_trans :=
match goal with
| [ H1 : ?a = ?b, H2 : ?b = ?c |- ?a = ?c ] =>
transitivity b; [exact H1 | exact H2]
| [ H1 : ?a <= ?b, H2 : ?b <= ?c |- ?a <= ?c ] =>
transitivity b; [exact H1 | exact H2]
end.
(* 自动前向推理 *)
Ltac forward_reasoning :=
repeat match goal with
| [ H : ?P -> ?Q, H' : ?P |- _ ] =>
let H_new := fresh "H" in
assert (H_new : Q) by (apply H; exact H')
end.
归纳证明自动化
(* 自动归纳策略 *)
Ltac auto_induction x :=
induction x;
intros;
simpl in *;
try reflexivity;
try (rewrite IHx; reflexivity);
try (rewrite <- IHx; reflexivity);
auto.
(* 列表相关的自动化 *)
Ltac list_solver :=
intros;
repeat match goal with
| [ |- context[?l ++ nil] ] => rewrite app_nil_r
| [ |- context[nil ++ ?l] ] => rewrite app_nil_l
| [ |- context[(?l1 ++ ?l2) ++ ?l3] ] => rewrite app_assoc
| [ H : ?l = nil |- _ ] => destruct l; [clear H | discriminate H]
| [ H : nil = ?l |- _ ] => destruct l; [clear H | discriminate H]
| [ H : ?h :: ?t = nil |- _ ] => discriminate H
| [ H : nil = ?h :: ?t |- _ ] => discriminate H
end;
auto.
(* 自然数归纳的高级策略 *)
Ltac nat_ind_strong P :=
intro n;
pattern n;
apply (nat_ind P);
[clear n | intro n'; intro IHn'].
反证法自动化
(* 自动反证法 *)
Ltac by_contradiction :=
match goal with
| [ |- ?P ] =>
destruct (classic P) as [H | H];
[exact H | exfalso]
end.
(* 寻找矛盾 *)
Ltac find_contradiction :=
match goal with
| [ H : ?P, H' : ~ ?P |- _ ] => contradiction
| [ H : ?x <> ?x |- _ ] => contradiction
| [ H : ?x = ?y, H' : ?x <> ?y |- _ ] => contradiction
| [ H : ?x < ?x |- _ ] => apply lt_irrefl in H; contradiction
| [ H : S ?n <= ?n |- _ ] => apply le_S_n in H; apply le_Sn_n in H; contradiction
end.
定制化证明搜索
(* 带深度限制的证明搜索 *)
Ltac bounded_search depth :=
match depth with
| O => fail "Search depth exceeded"
| S ?d =>
first [ assumption
| reflexivity
| (progress simpl; bounded_search d)
| (progress auto; bounded_search d)
| (left; bounded_search d)
| (right; bounded_search d)
| (split; bounded_search d)
| (intro; bounded_search d) ]
end.
(* 启发式证明搜索 *)
Ltac heuristic_prove :=
(* 先尝试简单策略 *)
try reflexivity;
try assumption;
try discriminate;
(* 然后尝试分解 *)
repeat match goal with
| [ |- _ /\ _ ] => split
| [ H : _ /\ _ |- _ ] => destruct H
| [ H : _ \/ _ |- _ ] => destruct H
| [ |- exists _, _ ] => eexists
end;
(* 最后尝试自动化 *)
auto.
领域特定的自动化
(* 集合论证明自动化 *)
Ltac set_solver :=
intros;
unfold subset, element_of, empty_set in *;
firstorder.
(* 排列组合证明自动化 *)
Ltac perm_solver :=
intros;
repeat match goal with
| [ |- Permutation ?l ?l ] => reflexivity
| [ H : Permutation ?l1 ?l2 |- Permutation ?l2 ?l1 ] =>
apply Permutation_sym; exact H
| [ H1 : Permutation ?l1 ?l2, H2 : Permutation ?l2 ?l3 |- Permutation ?l1 ?l3 ] =>
eapply Permutation_trans; [exact H1 | exact H2]
end.
(* 等价关系证明自动化 *)
Ltac equiv_solver :=
intros;
repeat match goal with
| [ |- ?R ?x ?x ] => apply refl_equiv
| [ H : ?R ?x ?y |- ?R ?y ?x ] => apply sym_equiv; exact H
| [ H1 : ?R ?x ?y, H2 : ?R ?y ?z |- ?R ?x ?z ] =>
eapply trans_equiv; [exact H1 | exact H2]
end.
性能优化技巧
(* 避免重复计算 *)
Ltac cache_computation term :=
let v := fresh "cached" in
remember term as v.
(* 惰性匹配 - 只在需要时匹配 *)
Ltac lazy_match :=
lazymatch goal with
| [ |- ?P /\ ?Q ] => split
| [ |- ?P ] => fail "Not a conjunction"
end.
(* 多线程策略(概念性) *)
Ltac parallel_try tac1 tac2 :=
first [progress tac1 | progress tac2].
(* 记忆化策略结果 *)
Ltac memoized_auto :=
let cache := fresh "cache" in
assert (cache : True) by constructor;
auto;
调试和错误处理
(* 详细的错误信息 *)
Ltac fail_with_context msg :=
match goal with
| [ |- ?G ] => fail "Error:" msg "in goal:" G
end.
(* 断言和前置条件检查 *)
Ltac assert_precondition P :=
assert P by (auto || fail "Precondition failed").
(* 策略执行跟踪 *)
Ltac trace_exec tac name :=
idtac "Entering" name;
let result := tac in
idtac "Exiting" name;
result.
(* 超时处理 *)
Ltac with_timeout n tac :=
timeout n tac.
与其他 Coq 特性的集成
(* 与类型类集成 *)
Ltac typeclass_solver :=
typeclasses eauto.
(* 与 Program 集成 *)
Ltac program_simpl :=
program_simpl;
try solve [program_solve_wf].
(* 与 setoid 重写集成 *)
Ltac setoid_rewrite_all :=
repeat match goal with
| [ H : ?R ?x ?y |- context[?x] ] => setoid_rewrite H
end.
(* 与 ssreflect 集成 *)
Ltac ssr_auto :=
by []; done.
高级练习:编写归纳证明自动化
编写一个 Ltac 策略,自动证明关于列表长度的简单性质。
实战中的 Ltac 最佳实践
大型项目中的 Ltac 组织
(* 创建模块化的策略库 *)
Module MyTactics.
(* 基础策略 *)
Ltac basic_cleanup :=
intros;
simpl in *;
unfold not in *;
repeat match goal with
| [ H : ?X = ?X |- _ ] => clear H
| [ H : True |- _ ] => clear H
| [ H : ?X -> ?X |- _ ] => clear H
end.
(* 中级策略 *)
Ltac solve_by_inverting :=
basic_cleanup;
repeat match goal with
| [ H : ?f _ = ?f _ |- _ ] => injection H; clear H; intros; subst
| [ H : Some _ = Some _ |- _ ] => injection H; clear H; intros; subst
| [ H : Some _ = None |- _ ] => discriminate H
| [ H : None = Some _ |- _ ] => discriminate H
end;
auto.
(* 高级策略 *)
Ltac full_auto :=
basic_cleanup;
solve_by_inverting;
repeat (progress (auto; firstorder)).
End MyTactics.
Import MyTactics.
领域特定语言 (DSL) 的策略
(* 为编译器验证设计的策略 *)
Ltac eval_step :=
match goal with
| [ |- context[eval_expr (EConst ?n)] ] =>
unfold eval_expr; simpl
| [ |- context[eval_expr (EPlus ?e1 ?e2)] ] =>
unfold eval_expr; simpl;
rewrite <- eval_expr_sound
| [ |- context[eval_expr (EVar ?x)] ] =>
unfold eval_expr; simpl;
rewrite lookup_correct
end.
(* 类型系统的自动化 *)
Ltac type_check :=
repeat match goal with
| [ |- has_type _ (TConst _) TInt ] =>
constructor
| [ |- has_type ?env (TVar ?x) ?t ] =>
apply T_Var; apply lookup_env
| [ |- has_type ?env (TApp ?f ?x) ?t ] =>
eapply T_App; [type_check | type_check]
end.
复杂证明的组织
(* 使用子目标标记 *)
Ltac prove_with_labels :=
match goal with
| [ |- ?P /\ ?Q ] =>
split;
[ idtac "Proving" P; prove_with_labels
| idtac "Proving" Q; prove_with_labels ]
| [ |- forall x, ?P x ] =>
let x' := fresh x in
intro x';
idtac "Proving for" x';
prove_with_labels
| _ => auto
end.
(* 结构化证明 *)
Ltac structured_proof :=
(* 阶段1: 初始化 *)
intros; simpl in *;
(* 阶段2: 分解复杂结构 *)
repeat match goal with
| [ H : exists x, _ |- _ ] => destruct H
| [ H : _ /\ _ |- _ ] => destruct H
| [ |- _ /\ _ ] => split
end;
(* 阶段3: 应用域特定知识 *)
repeat match goal with
| [ H : ?P -> ?Q, H' : ?P |- _ ] =>
apply H in H'
end;
(* 阶段4: 终结 *)
auto.
性能调优和经验教训
- 避免回溯:使用
lazymatch代替match来避免不必要的回溯 - 早期失败:在不可能成功的情况下尽早使用
fail - 缓存结果:使用
remember来避免重复计算 - 限制搜索空间:使用
only和using来限制自动化策略的搜索范围 - 渐进式开发:先编写简单版本,然后逐步优化
调试复杂 Ltac 程序
(* 使用 Info 命令 *)
Goal forall n m : nat, n + m = m + n.
Proof.
intros.
Info 2 auto. (* 查看 auto 执行的详细步骤 *)
(* 使用 Show Ltac Profile *)
Set Ltac Profiling.
(* 执行一些策略 *)
Show Ltac Profile.
(* 使用 debug 模式 *)
Set Ltac Debug.
auto.
Unset Ltac Debug.
Abort.
Ltac 的可编程性和替代方案
Ltac 的 AST 和外部访问
Ltac 确实有内部 AST 表示,但:
- 有限的外部访问:Ltac 的 AST 不像 MetaCoq 那样完全暴露给外部
- Ltac2 的改进:新的 Ltac2 提供了更好的程序化接口
- ML 插件:可以通过 OCaml 插件访问更底层的证明状态
OCaml 插件示例
(* OCaml 插件直接操作证明状态 *)
let my_tactic : unit Proofview.tactic =
Proofview.Goal.enter (fun gl ->
let env = Proofview.Goal.env gl in
let sigma = Proofview.Goal.sigma gl in
let concl = Proofview.Goal.concl gl in
(* 直接操作证明状态 *)
...
)
不使用 Ltac 的替代方案
1. SerAPI / coq-lsp
# Python 示例使用 SerAPI
from serapi import SerAPI
coq = SerAPI()
coq.add("Theorem test : 2 + 2 = 4.")
state = coq.query_goals()
# 程序化地分析状态并发送策略
coq.execute("simpl.")
coq.execute("reflexivity.")
2. MetaCoq 直接构造证明
From MetaCoq.Template Require Import All.
(* 直接构造证明项 *)
Definition my_proof : 2 + 2 = 4 :=
eq_refl 4.
(* 或者程序化生成证明 *)
MetaCoq Quote Definition q_proof := my_proof.
3. Elpi (λProlog 策略语言)
% Elpi 策略示例
solve (goal _ _ {{ lp:A = lp:B }} as G) GL :-
% 直接操作证明状态
coq.unify-eq A B ok,
refine {{ eq_refl lp:A }} G GL.
4. 直接构造证明项
(* 完全跳过策略,直接写证明项 *)
Definition my_theorem : forall n : nat, n + 0 = n :=
fix f n := match n with
| 0 => eq_refl 0
| S n' => f_equal S (f n')
end.
为什么 Ltac 仍然流行?
- 集成性:Ltac 深度集成在 Coq 中,无需额外工具
- 表达力:对证明状态的模式匹配很自然
- 生态系统:大量现有代码使用 Ltac
- 学习曲线:比 OCaml 插件或 MetaCoq 更容易上手
未来展望
Ltac 虽然强大,但也有一些局限性。Coq 社区正在开发新的解决方案:
- Ltac2:提供静态类型和更好的性能
- Elpi:基于 λProlog 的策略语言
- MetaCoq:元编程框架
- ML 策略:使用 OCaml 编写高性能策略
掌握 Ltac 是成为 Coq 高手的必经之路。通过编写自定义策略,您可以大大提高证明效率,并为特定领域构建强大的自动化工具。
Ltac2:下一代策略语言
设计动机
Ltac1 的主要问题:
- 动态类型:运行时才发现类型错误
- 性能问题:解释执行,回溯开销大
- 语义模糊:求值顺序不明确
- 调试困难:错误信息不友好
Ltac2 的核心特性
1. 静态类型系统
From Ltac2 Require Import Ltac2.
(* 类型明确的策略定义 *)
Ltac2 my_tactic (n : int) (tac : unit -> unit) : unit :=
match Int.equal n 0 with
| true => ()
| false => tac (); my_tactic (Int.sub n 1) tac
end.
(* 多态策略 *)
Ltac2 map_hyps (f : ident -> constr -> unit) : unit :=
List.iter (fun h =>
let typ := Constr.type (Control.hyp h) in
f h typ
) (Control.hyps ()).
2. 异常处理
Ltac2 Type exn ::= [ MyError (string) ].
Ltac2 safe_assumption () :=
match Control.case (fun () => assumption) with
| Val _ => Message.print (Message.of_string "Assumption succeeded")
| Err e => Control.throw (MyError "No matching assumption")
end.
3. 模块系统
Ltac2 mutable debug : bool := false.
Module MyTactics.
Ltac2 trace (msg : string) :=
if debug then Message.print (Message.of_string msg)
else ().
Ltac2 smart_intro () :=
trace "Introducing...";
intro.
End MyTactics.
4. 更好的控制流
(* 明确的求值控制 *)
Ltac2 lazy_or tac1 tac2 :=
Control.plus (fun () => tac1 ()) (fun _ => tac2 ()).
(* 高效的循环 *)
Ltac2 repeat_n (n : int) (tac : unit -> unit) :=
let rec aux i :=
if Int.equal i 0 then ()
else (tac (); aux (Int.sub i 1))
in aux n.
Ltac2 实战示例
Ltac2 crush_arith () :=
(* 类型安全的模式匹配 *)
repeat (
first [
reflexivity
| progress (Std.intros)
| progress (Std.simpl)
| match! goal with
| [ h : ?x = ?y |- _ ] => Std.rewrite h
| [ |- context [?n + 0] ] =>
Pattern.rewrite (fun () => open_constr:(_ + 0))
(fun () => open_constr:(_))
(fun () => plus_n_O)
end
]
).
(* 使用 *)
Goal forall n m : nat, n + 0 + m = n + m.
Proof.
ltac2:(crush_arith ()).
Qed.
Ltac2 vs Ltac1 对比
| 特性 | Ltac1 | Ltac2 |
|---|---|---|
| 类型系统 | 动态类型 | 静态类型 |
| 性能 | 解释执行 | 编译执行 |
| 错误处理 | 基本 | 完善的异常系统 |
| 模块化 | 有限 | 完整模块系统 |
| 学习曲线 | 简单 | 中等 |
F. MetaCoq:元编程框架
MetaCoq 是 Coq 的元编程框架,允许在 Coq 内部操作和生成 Coq 项。它提供了对 Coq 的内部表示的完全访问。
核心概念
MetaCoq 提供了:
- 具体化(Reification):将 Coq 项转换为 AST
- 反具体化(Reflection):将 AST 转换回 Coq 项
- 类型检查器:在 Coq 内验证项的类型
- 求值器:在 Coq 内求值项
基本使用
1. Quote:获取 AST
From MetaCoq.Template Require Import All.
Definition foo := 42.
(* 获取 foo 的 AST *)
MetaCoq Quote Definition foo_ast := foo.
(* foo_ast = tConst "Top.foo" [] *)
(* 获取更复杂的项 *)
MetaCoq Quote Definition plus_ast := (fun x y : nat => x + y).
Print plus_ast.
(* 显示完整的 AST 结构 *)
2. Unquote:从 AST 生成项
(* 程序化构造 AST *)
Definition my_const_ast :=
tConstruct {| inductive_mind := "Coq.Init.Datatypes.nat";
inductive_ind := 0 |} 1 []. (* S 构造子 *)
(* 转换回 Coq 项 *)
MetaCoq Unquote Definition my_num :=
(tApp my_const_ast [tConstruct ... 0 []]). (* S O *)
(* my_num = 1 *)
3. 程序化证明生成
From MetaCoq.Template Require Import All.
Import MCMonadNotation.
(* 自动生成等式证明 *)
Definition make_eq_proof (n : nat) : TemplateMonad unit :=
let rhs := tApp (tConst "Nat.add" [])
[tConst "n" []; tConstruct ... 0 []] in
let eq_type := tApp (tConst "eq" []) [tInd ...; tConst "n" []; rhs] in
proof <- tmLemma ("n_plus_0_eq_" ++ string_of_nat n) eq_type ;;
tmDefinition ("proof_" ++ string_of_nat n) proof.
(* 运行元程序 *)
MetaCoq Run (make_eq_proof 5).
高级应用
1. 自动派生实例
(* 自动生成 Decidable 实例 *)
MetaCoq Derive Decidable for nat.
MetaCoq Derive Decidable for list.
(* 自动生成归纳原理 *)
MetaCoq Derive Induction for tree.
2. 程序变换
(* 定义优化变换 *)
Definition optimize_plus (t : term) : term :=
match t with
| tApp (tConst "Nat.add" _) [x; tConstruct _ 0 []] => x (* x + 0 => x *)
| tApp (tConst "Nat.add" _) [tConstruct _ 0 []; y] => y (* 0 + y => y *)
| _ => t
end.
(* 应用到具体函数 *)
MetaCoq Quote Definition original := (fun n => n + 0 + 0).
Definition optimized := optimize_plus original.
MetaCoq Unquote Definition better_fun := optimized.
3. 验证编译器
(* 简单的编译器示例 *)
Inductive expr :=
| EConst (n : nat)
| EPlus (e1 e2 : expr).
Fixpoint compile (e : expr) : term :=
match e with
| EConst n => tConstruct ... (* 编码自然数 *)
| EPlus e1 e2 =>
tApp (tConst "Nat.add" []) [compile e1; compile e2]
end.
(* 证明编译器正确性 *)
Lemma compile_correct : forall e,
eval (unquote (compile e)) = eval_expr e.
MetaCoq 的主要组件
AST 类型
(* MetaCoq 的核心 AST 类型 *)
Inductive term :=
| tRel (n : nat) (* de Bruijn 索引 *)
| tVar (id : ident) (* 变量 *)
| tEvar (ev : nat) (args : list term) (* 存在变量 *)
| tSort (s : sort) (* 类型宇宙 *)
| tProd (na : name) (ty : term) (body : term) (* 依赖乘积 *)
| tLambda (na : name) (ty : term) (body : term) (* 函数 *)
| tLetIn (na : name) (def : term) (def_ty : term) (body : term)
| tApp (f : term) (args : list term) (* 应用 *)
| tConst (c : kername) (u : list Level.t) (* 常量 *)
| tInd (ind : inductive) (u : list Level.t) (* 归纳类型 *)
| tConstruct (ind : inductive) (idx : nat) (u : list Level.t)
| tCase (ind_nbparams_relevance : inductive * nat * relevance)
(type_info : term)
(discr : term)
(branches : list (nat * term))
| tProj (proj : projection) (t : term)
| tFix (mfix : mfixpoint term) (idx : nat)
| tCoFix (mfix : mfixpoint term) (idx : nat)
| tInt (i : PrimInt63.int)
| tFloat (f : PrimFloat.float).
Template Monad
(* MetaCoq 的 Template Monad 操作 *)
Inductive TemplateMonad : Type -> Type :=
| tmReturn {A} : A -> TemplateMonad A
| tmBind {A B} : TemplateMonad A -> (A -> TemplateMonad B) ->
TemplateMonad B
| tmPrint : term -> TemplateMonad unit
| tmMsg : string -> TemplateMonad unit
| tmFail {A} : string -> TemplateMonad A
| tmEval : reductionStrategy -> term -> TemplateMonad term
| tmDefinition : ident -> option term -> term -> TemplateMonad unit
| tmAxiom : ident -> term -> TemplateMonad unit
| tmLemma : ident -> term -> TemplateMonad term
| tmFreshName : ident -> TemplateMonad ident
| tmQuote : forall {A}, A -> TemplateMonad term
| tmUnquote : term -> TemplateMonad typed_term
| tmUnquoteTyped : forall A, term -> TemplateMonad A
| tmExistingInstance : ident -> TemplateMonad unit
| tmInferInstance : option reductionStrategy -> term ->
TemplateMonad (option term).
MetaCoq vs Ltac2 对比
| 特性 | Ltac2 | MetaCoq |
|---|---|---|
| 主要用途 | 策略编程 | 元编程、代码生成 |
| 抽象层次 | 证明策略层 | AST 操作层 |
| 类型安全 | 静态类型 | 依赖类型 |
| 性能 | 编译执行 | 解释执行 |
| 学习曲线 | 中等 | 陡峭 |
| 适用场景 | 自动化证明 | 代码生成、程序变换 |
实际选择建议
- 使用 Ltac2:
- 需要编写复杂的证明自动化
- 关注性能和类型安全
- 想要更好的错误信息
- 使用 MetaCoq:
- 需要生成 Coq 代码
- 实现领域特定语言
- 开发验证工具
- 需要深度内省 Coq 项
- 结合使用:
(* MetaCoq 生成定理和证明框架 *) MetaCoq Run (generate_lemmas ...). (* Ltac2 完成具体证明 *) Ltac2 prove_generated_lemmas () := ...
实战示例:自动生成数据结构操作
From MetaCoq.Template Require Import All.
(* 为任意数据类型生成 fold 函数 *)
Definition generate_fold (ind_name : string) : TemplateMonad unit :=
(* 获取归纳类型信息 *)
ind_info <- tmQuoteInductive ind_name ;;
match ind_info with
| Build_mutual_inductive_body _ _ bodies _ =>
match bodies with
| [body] =>
(* 构造 fold 函数的类型 *)
let fold_type := ... in
(* 构造 fold 函数的定义 *)
let fold_body := ... in
(* 定义 fold 函数 *)
tmDefinition (ind_name ++ "_fold") (Some fold_type) fold_body
| _ => tmFail "Multiple mutually inductive types not supported"
end
end.
(* 使用示例 *)
Inductive tree (A : Type) :=
| Leaf : A -> tree A
| Node : tree A -> tree A -> tree A.
MetaCoq Run (generate_fold "tree").
(* 自动生成 tree_fold 函数 *)
总结
MetaCoq 为 Coq 提供了强大的元编程能力,使得:
- 可以在 Coq 内部分析和生成 Coq 代码
- 能够实现复杂的代码变换和优化
- 支持开发经过验证的工具和编译器
- 为领域特定语言提供实现基础
虽然学习曲线较陡,但 MetaCoq 在需要深度元编程的场景中是不可替代的工具。