Ltac と proof term 2
今度は算術でやってみました。
Coq < Goal forall n, n = 0 \/ 1 <= n. 1 subgoal ============================ forall n : nat, n = 0 \/ 1 <= n
こんなのを証明してみましょう。帰納法に決まってるので帰納法を使います。
Unnamed_thm < induction n. 2 subgoals ============================ 0 = 0 \/ 1 <= 0 subgoal 2 is: S n = 0 \/ 1 <= S n Unnamed_thm < Show Proof. LOC: Subgoals 1 -> 0 = 0 \/ 1 <= 0 2 -> forall n : nat, n = 0 \/ 1 <= n -> S n = 0 \/ 1 <= S n Proof: fun n : nat => nat_ind (fun n0 : nat => n0 = 0 \/ 1 <= n0) ?1 (fun (n0 : nat) (IHn : n0 = 0 \/ 1 <= n0) => ?2 n0 IHn) n
nat_ind に展開されるようです。nat を定義したときに自動的に生成される induction principle ですね。
型を確認してみます。
Unnamed_thm < Check nat_ind. nat_ind : forall P : nat -> Prop, P 0 -> (forall n : nat, P n -> P (S n)) -> forall n : nat, P n
上の proof term と見比べると,?1 が P 0 で IHn が P n 型の仮引数,?2 n0 IHn が P (S n0) 型となることがわかります。
最初の subgoal を片付けましょう。いかにも成り立ちそうなものが左側に見えています。
Unnamed_thm < left. 2 subgoals ============================ 0 = 0 subgoal 2 is: S n = 0 \/ 1 <= S n Unnamed_thm < Show Proof. LOC: Subgoals 1 -> 0 = 0 2 -> forall n : nat, n = 0 \/ 1 <= n -> S n = 0 \/ 1 <= S n Proof: fun n : nat => nat_ind (fun n0 : nat => n0 = 0 \/ 1 <= n0) (or_introl (1 <= 0) ?1) (fun (n0 : nat) (IHn : n0 = 0 \/ 1 <= n0) => ?2 n0 IHn) n
proof term に or_introl が現れました。
left はコンストラクタを二つもつ型が goal のとき,その一つ目を適用する tactic で,ここでは apply or_introl と同じです(たぶん)。
Unnamed_thm < reflexivity. 1 subgoal n : nat IHn : n = 0 \/ 1 <= n ============================ S n = 0 \/ 1 <= S n Unnamed_thm < Show Proof. LOC: Subgoals 1 -> forall n : nat, n = 0 \/ 1 <= n -> S n = 0 \/ 1 <= S n Proof: fun n : nat => nat_ind (fun n0 : nat => n0 = 0 \/ 1 <= n0) (or_introl (1 <= 0) (eq_refl 0)) (fun (n0 : nat) (IHn : n0 = 0 \/ 1 <= n0) => ?1 n0 IHn) n
reflexivity は apply refl_equal と等価なのでした。proof term では eq_refl になってますが,refl_equal は Notation で,eq_refl に展開されるように定義されています。
これで or_introl に渡される引数が完成しました。次はもうひとつの subgoal ですが,今度は左側がいかにも成り立ちそうにないので右側を狙います。
Unnamed_thm < right. 1 subgoal n : nat IHn : n = 0 \/ 1 <= n ============================ 1 <= S n Unnamed_thm < Show Proof. LOC: Subgoals 1 -> forall n : nat, n = 0 \/ 1 <= n -> 1 <= S n Proof: fun n : nat => nat_ind (fun n0 : nat => n0 = 0 \/ 1 <= n0) (or_introl (1 <= 0) (eq_refl 0)) (fun (n0 : nat) (IHn : n0 = 0 \/ 1 <= n0) => or_intror (S n0 = 0) (?1 n0 IHn)) n
さっきとだいたい同じですね。or の二つ目のコンストラクタは or_intror という名前だそうです。
場合分けします。
Unnamed_thm < destruct IHn. 2 subgoals n : nat H : n = 0 ============================ 1 <= S n subgoal 2 is: 1 <= S n Unnamed_thm < Show Proof. LOC: Subgoals 1 -> forall n : nat, n = 0 -> 1 <= S n 2 -> forall n : nat, 1 <= n -> 1 <= S n Proof: fun n : nat => nat_ind (fun n0 : nat => n0 = 0 \/ 1 <= n0) (or_introl (1 <= 0) (eq_refl 0)) (fun (n0 : nat) (IHn : n0 = 0 \/ 1 <= n0) => or_intror (S n0 = 0) match IHn with | or_introl H => ?1 n0 H | or_intror H => ?2 n0 H end) n
destruct するとやはり match 式が現れます。
n の値がわかっているので代入したい。
Unnamed_thm < subst. 2 subgoals ============================ 1 <= 1 subgoal 2 is: 1 <= S n Unnamed_thm < Show Proof. LOC: Subgoals 1 -> 1 <= 1 2 -> forall n : nat, 1 <= n -> 1 <= S n Proof: fun n : nat => nat_ind (fun n0 : nat => n0 = 0 \/ 1 <= n0) (or_introl (1 <= 0) (eq_refl 0)) (fun (n0 : nat) (IHn : n0 = 0 \/ 1 <= n0) => or_intror (S n0 = 0) match IHn with | or_introl H => eq_ind_r (fun n1 : nat => 1 <= S n1) ?1 H | or_intror H => ?2 n0 H end) n
eq_ind_r って何でしょう。eq_ind とは違うのか,と思って確認してみました。
Coq < Print eq_ind. eq_ind = fun (A : Type) (x : A) (P : A -> Prop) => eq_rect x P : forall (A : Type) (x : A) (P : A -> Prop), P x -> forall y : A, x = y -> P y Coq < Print eq_ind_r. eq_ind_r = fun (A : Type) (x : A) (P : A -> Prop) (H : P x) (y : A) (H0 : y = x) => eq_ind x (fun y0 : A => P y0) H y (eq_sym H0) : forall (A : Type) (x : A) (P : A -> Prop), P x -> forall y : A, y = x -> P y
だそうです。等式の左右が違うのか。
ところで今は変数への代入を使いましたが,より一般的な書き換えを行う tactic として rewrite があります。こっちを使うと proof term はどうなるんでしょう。試してみましょう。
Unnamed_thm < Undo. 2 subgoals n : nat H : n = 0 ============================ 1 <= S n subgoal 2 is: 1 <= S n Unnamed_thm < rewrite H. 2 subgoals n : nat H : n = 0 ============================ 1 <= 1 subgoal 2 is: 1 <= S n Unnamed_thm < Show Proof. LOC: Subgoals 1 -> forall n : nat, n = 0 -> 1 <= 1 2 -> forall n : nat, 1 <= n -> 1 <= S n Proof: fun n : nat => nat_ind (fun n0 : nat => n0 = 0 \/ 1 <= n0) (or_introl (1 <= 0) (eq_refl 0)) (fun (n0 : nat) (IHn : n0 = 0 \/ 1 <= n0) => or_intror (S n0 = 0) match IHn with | or_introl H => eq_ind_r (fun n1 : nat => 1 <= S n1) (?1 n0 H) H | or_intror H => ?2 n0 H end) n
なんかちょっとだけ長くなりました。何が違うかよく見てみると,?1 に引数が渡ってますね。
subst は代入した後で clear してしまいますが,rewrite はそんなことしないので,残った n0 と H が ?1 への引数として (実際の意図としては,?1 の中で使われる可能性がある変数として?) 渡されることになるのですね。
不等式の証明をすることになるのですが,まずは不等号の定義を見てみましょう。実体は le という inductive predicate です。
Unnamed_thm < Print le. Inductive le (n : nat) : nat -> Prop := le_n : n <= n | le_S : forall m : nat, n <= m -> n <= S m
le_n が反射性,le_S が推移性もどきの形をしていることがわかります。le のコンストラクタの与え方は他にも考えられますが (0 <= n | n <= m -> S n <= S m とか),運のいいことに Coq で採用されている上の定義が今の goal にはちょうどよい形をしています。
Unnamed_thm < constructor. 1 subgoal n : nat H : 1 <= n ============================ 1 <= S n Unnamed_thm < Show Proof. LOC: Subgoals 1 -> forall n : nat, 1 <= n -> 1 <= S n Proof: fun n : nat => nat_ind (fun n0 : nat => n0 = 0 \/ 1 <= n0) (or_introl (1 <= 0) (eq_refl 0)) (fun (n0 : nat) (IHn : n0 = 0 \/ 1 <= n0) => or_intror (S n0 = 0) match IHn with | or_introl H => eq_ind_r (fun n1 : nat => 1 <= S n1) (le_n 1) H | or_intror H => ?1 n0 H end) n
一つ目のコンストラクタ le_n の型が goal にマッチするので,これが apply されました。マッチングで機械的に求まる引数の 1 は自動的に推論されて,subgoal が完成しました。eq_ind_r の第二引数にこの式が現れています。
次の goal は le_S の型にマッチします。le_S の引数が subgoal として残りますが,これは local context にあるものがそのまま使えます。
Unnamed_thm < constructor. 1 subgoal n : nat H : 1 <= n ============================ 1 <= n Unnamed_thm < assumption. Proof completed. Unnamed_thm < Qed. induction n. left. reflexivity. right. destruct IHn. rewrite H. constructor. constructor. assumption. Unnamed_thm is defined Coq < Print Unnamed_thm. Unnamed_thm = fun n : nat => nat_ind (fun n0 : nat => n0 = 0 \/ 1 <= n0) (or_introl (1 <= 0) (eq_refl 0)) (fun (n0 : nat) (IHn : n0 = 0 \/ 1 <= n0) => or_intror (S n0 = 0) match IHn with | or_introl H => eq_ind_r (fun n1 : nat => 1 <= S n1) (le_n 1) H | or_intror H => le_S 1 n0 H end) n : forall n : nat, n = 0 \/ 1 <= n
というわけで完成しました。最後の式を見ると ?1 n0 H だった部分の n0 H がそのまま残ってるようで一瞬不思議に思いましたが,偶然そういう形になっただけです(たぶん)。
subst や rewrite が eq_ind_r を使った proof term を生成する (rewrite <- を使うと,r のつかない eq_ind になります) ということには,ただ tactic 書いてるだけだと気付きにくいかな,と思います。