Program コマンド
こんにちは.未だに hatena に慣れない airobo です.
今日は Coq の Program コマンドの話です.Reference Manual にもある通り,まだ experimental らしいので,今後大幅に仕様が変わるかもしれません.
Program コマンドは,その名前の通り Coq 上でプログラムを定義するのに使うと便利なコマンドです.どういう風に便利かというと,型 T と型 {x:T | P(x)} とを(ある意味で)同一視してくれます.例えば次の例です.
Program Definition x : {v | v = 1} := 1.
このコードで,x の型は {v | v = 1} であると宣言しているのにも関わらず,定義は nat 型の値 1 になっています.このように,Program コマンドは型 T と型 {x:T | P(x)} とを同一視してくれます.
では Coq の内部では何が行われているのかというと,型が合うように coercion を自動で挿入しています.実際に,先ほど定義した x を Print してみると,
x = exist (fun v : nat => v = 1) 1 x_obligation_1 : {v : nat | v = 1}
と表示され,exist が挿入されていることがわかります.
上の例とは逆に,型 {x:T | P(x)} から型 T への coercion が入る例としては次のようなものがあります.
Program Theorem nonsense_1_gt : forall x:{v:nat | v = 1}, {v:nat | v > x}.
ここで,x は {v:nat | v = 1} 型で宣言されていますが,nat 型として使われています.実際に Coq に入力すると,goal が "forall x : {v : nat | v = 1}, {v : nat | v > proj1_sig x}" となり,coercion として proj1_sig が挿入されていることが確認できます.
さて,T 型の値を {x:T | P(x)} 型の値に変換するためには当然 P(x) の証明が必要になります.実は先程の x の定義では,Coq が自動で証明をしてくれていて,x を定義したときにその旨が表示されていました.
x has type-checked, generating 1 obligation(s) Solving obligations automatically... x_obligation_1 is defined No more obligations remaining x is defined
"obligation(s)" というのが,示さないといけないもので,ここでは自動で解かれています.では,自動では解けない例を見てみましょう.
Program Definition zero : {v | forall n, n >= v} := 0.
これを実行すると,
zero has type-checked, generating 1 obligation(s) Solving obligations automatically... 1 obligation remaining Obligation 1 of zero: forall n : nat, n >= 0.
と表示され,"forall n : nat, n >= 0." を証明しなければならないことがわかります.
実際に証明するには,"Next Obligation." または "Obligation 1." とすることで,証明が始まります.そして,すべての obligations の証明が終わったところで,定義が終了します.
ここまでの使い方では,あまり嬉しさを感じないかもしれません.私が便利だと思っているのは Program Fixpoint です.何が便利かというと,(coercion の自動挿入も嬉しいのですが,)induction の measure として,複数の引数が使えることです.例えば,nat -> nat -> nat 型の再帰関数を定義しようとしたときに,Function の measure関数は一引数関数しか許されていないので,uncurry化した nat * nat -> nat 型の関数を一度定義した後に curry化してやる必要があります.
例として,みんな大好きユークリッドの互差法を考えてみましょう.Program Fixpoint を使うと次のように書くことができます.
Definition divide n m := exists k, n = k * m. Program Fixpoint gcd (m:nat) (n:{v:nat| m >= v}) {measure (m + n)} : {d:nat | divide m d /\ divide n d} := match n with | O => m | _ => match leb (m-n) n with | true => gcd n (m-n) | false => gcd (m-n) n end end.
ここで,measure を見ると,m と n に関するものになっています.Function で書こうとすると m と n を組にする必要がありますが,組の要素って依存できるんでしょうか?少なくとも私は方法を知りません.ということで,Function や fix で書こうと思うとつまります*1.
以上,Coq 上で関数を定義するときに Program Fixpoint 使うと便利だよ,という話でした.ここでいくつか気をつけないといけないことを書いておきます.
- Function と違って,func_ind や func_rec みたいな便利な関数は定義されないので,関数の性質はプログラムを定義するときに全部書きましょう.
- let じゃなくて,let ' を使うといいことがあります.詳しくは Reference Manual を参照のこと.
- if dec というのを使うと分岐先で条件の情報が使えるよ,ということが Reference Manual に書いてあるのですが,自分の環境では dec が定義されていないと怒られます.とりあえずの解決策として if じゃなくて match を使いましょう.
最後に,今回でてきたコードとその証明を載せておきます.おまけとしてユークリッドの互助法も定義してあります.
Require Import Recdef. Require Import ExtrOcamlNatBigInt. Require Import Bool Arith NPeano Omega. Require Import Coq.Arith.Wf_nat. Require Import Coq.Program.Wf. Program Definition x : {v | v = 1} := 1. Program Theorem nonsense_1_gt : forall x:{v:nat | v = 1}, {v:nat | v > x}. intro x. exists (S (proj1_sig x)). auto. Qed. Program Definition zero : {v | forall n, n >= v} := 0. Next Obligation. auto with arith. Qed. Definition divide n m := exists k, n = k * m. Program Fixpoint gcd (m:nat) (n:{v:nat| m >= v}) {measure (m + n)} : {d:nat | divide m d /\ divide n d} := match n with | O => m | _ => match leb (m-n) n with | true => gcd n (m-n) | false => gcd (m-n) n end end. Next Obligation. split; [exists 1 | exists 0]; auto with arith. Qed. Next Obligation. symmetry in Heq_anonymous. apply leb_iff in Heq_anonymous. omega. Qed. Next Obligation. symmetry in Heq_anonymous. apply leb_iff in Heq_anonymous. omega. Qed. Lemma divide_sub : forall m n d, m >= n -> divide n d -> divide (m - n) d -> divide m d. intros m n d Hmn Hdiv_nd Hdiv_mnd. destruct (le_lt_eq_dec n m Hmn). inversion Hdiv_nd as (k1, Hdiv_nd'). inversion Hdiv_mnd as (k2, Hdiv_mnd'). subst n. exists (k2 + k1). remember (m - k1 * d) as m'. symmetry in Heqm'. apply add_sub_eq_nz in Heqm'. subst. ring. omega. subst n; assumption. Qed. Next Obligation. destruct (gcd n (exist (fun v : nat => n >= v) (m - n) (gcd_func_obligation_2 m (exist (fun v : nat => m >= v) n H0) gcd n H (Logic.eq_refl n) Heq_anonymous)) (gcd_func_obligation_3 m (exist (fun v : nat => m >= v) n H0) gcd n H (Logic.eq_refl n) Heq_anonymous)) as [k Hdiv]; simpl. simpl in Hdiv. inversion Hdiv as (Hdiv1, Hdiv2). split. apply divide_sub with n; assumption. assumption. Qed. Next Obligation. symmetry in Heq_anonymous. apply leb_iff_conv in Heq_anonymous. omega. Qed. Next Obligation. symmetry in Heq_anonymous. apply leb_iff_conv in Heq_anonymous. omega. Qed. Next Obligation. destruct (gcd (m - n) (exist (fun v : nat => m - n >= v) n (gcd_func_obligation_5 m (exist (fun v : nat => m >= v) n H0) gcd n H (Logic.eq_refl n) Heq_anonymous)) (gcd_func_obligation_6 m (exist (fun v : nat => m >= v) n H0) gcd n H (Logic.eq_refl n) Heq_anonymous)) as [k Hdiv]; simpl. simpl in Hdiv. inversion Hdiv as (Hdiv1, Hdiv2). split. apply divide_sub with n; assumption. assumption. Qed. Program Fixpoint gcd' (m:nat) (n:{v:nat| m >= v}) {measure n} : {d:nat | divide m d /\ divide n d} := match n with | O => m | _ => gcd' n (m mod n) end. Next Obligation. split; [exists 1 | exists 0]; auto with arith. Qed. Next Obligation. apply lt_le_weak. apply mod_upper_bound. auto. Qed. Next Obligation. apply mod_upper_bound. auto. Qed. Theorem mod_divides_res : forall a b r: nat, b <> 0 -> a mod b = r -> exists c : nat, a = b * c + r. intros a b r Hb Hmod. assert (b > r) as Hbr. subst r. apply mod_upper_bound. assumption. assert ((a + (b - r)) mod b = 0) as Hmod0. rewrite add_mod. rewrite Hmod. destruct (eq_nat_dec r 0) as [Hr | Hr]. rewrite Hr. simpl. rewrite <- minus_n_O. rewrite mod_same. reflexivity. assumption. replace ((b - r) mod b) with (b - r) . rewrite le_plus_minus_r. apply mod_same. assumption. omega. symmetry . apply mod_small. omega. assumption. apply mod_divides in Hmod0. inversion Hmod0 as (c,Hmod0'). destruct c as [| c']. exists 0. omega. exists c'. remember (b - r) as br. assert (r + br = b) as Hadd_sub. apply add_sub_eq_nz. omega. symmetry; assumption. subst b. rewrite mult_comm in Hmod0'. simpl in Hmod0'. rewrite mult_comm. omega. assumption. Qed. Next Obligation. destruct (gcd' n (exist (fun v : nat => n >= v) (m mod n) (gcd'_func_obligation_2 m (exist (fun v : nat => m >= v) n H0) gcd' n H (Logic.eq_refl n))) (gcd'_func_obligation_3 m (exist (fun v : nat => m >= v) n H0) gcd' n H (Logic.eq_refl n))) as [d Hdiv]; simpl. simpl in Hdiv; inversion Hdiv as [H1 H2]. split. inversion H1 as (k1, H1'). inversion H2 as (k2, H2'). apply mod_divides_res in H2'. inversion H2' as (k3, H2''). subst. exists (k1 * k3 + k2). ring. auto. assumption. Qed.
*1:いろいろがんばってみましたが,結局定義できませんでした.もちろん,well_founded_induction を使えば書けますが,書く気が起きないので書いてません.