Lean の仕組み — 来歴と、検証が働く仕組み
「証明を機械に確かめさせる」という考えは Lean より半世紀ほど古く、Lean はその系統の新しい一本です。ここでは、その考えがどこから来たか、Lean 自身がどう作られているか、そして検査のあいだに実際に何が起きているかを順に見ます。最後の節は、Lean を見たことがなくても読めるように書いてあります。
機械に証明を確かめさせる、という考えの来歴
この考えは、離れた場所から来た二本の筋が合流したものです。
一本目は、証明を機械で扱える対象として書けること。関数に型を付ける仕組みと、含意「A ならば B」を証明する仕組みが同じ形をしている——この対応は Curry–Howard 対応と呼ばれます。名前の一方は、1934 年の Curry の論文「組み合わせ論理における関数性」に由来します。この対応があると、証明は読んで納得するものではなく、型の合った項という具体的な書き物になります。
二本目は、確かめる側は小さいほうがよいこと。1972 年、Milner は Stanford で LCF という証明検査系を実装しました。LCF の系統では、証明を組み立てる道具(tactic)はメタ言語で自由に書ける一方、出来上がったものを認めるかどうかは小さな中核だけが決めます。道具がいくら増えても、信頼しなければならない部分は増えません。
この二本を最初に本格的に合流させたのが、de Bruijn の Automath です。数学の証明を書き下し、機械に検査させるための言語として設計され、1970 年に報告されました。数学の証明を機械検査にかけるための、最初の全面的な試みです。1970 年代には、Jutting が Landau の『解析学の基礎』をこの言語で検査しています。
ここから出てきた設計方針が、のちに de Bruijn の基準と呼ばれるものです。証明を「検査の途中経過」としてではなく、それ自体で完結した項として残し、小さな独立の検査器で読み直せるようにする。証明支援系を比較する古典的な一覧表では、これが「小さな証明核(証明対象を持つこと)」という一項目として立てられています。Lean の公式文書も同じことを述べています——「証明項は定理の真理性の十分な証拠であり、独立した検証にかけられる」。
型理論の側では、Martin-Löf の直観主義型理論(1975 年)が数学の基礎を型の上に置き直そうとし、それを踏まえて Coquand が 1985 年に Calculus of Constructions の最初の版を出しました。最初の実装は 1984 年に Huet と Coquand が始めたもので、その中核は Constructive Engine と呼ばれる型検査器でした。1989 年に Coquand と Paulin が帰納的定義を加え、Calculus of Inductive Constructions になります。これが Coq の論理であり、そして Lean の論理の土台でもあります。
この系統で実際に検査された大きな定理を、年表に混ぜて並べます。
| 年 | できごと |
|---|---|
| 1934 | Curry「組み合わせ論理における関数性」。Curry–Howard の名の一方 |
| 1970 | de Bruijn が Automath を報告。証明を機械検査にかける最初の全面的な試み |
| 1972 | Milner が Stanford で LCF を実装(「計算可能関数の論理——機械実装の記述」) |
| 1970 年代 | Jutting が Landau の『解析学の基礎』を Automath で検査 |
| 1975 | Martin-Löf「直観主義型理論——述語的部分」 |
| 1984 | Huet と Coquand が Calculus of Constructions の実装を始める。中核は Constructive Engine と呼ばれる型検査器 |
| 1985 | Coquand が Calculus of Constructions の最初の版を示す |
| 1988 | Coquand と Huet が Calculus of Constructions の論文を出す |
| 1989 | Coquand と Paulin が帰納的定義を加え、Calculus of Inductive Constructions へ |
| 2005 頃 | 四色定理の証明が Coq で検査されたことが報告される |
| 2013 | Feit–Thompson の定理(奇数位数定理)を Coq で検査した論文が出る。6 年の共同作業 |
| 2017 | Kepler 予想の証明を HOL Light と Isabelle で検査した論文が出る(Flyspeck) |
Lean はこの表の続きに位置します。土台の型理論は Coq と同系統で、設計方針は LCF 系統の「小さな中核」を引き継いでいます。
Lean の来歴
Lean は 2013 年、Microsoft Research の de Moura が始めました。最初のコミットは同年 7 月です。以後、記法と実装は何度も入れ替わりました。
最初の公開版は 0.1(2014 年)です。版 0.2 は標準の論理に加えてホモトピー型理論のモードを持っており、Lean 2 という名の別リポジトリに凍結されています。Lean 3(2017 年 1 月)で利用者が増え、同年 7 月に数学ライブラリ mathlib が作られます。2018 年 4 月に Lean 4 の開発が始まり、2023 年 9 月に正式版が出ました。
Lean 4 の特徴のひとつは、Lean 自身で書き直されていることです。parser も elaborator も tactic も Lean で書かれており、利用者がそれらを Lean のまま拡張できます。前の版で C++ を書かないと足せなかった部分が、ライブラリと同じ言語で書けるようになりました。
mathlib のほうは、個人の作業ではなく共同体の作業として育ちました。設計の記録は 2019 年の論文にまとめられており、いまでは 100 万行を超える規模になっています。Lean 3 から Lean 4 への移植は 2023 年 7 月に完了しました。
同じ 2023 年 7 月、Lean の開発体制そのものが変わります。de Moura と Ullrich が Lean FRO(Focused Research Organization)を設立し、Lean は非営利組織の下で開発される道具になりました。
Lean を使った形式化で、規模の目印になるものを二つ挙げます。Liquid Tensor Experiment は、2020 年 12 月に「この定理を形式化できるか」という挑戦として出され、第一目標の証明が 2021 年 5 月 28 日に予告され、2022 年 7 月 14 日に全体が完了しました。多項式 Freiman–Ruzsa 予想のほうは、証明の論文が 2023 年 11 月に出た直後に形式化のプロジェクトが立ち上がり、第一段階は完了しています。
| 年月 | できごと |
|---|---|
| 2013-07 | Lean のリポジトリに最初のコミット |
| 2014-06 | Lean 0.1 公開 |
| 2015-01 | Lean を使った最初の大学の講義(Carnegie Mellon University) |
| 2015-08 | CADE-25 で Lean の系統記述(system description)が出る |
| 2017-01 | Lean 3.0 公開 |
| 2017-07 | mathlib(Lean 3 用)が作られる |
| 2018-04 | Lean 4 の開発開始 |
| 2019-10 | mathlib の設計を述べた論文が出る |
| 2020-12 | Liquid Tensor Experiment が挑戦として出される |
| 2021-05 | mathlib4 のリポジトリが作られる |
| 2022-07 | Liquid Tensor Experiment 完了 |
| 2023-07 | Lean FRO 設立。mathlib の Lean 4 への移植完了 |
| 2023-09 | Lean 4.0 正式公開 |
| 2023-11 | 多項式 Freiman–Ruzsa 予想の形式化プロジェクト開始 |
| 2023-12 | フェルマーの最終定理の形式化プロジェクト開始 |
| 2025-01 | mathlib4 への貢献が 2 万件を超える |
Lean はどう組み立てられているか
利用者が書いた文字列が定理として認められるまでに、Lean は次の段を通ります。
| 段 | すること |
|---|---|
| parser | 文字の列を構文木にする。利用者が新しい記法を足せるので、構文木の型はとても一般的 |
| macro 展開 | 書きやすくするための糖衣構文を、もっと基本的な構文に置き換える |
| elaborator | 利用者向けの構文を、核となる型理論の項に変える。省略された引数を補い、型クラスの実例を探し、by の中の tactic を走らせる |
| kernel | elaborator が出した項が、型理論の規則に従っているかを検査する |
| compiler | elaborate 済みの Lean のコードを、実行できるものに変える |
ここで効いているのは、核となる型理論が、利用者が書く言語よりずっと単純であることです。公式文書の言い方では、「この核の理論はずっと単純であり、それによって信頼される kernel を非常に小さく保てる」。elaborator がどれだけ賢くなっても、kernel が読むのは単純な言語のままです。
compiler は検証の列に入っていません。実行できるプログラムを作るための別系統で、定理が正しいかどうかには関わりません。例外は native_decide で、そのときだけ compiler の出した結果が公理として論理の側に入ります(01)。
核の型理論のほうも、名前だけ押さえておきます。
依存型
型が、値に依存して決まる仕組みです。「長さ n のリストの型」のように、n を受け取ってから型が定まる。命題を型として書けるのは、この仕組みがあるからです。
帰納型
「こういう作り方でできるものが全部」と宣言して型を作る仕組みです。自然数なら「0 と、何かの次の数」。この宣言から、場合分けと帰納法の原理が機械的に生成されます。
宇宙
型そのものも項なので、「型の型」が要ります。これを段に分けたものが宇宙で、各段には水準(自然数)が付きます。どの宇宙も一つ上の宇宙の要素であり、ある宇宙の型が量化できるのは、命題を除いて、それより小さい宇宙の型だけです。だから「すべての型についての型」を素朴に作ることはできません。
定義的等しさ
「計算すれば同じものになる」という関係です。関数に引数を当てる(β)・定義された名前を中身に置き換える(δ)・帰納型の場合分けを進める(ι)・let で置いた名前を値に置き換える(ζ)。このほか、商型の還元と、関数・単一構成子の型についての η 同値、それに証明の無関係性(同じ命題の証明はどれも等しい)が含まれます。kernel が「両辺が同じ」と言うときは、この関係のことです。
最後にひとつ、区別を。mathlib は kernel の一部ではありません。ライブラリ——つまり Lean で書かれた定義と定理の集まりであって、立場としては利用者が書くファイルと同じです。mathlib に誤った証明が混ざったとしても、それが kernel を通っていることに変わりはなく、逆に mathlib を全部外しても kernel は同じように働きます。
検証の仕組み — 初心者の歩幅で
ここがこのページの中心です。Lean を触ったことがなくても追えるように、たとえ話から始めて、実物を一歩ずつ見ていきます。
窓口のたとえ
証明を記入済みの書類だと思ってください。命題は書類の様式で、証明はその様式を埋めた一枚です。
kernel は記入の規則だけを知っている窓口です。窓口は、書類が誰の手で書かれたか、手書きか印字か、下書きを何枚捨てたかを見ません。見るのは、様式の欄が規則どおりに埋まっているかだけ。埋まっていれば受け取り、埋まっていなければ突き返します。
この「見ないこと」が要点です。証明を組み立てる道具(tactic)も、探索のプログラムも、人の勘も、窓口の外側にあります。窓口が見るのは、最後に提出された一枚だけです。
一歩目 — 計算で閉じる
いちばん小さい書類を見ます。
theorem two_add_two : 2 + 2 = 4 := rfl
: の後が様式(命題)、:= の後が記入(証明)です。rfl は「両辺を計算すると同じものになる」という一語の記入で、これを受け取った kernel は、実際に両辺を計算します。
計算の規則は、自然数の加法の定義そのものです。Lean にはこう入っています。
#check @Nat.add_zero
#check @Nat.add_succ
Nat.add_zero : ∀ (n : ℕ), n + 0 = n
Nat.add_succ : ∀ (n m : ℕ), n + m.succ = (n + m).succ
「何かに 0 を足すとそれ自身」と「何かに『次の数』を足すと、足した結果の次の数」。この二本しかありません。succ は「次の数」を作る操作で、2 + 2 の右の 2 は 0 の次の次です。だから kernel の手は三手で済みます。
example : 2 + 2 = Nat.succ (2 + 1) := rfl
example : 2 + 1 = Nat.succ (2 + 0) := rfl
example : 2 + 0 = 2 := rfl
example : Nat.succ (Nat.succ 2) = 4 := rfl
四行とも検査を通ります。一行目と二行目が Nat.add_succ、三行目が Nat.add_zero、四行目は「4 という書き方が 2 の次の次を指している」ことです。この四つをつなげると 2 + 2 から 4 に着きます。最初の定理で kernel がしているのは、これと同じことです。
rfl を受け取った kernel は、定義の規則を何手か使って両辺を同じ形にする。二歩目 — 「かつ」の証明は組
計算で閉じない命題も、同じ枠に収まります。「A かつ B」の様式を見ます。
#check @And.intro
@And.intro : ∀ {a b : Prop}, a → b → a ∧ b
読み方は「命題 a と b について、a の証明と b の証明を受け取ると、a ∧ b の証明になる」。つまり a ∧ b の証明とは、a の証明と b の証明の組です。
example (p q : Prop) (hp : p) (hq : q) : p ∧ q := ⟨hp, hq⟩
(hp : p) は「p の証明を hp という名前で受け取る」という宣言、⟨ ⟩ は組を作る記法です。片方に間違ったものを入れると、窓口は理由を添えて突き返します。
example (p q : Prop) (hp : p) : p ∧ q := ⟨hp, hp⟩
error: Application type mismatch: The last
hp
argument has type
p
but is expected to have type
q
in the application
⟨hp, hp⟩
「二つ目の欄には q の証明が来るはずなのに、p の証明が入っている」。書類の不備の指摘です。rfl の側でも同じです。
example : 2 + 2 = 5 := rfl
error: Type mismatch
rfl
has type
?m.16 = ?m.16
but is expected to have type
2 + 2 = 5
この二つの指摘を出しているのは、正確には kernel ではなく elaborator です。elaborator は項を組み立てながら自分でも型を見ているので、不備はたいてい kernel に届く前に見つかります。kernel は同じ規則で、出来上がった項をもう一度通す最後の関門です。
三歩目 — 「ならば」の証明は関数
「A ならば B」の記入は、A の証明を受け取って B の証明を返す関数です。
example (p : Prop) : p → p := fun hp => hp
example (p q : Prop) (h : p → q) (hp : p) : q := h hp
一行目は「p ならば p」——受け取ったものをそのまま返す関数です。二行目は「p ならば q」の証明 h と p の証明 hp があれば q の証明が得られる、を h hp(関数に引数を当てる)と書いています。論理の一歩が、関数の呼び出しと同じ形をしている。これが Curry–Howard 対応の手触りです。
四歩目 — tactic で書いても、最後は項になる
実際の証明は、こんなに短くは書けません。そこで by の後に tactic を並べ、項を代わりに組み立ててもらいます。
theorem and_swap (p q : Prop) (h : p ∧ q) : q ∧ p := by
constructor
· exact h.2
· exact h.1
constructor は「q ∧ p を作るには q と p が要る」と目標を二つに割る tactic、exact はその欄に手持ちのものを入れる tactic です。組み立てた結果は #print で見えます。
#print and_swap
theorem and_swap : ∀ (p q : Prop), p ∧ q → q ∧ p :=
fun p q h => ⟨h.right, h.left⟩
四行の tactic が消えて、一行の項が残りました。しかも中身は、二歩目と三歩目で見た形そのものです——引数を受け取る関数(fun)と、二つの証明の組(⟨ ⟩)。tactic はこの項を書くための道具であって、kernel が受け取るのはこの一行だけです。
なぜ信じられるのか
ここまでを踏まえると、信じなければならないものと、信じなくてよいものが分かれます。
それでも「kernel の実装」は残ります。そこで、出来上がった証明を読み直す手立てが用意されています。Lean の公式文書は、確かめ方を強さの順に並べています。
| 確かめ方 | 何が分かるか |
|---|---|
| エディタの青い二重チェック | その定理の言明が elaborate され、kernel が証明を受け入れたこと。日常の作業ではこれで足りる |
#print axioms | 依拠している公理の一覧。sorry の穴、勝手に足した公理、native_decide はここに出る |
leanchecker | ビルドで作られた .olean の中の宣言を読み直し、kernel に通し直す。Lean の道具立てに同梱されている |
| 照合器と外部の検査器 | 証明項を書き出し、Lean の kernel と別の実装の検査器の両方で通し、さらに証明された言明が手元の言明と一致するかを照合する |
三つ目の leanchecker は Lean の kernel をもう一度走らせる道具なので、kernel 自体の不具合は捕まえられません。捕まえるのは、kernel の状態を扱う周辺の不具合や、メタプログラムが検査を迂回して宣言を足す類のことです。
四つ目が、いま用意されているいちばん強い形です。証明を隔離した環境で組み立て、証明項を書き出し、その外側で——つまり証明の側のプログラムが手を出せない場所で——Lean の kernel と、独立に書かれた別の検査器の両方に通します。公式文書が名を挙げている外部検査器は Rust で独立に実装されたもので、ほかにも検査器を並べて比べる場が作られています。これが、de Bruijn の基準が実際に払っている配当です。証明が項として残っているから、別の検査器で読み直せる。
それでも前提は残ります。公式文書はそれも列挙しています——Lean の論理そのものが健全であること、書き出しと照合の配管が正しいこと、隔離環境が破られないこと、用いたすべての検査器に同時に効く不具合が無いこと、そして言明に人の誤りや誤解を招く書き方が無いこと。
それでも間違えうる所
言明が、言いたいことと違う
いちばん多い形です。定義を弱く取る、量化の順を入れ替える、仮定をひとつ足す。どれも検査は通り、証明された内容だけが別のものになります。機械は「落とした条件」を教えません。実例は 08、受け取った側の確かめ方は 09 に。
公理を足した
Lean では axiom で新しい公理を宣言できます。足せば何でも証明できてしまうので、標準の三つ以外が出ていないことを #print axioms で見ます(01)。
sorry の穴
書きかけの箇所を認めさせる印です。警告は出ますが検査は通り、#print axioms に sorryAx として現れます。エラーだけを見ていると見落とします(01)。
native_decide
判定を機械語にコンパイルして走らせ、その結果を公理として受け入れます。信頼の範囲が compiler と、実行時の差し替え指定が付いた定義すべてに広がります。このサイトでは使いません(01)。
kernel の不具合
kernel は小さいとはいえプログラムです。実際に、kernel を別の言語で書き直す試みから型検査の不具合が見つかり、報告された当日に対処された例があります(let 束縛の型に現れる変数の扱いについてのもの)。この種の不具合は、上の表の四つ目——実装の違う検査器を並べる形——でこそ捕まります。
誰が何を保証するか
| もの | 保証すること | 保証しないこと |
|---|---|---|
| kernel | 提出された項が、定義と公理から型の規則に従って組まれていること | その言明が言いたかったことであること |
| tactic・自動化 | 何も。項を作るだけ | ——(失敗しても命題が偽とは限らない) |
#print axioms | 依拠する公理の一覧が漏れなく出ること | その公理が妥当かどうか |
| 外部の検査器 | Lean の kernel の実装に頼らずに項を読み直せること | Lean の論理そのものの健全性 |
| 書いた人・読む人 | —— | 言明と定義が意図と合っているかは、人が確かめる仕事のまま |
機械検査が動かしたのは、確かめる対象と確かめる手段の境目です。「証明が正しいか」は機械に渡せる。「その定理が言いたかったことか」は渡せない。後者が残ることは弱点ではなく、前者が片付いたおかげで前面に出てきた仕事です。
文献・出典
| 節 | もの | 出典 |
|---|---|---|
| 01 | Curry–Howard 対応の名と、Automath がそれを用いたこと/LCF と tactic とメタ言語/CoC と CIC の年と人 | Rocq(旧 Coq)参考マニュアル「Early history of Coq」 |
| 01 | Curry「Functionality in Combinatory Logic」(1934) | Proceedings of the National Academy of Sciences, 1934 年 11 月 |
| 01 | de Bruijn「The mathematical language AUTOMATH, its usage, and some of its extensions」(1970) | Symposium on Automatic Demonstration, Lecture Notes in Mathematics |
| 01 | Milner「Logic for Computable Functions: Description of a Machine Implementation」(1972 年 5 月) | Stanford Artificial Intelligence Project, Memo AIM-169 / STAN-CS-72-288 |
| 01 | Martin-Löf「An Intuitionistic Theory of Types: Predicative Part」(1975) | Logic Colloquium '73 |
| 01 | Coquand・Huet「The calculus of constructions」(1988) | Information and Computation |
| 01 | de Bruijn の基準という呼び名と、「小さな証明核」という項目立て | Wiedijk「The Seventeen Provers of the World」の比較表と脚注 |
| 01 | 四色定理の Coq による検査 | Gonthier「A computer-checked proof of the Four Colour Theorem」(Microsoft Research Cambridge) |
| 01 | Feit–Thompson の定理、6 年の共同作業 | Gonthier ほか「A Machine-Checked Proof of the Odd Order Theorem」(ITP 2013) |
| 01 | Kepler 予想を HOL Light と Isabelle で検査 | Hales ほか「A formal proof of the Kepler conjecture」Forum of Mathematics, Pi(2017) |
| 02 | Lean の年表(最初のコミットから現在まで) | Lean FRO「A Brief History of Lean」 |
| 02 | 2013 年開始・小さな信頼される kernel・依存型理論 | de Moura ほか「The Lean Theorem Prover (System Description)」(CADE-25, 2015) |
| 02 | Lean 4 が Lean 自身での書き直しであること・parser と elaborator を利用者が拡張できること | de Moura・Ullrich「The Lean 4 Theorem Prover and Programming Language」(CADE 28, 2021) |
| 02 | Lean 0.2 が標準モードとホモトピー型理論モードを持つこと | Lean 2 のリポジトリ(凍結済み)の記述 |
| 02 | mathlib の設計と共同体の組織 | the mathlib Community「The lean mathematical library」(CPP 2020) |
| 02 | mathlib が 100 万行を超えること | Lean 公式サイトの紹介 |
| 02 | Liquid Tensor Experiment の日付(2020-12 の挑戦・2021-05-28 の予告・2022-07-14 の完了) | 同プロジェクトのリポジトリの記述 |
| 02 | 多項式 Freiman–Ruzsa 予想の証明の論文と、形式化の第一段階の完了 | Gowers・Green・Manners・Tao「On a conjecture of Marton」/同プロジェクトのリポジトリの記述 |
| 03 | parser から compiler までの五段と、それぞれの役割/「核の理論はずっと単純であり、それによって kernel を非常に小さく保てる」 | Lean 言語参考マニュアル「Elaboration and Compilation」 |
| 03 | 依存型・帰納型・宇宙・商/定義的等しさ(β・δ・ι・ζ・商の還元・η・証明の無関係性)/「証明項は定理の真理性の十分な証拠であり、独立した検証にかけられる」 | Lean 言語参考マニュアル「The Type System」 |
| 04 | 確かめ方の四段とそれぞれが守る範囲/残る前提の一覧/外部検査器が Rust で独立に実装されていること | Lean 言語参考マニュアル「Validating a Lean Proof」 |
| 04 | leanchecker が Lean の道具立てに同梱されていること | 旧 lean4checker のリポジトリの告知と、手元の Lean 4.33.1 の同梱物 |
| 04 | native_decide が compiler と差し替え指定付きの定義に信頼を広げること | Lean 言語参考マニュアル(decide の説明) |
| 04 | kernel の型検査の不具合が見つかり同日に修正された例(let 束縛の型の変数の扱い) | Lean の課題票 10475/kernel を別言語で書き直す試みが挙げている不具合一覧 |
| 04 | このページのコード片と出力 | Lean 4.33.1(leanprover/lean4:v4.33.1)+ mathlib。すべて手元で検査し、出力をそのまま写した。手順は 02 |
Lean の初歩は 01 Lean とは何か、手元で動かすところは 02 使い方の入口、語の意味は 12 用語集。