《面向软件工程师的 Lean 证明剖析》
Anatomy of a Lean proof for software engineers

原始链接: https://agostbiro.net/posts/2026-10-anatomy-of-a-lean-proof/

本文分步介绍了一个教材定理的 Lean 4 形式化证明:三行二进制位构成的语言中,若最下面一行等于上面两行之和,则该语言是正则语言。由于 DFA 从左向右读取符号,而二进制加法从最低有效位开始处理更为方便,因此证明构造了一个识别反转语言的 DFA。 该 DFA 有三个状态,分别跟踪进位为 0、进位为 1,以及一个死状态。进位为 0 的状态既是初始状态,也是接受状态,从而防止溢出。 核心证明建立了一个运行不变式,将自动机的执行与任意长度输入上的二进制加法联系起来。归纳过程使用若干辅助引理来拆分运行、将一次转移转化为加法器方程,并在最低有效位处分解整个算术方程。布尔情况通过 `decide` 或 `simp` 处理,线性算术则由 `omega` 求解。 最后,利用 Mathlib 中关于语言反转的封闭性定理,将正则性传递回原语言。本文还指出,形式化验证能够揭示规范中原本未明确写出的要求,同时警告不应盲目信任机器生成的证明,而应确保这些证明仍然易于理解。

一场关于《面向软件工程师的 Lean 证明剖析》的 Hacker News 讨论,焦点在于非常大的、由机器检查的 Lean 证明是否真正值得信任。一位评论者认为,即使代码经过了形式化验证,语义错误仍可能藏在其中,因此这类成果尽管 Lean 的运行时十分稳健,却依然异常脆弱。另一位评论者则指出,文章缺少目录。 不少人建议,在开始学习 Lean 之前,先掌握证明论、直觉主义逻辑、柯里–霍华德对应、不完备性和紧致性。 其他人则好奇,如果不验证所有内容,Lean 是否仍然有用——例如,把它当作描述模块行为及其性质的一种精确规格语言,再辅以基于属性的测试。有人回答称,可以,并将这种方法与 Hoare 逻辑和 Concrete Semantics 框架进行了比较,不过 Lean 的程序验证库还不如其数学生态系统成熟。 最后的讨论较为怀疑:一位读者认为 Lean 难以理解,另一位则认为它不过是一项学术上的新鲜事物,第三位则回应说,面对陌生技术,在批评之前应当先努力理解它。
相关文章

原文
[Contents]

Intro

I recently worked through a problem from a theory of computation textbook that asked me to prove a property of a language using finite automata. The informal proof is a simple constructive proof where you build an automaton and show that it recognizes the language. This is kind of similar to program verification, so I thought it’d be interesting to see what it takes to formalize the proof. Lean is a good choice for this, because its Mathlib has all the theorems for the problem.

After finishing the formal proof, I decided to write it up, because I think it provides software engineers with good insight into what it takes to formally prove properties of a system.

I tried to make this post accessible. If you’re comfortable with a modern statically typed programming language (such as TypeScript or Rust), binary arithmetic, basic propositional logic, and inductive proofs, you should be able to follow along.

Background: DFAs & Regular Languages

Feel free to skip to the next section if you’re comfortable with DFAs and regular languages.

Finite automata provide a theoretical model of computation with fixed memory. Besides theory, finite automata also have important practical applications. For example, finite automata are relevant for parsers and regular expressions, where a bug once took a significant portion of the internet down.

Deterministic Finite Automaton (DFA)

A deterministic finite automaton (DFA) is a machine with a fixed, finite set of states that reads its input one symbol at a time, left to right. With each symbol, it updates its state using a deterministic transition function. After the last symbol, the machine either sits in an accepting state (input is accepted) or not (input is rejected).

If you’ve ever written a simple regular expression like -?[0-9]+, then you’ve constructed a DFA. This regex matches integer literals like 12 and -123 and the corresponding DFA looks like this (the arrows are annotated with the symbols that lead to the next state):

DFA for a decimal integer literal, including the dead statestartsigndigitsdead-0-90-90-9otherotherotherany

This DFA has four states:

  • Start: this is where we start before processing the first character. Since the start state is not an accepting state, we reject the empty string.
  • Sign: we move to the sign state when we encounter the - character in the start state. We can skip the sign state and jump directly to digits from start, since the sign character is optional (-?). If we’re in this state at the end of the string, then we reject the string.
  • Digits: we move from start or sign to digits when we encounter a digit character ([0-9]). If we’re in the digits state and encounter a digit character again, then we stay in the digits state. The digits state is the only accepting state of the DFA. If we’re in this state after we’ve processed the input string, then the DFA accepts the string.
  • Dead: we get into this state if we encounter any other character than a digit (unless it’s a negative sign at the start). If we’re in the dead state at the end of the string, then the DFA rejects the string. Once we’re in the dead state, we stay in it, so the dead state in this DFA is a sink.

The set of input symbols to the machine is defined by the set Σ\Sigma. In our regex example, Σ={−,0,1,2,…,9}\Sigma = \left\{-, 0, 1, 2, \ldots, 9\right\}

Regular Languages

A language is just a set of strings, also called words, and a language is called regular if some DFA accepts exactly the strings in it. Recognizing regular languages is the class of decision problems solvable with an amount of memory that does not grow with the input.

Regular languages have useful closure properties: the union and intersection of two regular languages are regular, and so are the complement and (important for us) the reversal of a regular language.

The standard way to prove that a language is regular is to build a DFA and show that it accepts exactly that language.

We can describe a language AA with set-builder notation:

A={ w∈Σ∗∣P(w) }A = \bigl\{\, w \in \Sigma^{*} \bigm| P(w) \,\bigr\}
联系我们 contact @ memedata.com