コンテンツにスキップ

by と tactic モードの基本

by 以降に tactic を並べる書き方を tactic モードと呼びます. Lean は現在のゴールを持っていて,各 tactic はそのゴールを変形したり閉じたりします. VS Code の Infoview では,上側にローカルコンテキスト,下側に現在のゴールが表示されます. tactic を 1 行実行するたびに,Lean は未解決のゴールを更新します. すべてのゴールが閉じると,by ... 全体が証明項になります.

ここでいう tactic は,Lean のカーネルが直接持っている推論規則そのものではありません. tactic は,elaboration の途中で実行され,ゴールを操作しながら証明項を構成するためのプログラムです. 完成した証明項は,最後に Lean のカーネルによって型検査されます. この章では tactic の使い方に集中し,elaboration やカーネルの詳しい仕組みは Chapter 06 で扱います.

by は,example ... := bytheorem ... := by の直後だけに現れる特別な記号ではありません. Lean がある場所で型 T の項を期待しているとき,そこに

by
  ...

と書くと,Lean は「型 T の項を tactic で作る」モードに入ります. 特に T : Prop のとき,これは「その場所で必要な証明を tactic で作る」という意味です.

一番直接的な tactic は exact です. ゴールと同じ型をもつ項や証明をすでに持っているとき,それを exact に渡します. 厳密には,ゴールの型と exact に渡す項の型が定義的に等しいときに使えます.

example (P : Prop) (hP : P) : P := by
  exact hP

assumption は,ローカルコンテキストの中からゴールと一致する仮定を探して使います.

example (P Q : Prop) (hP : P) (_hQ : Q) : P := by
  assumption

rfl は,両辺が定義を展開すれば同じになる等式を閉じます. 「計算すれば同じ」という等式によく使います.

example : (fun n : Nat => n + 1) 2 = 3 := by
  rfl

Lean の等式 a = bEq a b という型です. rflEq.refl,つまり反射律を使う tactic です. 反射律は本来 a = a を証明するものですが,Lean は両辺を計算・定義展開して同じ式になる場合にも rfl を受け入れます. このように,計算や定義展開だけで同じと判定されることを「定義的に等しい(definitionally equal)」と言います.

#check Eq
#check Eq.refl

example : Eq 3 3 := by
  rfl

example : (fun n : Nat => n + 1) 2 = 3 := by
  exact Eq.refl 3

一方,数学的には正しい等式でも,定義的に等しくない場合は rfl では閉じません. たとえば a + b = b + a は可換性の定理 Nat.add_comm を使って証明します. このような「命題として証明された等式」は,rw などで書き換えに使います.

example (a b : Nat) : a + b = b + a := by
  exact Nat.add_comm a b

def Positive (n : Nat) : Prop :=
  0 < n

show は,現在のゴールを明示的に書く tactic です. 証明を読む人に「いま何を示しているか」を示すのに使えます. show で書いた命題は,現在のゴールと定義的に同じでなければなりません.

example (n : Nat) : n + 0 = n := by
  show n + 0 = n
  exact Nat.add_zero n

change は,現在のゴールを定義的に等しい別の表示へ置き換える tactic です. 次の例では,Positive 3 を定義通り 0 < 3 に変えています. 論理的に同値な任意の命題へ変えられるわけではありません.

example : Positive 3 := by
  change 0 < 3
  decide

unfold も定義を展開します. 特定の名前を明示的に展開したいときに使います.

def double (n : Nat) : Nat :=
  n + n

example : double 4 = 8 := by
  unfold double
  rfl

dsimp は definitional simplification の略で,定義を展開して計算で簡約できる部分を整理します. MiL では,関数を値に適用した式や,定義の中に隠れている全称量化を見やすくする場面で使われます. simp と違って,一般の補題による書き換えではなく,主に定義展開と計算による簡約を行います.

def FnUb (f : Nat  Nat) (a : Nat) : Prop :=
   x, f x  a

example (f : Nat  Nat) (a : Nat) (h : FnUb f a) : f 0  a := by
  dsimp [FnUb] at h
  exact h 0