Coqで、クイックソート
(* 注意:今回はCoq 8.1以上を使用。 *)
今回は、らくがきえんじんで、昔 言及されていた、クイックソートに挑戦してみる。
Coqで、再帰する関数を書く場合は、Definitionコマンドではなく、Fixpointコマンドを使う。
しかし、Fixpointによる関数定義では、明らかに停止する構造を持つ必要がある。
Coqでは、必ず停止する関数のみが定義できる、という制限のため、このように再帰する関数を定義するのは難しいが、Coqで作った関数は、必ず停止して結果を返す、という性質を保っている。
しかし、Fixpointによる定義では、簡単に定義できない再帰関数もある。
例えば、次のように、クイックソートの関数を素直にかいてみると、以下のようなエラーになる。
(* クイックソートの定義 失敗例 *) Fixpoint qsort (l : list A) : list A := match l with | nil => nil | x :: xs => qsort (filter (A_gtb compare x) xs) ++ (x :: qsort (filter (A_leb compare x) xs)) end. (* (::) はcons, (++) はappend *)
↓
Recursive definition of qsort is ill-formed.
In environment
qsort : list A -> list A
l : list A
x : A
xs : list A
Recursive call to qsort has principal argument equal to
"filter (A_gtb compare x) xs"
instead of xs
このqsort関数は、関数の定義内で、自分自身qsortを呼び出す、再帰関数であるが、その再帰呼び出しで、(filterごにょごにょ)が、与えられており、この関数が停止するかどうか、構造的には、明らかでない形になっている。
このため、Coqからは、qsortの再帰定義が正しくない形だよ。と言われてエラーになってしまう。
この場合は、 Listのfilter関数の結果は、必ず、長さが大きくならないので、停止するはずである。
このような関数を簡単に定義する方法が、Coqの8.1から導入された。それはFunctionコマンドとmeasureキーワードである。
このコマンドによって、停止するかどうかが、簡単にはわからない再帰関数を手軽に書き表すことができ、そのあと、停止性をtacticsを使って証明することができる。
先ほどのコードにおける、Fixpoint を Function にして、measureキーワードをつけたせば、エラーにならずに、停止性の証明モードに推移する。ここでは、measureとして、listの長さを測るlength関数を指定する。
(* クイックソートの定義 成功例 *) Function qsort (l : list A) {measure length l} : list A := match l with | nil => nil | x :: xs => qsort (filter (A_gtb compare x) xs) ++ (x :: qsort (filter (A_leb compare x) xs)) end.
↓
Coq < 2 subgoals A : Set compare : A -> A -> comparison ============================ forall (l : list A) (x : A) (xs : list A), l = x :: xs -> length (filter (A_leb compare x) xs) < length (x :: xs) subgoal 2 is: forall (l : list A) (x : A) (xs : list A), l = x :: xs -> length (filter (A_gtb compare x) xs) < length (x :: xs) qsort_tcc <
ここで、このゴールを証明すれば、停止性が保証され、関数定義が完了するといった具合だ。
証明が終了すると、次のような応答がかえってくるはずである。
Proof completed. qsort_tcc < Qed. qsort_ind is defined qsort_rec is defined qsort_rect is defined R_qsort_correct is defined R_qsort_complete is defined qsort is defined qsort_equation is defined qsort_terminate is defined qsort_tcc is defined
ぱっと見た感じ、qsort以外に、いろんなものが定義されている。これらは、qsortの性質を示すために便利な項達である。
今後、停止性以外の性質を証明しようと思ったら、ふつうのlistの構造に関する帰納法ではうまく行かないことが多い。
しかし、qsort_indを使うと、qsortの定義に従った帰納法によって、証明することができる。
ここでは、qsortしても、入力と出力の要素数が変わらないことと、順番は変わるけど、要素の内容は変わらないことを証明した。
以下が、ソースコードの全体である。
Section QSort. Require Import List. Require Import Bool. (* * Functionキーワードによる再帰定義をうまくやるためには * Recdefというモジュールをインポートする必要がある *) Require Import Recdef. Variable A:Set. Variable compare : A -> A -> comparison. (* 停止性のための補題、filterしたら短くなるよ。 *) Lemma filter_length : forall (A:Set)(l:list A)(p:A -> bool), length (filter p l) <= length l. Proof. intro X; induction l. simpl in |- *. auto. simpl in |- *. intro p; case (p a). simpl in |- *. apply Le.le_n_S. apply IHl. apply le_S. apply IHl. Qed. Definition A_gtb (compare : A -> A -> comparison) (a b : A) : bool := match compare a b with | Gt => true | _ => false end. Definition A_leb (compare : A -> A -> comparison) (a b : A) : bool := negb (A_gtb compare a b). (* * qsort関数の定義 (Proof以下は停止性の証明) * qsort : list A -> list A *) Function qsort (l : list A) {measure length l} : list A := match l with | nil => nil | x :: xs => qsort (filter (A_gtb compare x) xs) ++ (x :: qsort (filter (A_leb compare x) xs)) end. Proof. intros; simpl in |- *; apply Lt.le_lt_n_Sm. apply filter_length. intros; simpl in |- *; apply Lt.le_lt_n_Sm. apply filter_length. Qed. (* * qsortの停止性の証明は無事終了 * 以降ではqsort関数の簡単な性質について、証明するオマケレンマ *) (* pでfilterしたものと~pでfilterしたものを合体させると元通りだよ *) Lemma filter_length_not : forall (l : list A)(p q : A -> bool), (forall x:A, p x = negb (q x)) -> length (filter p l) + length (filter q l) = length l. Proof. intros l p q H; induction l. simpl in |- *; reflexivity. simpl in |- *. rewrite H in |- *. case (q a). simpl in |- *. rewrite <- plus_n_Sm in |- *; rewrite IHl in |- *; reflexivity. simpl in |- *. rewrite IHl in |- *; reflexivity. Qed. (* qsortしても長さは変わらないよ *) Lemma qsort_length : forall l: list A, length (qsort l) = length l. Proof. apply (qsort_ind (fun l1 l2 => length l2 = length l1)). intros; reflexivity. intros l x xs H00 H1 H2. rewrite app_length in |- *. rewrite H1 in |- *. simpl in |- *. rewrite H2 in |- *. rewrite <- plus_n_Sm in |- *; rewrite filter_length_not in |- *. reflexivity. unfold A_leb in |- *. intro x0; case (A_gtb compare x x0); simpl in |- *; reflexivity. Qed. (* l と qsort l とは、集合としては、等しい *) (* その1: l は qsort l の部分集合 *) Lemma qsort_In1 : forall (l:list A)(x:A), In x l -> In x (qsort l). Proof. apply (qsort_ind (fun l1 l2 : list A => forall x : A, In x l1 -> In x l2)). intros l _H x H; apply H. intros l x xs _H IH1 IH2 a H. elim (in_inv H). intro eq; rewrite eq in |- *; apply in_or_app. right; apply in_eq. intro H0. cut (A_gtb compare x a = true \/ A_gtb compare x a = false). intro tf; elim tf. intro t; apply in_or_app. left; apply IH1. elim (filter_In (A_gtb compare x) a xs). intros _H0 H1; apply H1. split; [ apply H0 | apply t ]. intro f; apply in_or_app; right. apply in_cons; apply IH2. elim (filter_In (A_leb compare x) a xs). intros _H0 H1; apply H1. split; [ apply H0 | unfold A_leb in |- *; rewrite f in |- * ]. simpl in |- *; reflexivity. case (A_gtb compare x a); [ left; reflexivity | right; reflexivity ]. Qed. (* その2: qsort l は l の部分集合 *) Lemma qsort_In2 : forall (l:list A)(x:A), In x (qsort l) -> In x l. Proof. apply (qsort_ind (fun l ql => forall x : A, In x ql -> In x l)). intros l _H x H; apply H. intros l x xs _H IH1 IH2 a. intro H; elim (in_app_or (qsort (filter (A_gtb compare x) xs)) (x :: qsort (filter (A_leb compare x) xs)) a). intro H1; apply in_cons. elim (filter_In (A_gtb compare x) a xs). intros H2 _H0; elim H2; intros. apply H0. apply IH1; apply H1. intro H0. elim (in_inv H0). intro eq; rewrite eq in |- *; apply in_eq. intro H1; apply in_cons. elim (filter_In (A_leb compare x) a xs). intros H2 _H0; elim (H2 (IH2 a H1)). intros H3 _H1; apply H3. apply H. Qed. End QSort.