Coq Tutorial

82
Formal Methods Forum Coq 入門 @tmiya April 20,2011 @tmiya : Coq 入門, 1

description

2011/04/23 Coq Tutorial at Formal Methods Forum

Transcript of Coq Tutorial

Page 1: Coq Tutorial

Formal Methods Forum

Coq入門@tmiya

April 20,2011

@tmiya : Coq 入門, 1

Page 2: Coq Tutorial

0. はじめに

本 Coq 入門コースの目的

この Coq 入門コースの目的はI Coq の操作方法について慣れるI Coq を用いてのプログラミングについて知る

• 関数型言語的なコーディングに慣れる• Coq を用いて簡単な定理を証明する• 仕様を述語論理で記述する• 開発したコードが仕様を満たす事を証明する

である。

@tmiya : Coq 入門, 2

Page 3: Coq Tutorial

1. 形式手法と Coq

形式手法ソフトウェアの検証とは「実装が仕様を満たす」事を保証すること

I 仕様は曖昧さが無いように形式的記述されるべきI Lightweight な形式手法

• 保証 (証明、網羅性)より手軽さを重視• 仕様を形式記述するだけでも効果がある• 仕様アニメーションにて動作確認

I モデル検査• 手軽。状態爆発を避けるノウハウが求められる場合も• 実装コードと別にモデル作成必要な場合も• 反例探しなど人間が解けない問題でも答えが得られる• 実際に適用可能な対象は案外限定的 (状態爆発)

I 対話的定理証明• 訓練が必要• 証明出来る事柄には何にでも使える• 実装コード生成も可能 or 実装コードへの注釈

@tmiya : Coq 入門, 3

Page 4: Coq Tutorial

1. 形式手法と Coq

Coq とはI 対話的定理証明の為の言語処理系。I フランスの INRIAで開発。OCamlで記述されている。I 理論的背景:CIC = Calculus of Inductive Construction。I プログラミング言語。依存型を用いた関数型プログラミングで開発可能。

I 高階述語論理を用いて仕様記述が可能。I tactic と呼ばれる命令を用いて証明のゴールを仮定に変形することで、証明を構成する。

I 色々な自動証明 tactic や豊富なライブラリを利用可能で、自分で開発も可能。

I 正しさを機械的検証可能な形で客観的に示す事が出来る。I Coq で行った証明は人間が検証可能な形式で出力出来る。I Coq コードから OCaml, Haskell, Scheme のプログラムを生成可能。I 正しいと証明された実行コードを生成出来る。

@tmiya : Coq 入門, 4

Page 5: Coq Tutorial

2. Coqの起動と停止

Coq標準対話環境の Coqtop, CoqIDE

CUI 対話環境 Coqtop の起動/停止方法:

% coqtopWelcome to Coq 8.3pl1 (December 2010)

Coq < Eval compute in (2+3).= 5: nat

Coq < Quit.%

Coq ではコマンドの最後に必ずピリオドが必要。CoqIDE はコマンド coqide あるいはアイコンから起動。

@tmiya : Coq 入門, 5

Page 6: Coq Tutorial

2. Coqの起動と停止

Proof General : emacs 上の対話環境

Proof General をインストールした後に、~/.emacs に

(load-file "***/ProofGeneral/generic/proof-site.el")

のように、proof-site.el へのパスを設定する。Coq ファイル (*.v) を開くかセーブすると Proof General が起動する。

@tmiya : Coq 入門, 6

Page 7: Coq Tutorial

3. Coq でのプログラミング

定義の方法と定義の確認方法

Coq < Definition x := 1. (* x を定義 *)x is defined (* 定義は := を用いる *)Coq < Check x. (* x の型を調べる *)x

: nat (* 型は nat = 自然数 *)Coq < Print x. (* x の定義の値を調べる *)x = 1

: natCoq < Definition x := 2. (* x を再定義 *)Error: x already exists (* --> 再定義出来ない *)

再定義したい場合は Reset x. で過去の定義を消せる。名前の衝突を避ける手段として Module (本入門コース範囲外) などがある。

@tmiya : Coq 入門, 7

Page 8: Coq Tutorial

3. Coq でのプログラミング

関数の定義の方法関数の定義や適用にはカッコを必要とせず、「関数名 引数 1 引数 2」などのように書く。Coq < Definition f x y := x - y. (* 関数 f を定義 *)f is definedCoq < Check f.f

: nat -> nat -> nat (* nat->(nat->nat) *)Coq < Definition f’ := f 3. (* f’ y = f 3 y *)f’ is definedCoq < Check f’.f’

: nat -> nat (* natを与えると natを返す *)Coq < Eval compute in (f’ 1). (* f’ 1 = f 3 1 = 2 *)

= 2: nat

多引数関数に一部の変数を渡すと部分適用されて、戻り値は関数を返す関数 (を返す関数になる。

@tmiya : Coq 入門, 8

Page 9: Coq Tutorial

3. Coq でのプログラミング

無名関数の定義方法★関数を無名関数として定義することも出来る。

Coq < Check (fun x => 2 * x). (* 無名関数 *)fun x : nat => 2 * x

: nat -> nat

Coq < Eval compute in ((fun x => 2 * x) 3).= 6: nat

Coq < Definition double := (fun x => 2 * x).double is defined (* 無名関数で double を定義 *)

Coq は記法の自由度が高い代わり、入力の最後に . が必要だったり、適宜空白を挿入する必要があったりする。どの程度空白が省略出来るかは次第に体得出来るはず。

@tmiya : Coq 入門, 9

Page 10: Coq Tutorial

3. Coq でのプログラミング

高階関数の定義方法

関数を引数に取る関数を高階関数という。

Coq < Definition twice(f:nat->nat):nat->nat :=Coq < fun x => f (f x).twice is defined

Coq < Definition add5(x:nat) := x + 5.add5 is defined

Coq < Definition twice_add5 := twice add5.twice_add5 is defined

Coq < Eval compute in (twice_add5 2).= 12: nat

@tmiya : Coq 入門, 10

Page 11: Coq Tutorial

3.1. ユーザ定義型

ユーザ定義の型多くのプログラミング言語では int, float などがデフォルトで用意されているが Coq では自然数 nat などもライブラリとして定義されている。(よく使われるものは起動時に読み込まれる。)ユーザ定義型 Weekday を定義してみる。

Coq < Inductive Weekday : Set :=Coq < Sun | Mon | Tue | Wed | Thr | Fri | Sat.

Coq < Check Sun.Sun (* Sun の型は *)

: Weekday (* Weekday *)

Coq < Check Weekday.Weekday (* 型の型は *)

: Set (* Set *)

値を表す型の型は Set に属する。

@tmiya : Coq 入門, 11

Page 12: Coq Tutorial

3.1. ユーザ定義型

ユーザ定義の型への関数関数定義の場合分けをする場合はパターンマッチ構文が使える。パターンマッチは全ケースを網羅しないとエラーになる。(網羅性によってバグが減る。)

Coq < Definition nextday d :=Coq < match d withCoq < | Sun => Mon

: (* 中略 *)Coq < | Sat => SunCoq < end.nextday is definedCoq < Check nextday.nextday (* 引数、戻り値の型を推論 *)

: Weekday -> WeekdayCoq < Eval compute in (nextday Mon).(* 結果を試してみよ *)

同様に prevday を定義してみよ。@tmiya : Coq 入門, 12

Page 13: Coq Tutorial

3.1. ユーザ定義型

Boolを定義してみる

Coq < Inductive Bool : Set :=Coq < | tru : BoolCoq < | fls : Bool.

Coq < Definition And(b1 b2:Bool):Bool :=Coq < match b1,b2 withCoq < | tru,tru => truCoq < | _,_ => flsCoq < end.

同様にして Or, Notも定義し、結果を Eval を用いて確認せよ。

@tmiya : Coq 入門, 13

Page 14: Coq Tutorial

3.1. ユーザ定義型

ライブラリの bool型

標準ライブラリには (当然ではあるが) bool という型が予め定義されている。下記コマンドを入力して確認してみよ。

Coq < Print bool.Coq < Print andb.Coq < Print orb.Coq < Print negb.

bool には値が2通り存在したが、値が1つしかない型に意味はあるか?下記について試し、用途について考えてみよ。

Coq < Print unit.

@tmiya : Coq 入門, 14

Page 15: Coq Tutorial

3.1. ユーザ定義型

De Morgan則を証明してみる (1)★ここまで定理証明の話をしなかったが簡単な定理を証明してみる。証明の操作の詳細は今は判らなくても良い。Coq < Theorem De_Morgan_1 : forall b1 b2,Coq < Not (And b1 b2) = Or (Not b1) (Not b2).1 subgoal

============================forall b1 b2 : Bool,Not (And b1 b2) = Or (Not b1) (Not b2)

De_Morgan_1 < intros.1 subgoal (* b1 b2 を仮定に移した *)

b1 : Boolb2 : Bool============================Not (And b1 b2) = Or (Not b1) (Not b2)

@tmiya : Coq 入門, 15

Page 16: Coq Tutorial

3.1. ユーザ定義型

De Morgan則を証明してみる (2)★

De_Morgan_1 < destruct b1; destruct b2.4 subgoals (* b1 b2 について総当たりの場合分け *)

============================Not (And tru tru) = Or (Not tru) (Not tru)

subgoal 2 is:Not (And tru fls) = Or (Not tru) (Not fls)subgoal 3 is:Not (And fls tru) = Or (Not fls) (Not tru)subgoal 4 is:Not (And fls fls) = Or (Not fls) (Not fls)

@tmiya : Coq 入門, 16

Page 17: Coq Tutorial

3.1. ユーザ定義型

De Morgan則を証明してみる (3)★De_Morgan_1 < simpl.4 subgoals (* 式を評価して変形 *)

============================fls = fls

(* 以下略 *)De_Morgan_1 < reflexivity.3 subgoals (* 左辺=右辺 の時に使うと証明完了して次課題へ *)

============================Not (And tru fls) = Or (Not tru) (Not fls)

subgoal 2 is:Not (And fls tru) = Or (Not fls) (Not tru)subgoal 3 is:Not (And fls fls) = Or (Not fls) (Not fls)

@tmiya : Coq 入門, 17

Page 18: Coq Tutorial

3.1. ユーザ定義型

De Morgan則を証明してみる (4)★残りの3通りについても証明する。

De_Morgan_1 < simpl; reflexivity.De_Morgan_1 < simpl; reflexivity.De_Morgan_1 < simpl; reflexivity.Proof completed.

De_Morgan_1 < Qed.intros.destruct b1; destruct b2.simpl in |- *.reflexivity.simpl in |- *; reflexivity.simpl in |- *; reflexivity.simpl in |- *; reflexivity.De_Morgan_1 is defined

同様に Not (Or b1 b2) = And (Not b1) (Not b2) も試してみよ。@tmiya : Coq 入門, 18

Page 19: Coq Tutorial

3.1. ユーザ定義型

課題1:3値論理

1. Yes, Maybe, No の3つの値を持つ型 Bool3 を定義せよ。2. Bool3 に対する And3, Or3, Not3 を適切に定義せよ。3. (★) 上の定義が適切であれば、やはり De Morgan則が成立するはずである。同様に証明してみよ。今回は何通りを総当たりすることになるか?

@tmiya : Coq 入門, 19

Page 20: Coq Tutorial

3.2. 再帰的な型と関数

自然数 nat について

自然数 nat は下記の様に定義されている。(見やすい様に整形)

Coq < Print nat.Inductive nat : Set :=O : nat

| S : nat -> nat

O (大文字の O) が 0 を表し、S は引数に nat を取って、1大きな natを与える関数、と考える。この自然数の定義方法を Peano の自然数という。

Coq < Eval compute in (S (S (S O))).= 3: nat

S (S (S O)) では人間に不便な為、Coq が 3 と表示してくれている。

@tmiya : Coq 入門, 20

Page 21: Coq Tutorial

3.2. 再帰的な型と関数

自然数の加法nat の加法を定義してみよう。第1引数の n についての再帰関数として定義する。

Coq < Fixpoint add(n m:nat):nat :=Coq < match n withCoq < | O => mCoq < | S n’ => S (add n’ m)Coq < end.add is recursively defined (decreasing on 1st argument)

再帰関数を定義する際はキーワード Fixpoint を用いて定義する。(大文字の O) が 0 を表し、S は引数に nat を取って、1 大きな nat を与える関数、と考える。この自然数の定義方法を Peano の自然数という。

Coq < Eval compute in (add (S (S O)) (S O)).= 3: nat

上記の計算過程 (call-by-value) を考えてみよ。@tmiya : Coq 入門, 21

Page 22: Coq Tutorial

3.2. 再帰的な型と関数

自然数の比較nat の値を比較する関数を定義してみよう。_,_ の部分はどんなパターンがマッチするか、なぜこの定義で OKなのか、考えよう。

Coq < Fixpoint eq_nat(n m:nat):bool :=Coq < match n,m withCoq < | O,O => trueCoq < | S n’, S m’ => eq_nat n’ m’Coq < | _,_ => falseCoq < end.

Coq < Eval compute in (eq_nat 3 3).

同様に le_nat を定義せよ。le_nat n m は n ≤ m の真偽値を返す。再帰関数を定義するとき Coq の場合はひとまず末尾再帰を考えなくて良い。最初は出来るだけ簡単かつ仕様に忠実に実装する事をまず考える(複雑なものは証明も複雑になりやすい)。将来必要になった場合に、末尾再帰版と非末尾再帰版が等価である事を証明すれば良い。

@tmiya : Coq 入門, 22

Page 23: Coq Tutorial

3.2. 再帰的な型と関数

再帰関数の停止性★Coq では再帰関数について停止性 (無限ループにならないこと) を保証する必要がある。実はここまで定義した再帰関数は再帰呼び出しの度にどれか一つの引数の構造が必ず小さくなることによって、再帰呼び出しの停止性を保証している。 下記の例は明示的に第1引数 n の構造についての再帰と宣言した例({struct n} は Coq が推論するため省略可)

Coq < Fixpoint add’(n m:nat){struct n} :=Coq < match n withCoq < | O => mCoq < | S n’ => S (add’ n’ m)Coq < end.add’ is recursively defined (decreasing on 1st argument)

例:add’ 2 3 であれば、add’ の第1引数が毎回簡単になっている。add’ (S (S O)) 3

= S (add’ (S O) 3)= S (S (add’ O 3)) = S (S 3) = 5.

@tmiya : Coq 入門, 23

Page 24: Coq Tutorial

3.2. 再帰的な型と関数

チューリングマシンの停止性判定★

計算機科学を勉強した人は「チューリングマシンの停止性判定」の話を聞いた事があるだろう。「停止性判定が不可能」というのは「全てのプログラムに対して」停止性を判定することは出来ない (そのようなアルゴリズムがあると矛盾する) ということである。Coq では停止性を保証したプログラムしか書けない (※) (停止すると判らないプログラムは処理系が受理しない) ため、Coq のプログラムが必ず停止する事は上記と矛盾しない。(つまりチューリングマシンで許される全てのプログラムが Coq で許される訳では無い。)同様にライスの定理から「『全てのプログラムについて』それが仕様を満たすか判定する方法は無い」ことが言えるが、Coq が証明付きで受理したプログラムにバグが無い (証明結果に反した動作をしない) ことは(Coq と各種公理や推論規則を信じるならば) 保証出来る。※ Coq でも余再帰・余帰納を使えば無限リストなどの止まらない計算を記述する事は可能だがここでは触れない。

@tmiya : Coq 入門, 24

Page 25: Coq Tutorial

3.2. 再帰的な型と関数

課題2:自然数の関数1. 掛け算を行う関数 mul を add を参考に定義せよ。2. mul を用いて階乗を計算する関数 fact を定義せよ。3. 引き算を行う関数 sub を定義してみよ。但し n = 0 の場合は

sub 0 m = 0 と定義する。4. 次の関数 div3 は何を計算する関数か考えよ。また Eval を用いて動作を確認してみよ。

Fixpoint div3(n:nat) :=match n with| S (S (S n’)) => S (div3 n’)| _ => Oend.

 注意:通常の言語では引き算を行う関数 sub から任意の割り算を行う div n m を定義するのは簡単である。Coq の場合は再帰関数の停止性を保証するため整楚帰納法 (入門コース対象外) を用いる必要があり、簡単では無い。

@tmiya : Coq 入門, 25

Page 26: Coq Tutorial

3.3. 多相型

多相型とは3引数の関数 cond c vt vf を考える。第1引数 c:bool が true ならvt を、false なら vf を返す。vt, vf の型を任意の Set の型 A として定義したい。Coq < Definition cond{A:Set}(c:bool)(vt vf:A) :=Coq < match c withCoq < | true => vtCoq < | false => vfCoq < end.

Coq < Eval compute in (cond true 2 3).= 2 : nat

Coq < Eval compute in (cond false false true).= true : bool

{A:Set} も cond の引数だが、呼び出し時に A が推測出来るならば省略出来る事を示す。(必要なら明示的に指定可)

Coq < Eval compute in (@cond nat false 2 3).

@tmiya : Coq 入門, 26

Page 27: Coq Tutorial

3.3. 多相型

option型手続き型言語では (適切な)値が無いことを示す為に null 値などを多用するが、関数型言語では同様の目的で option 型 (HaskellではMaybe型) を用いる。(※但し Coq では値が無い理由の証明を含めて表現可能な sumor 型を用いる方が便利な事も多い。)

Coq < Print option.Inductive option (A : Type) : Type :=Some : A -> option A

| None : option A

Definition option_map {A B:Type} (f:A->B)(o:option A) :=match o with| Some a => Some (f a)| None => None

end.Coq < Eval compute in (option_map (fun x => x + 1) (Some 1)).

@tmiya : Coq 入門, 27

Page 28: Coq Tutorial

3.3. 多相型

prod型と sum型★prod A B 型は A 型と B 型の2つの値を組にしたものである。prod A B を A * B と略記する。( x , y , .. , z ) というタプルとしての表記は (pair .. (pair x y) .. z) の略記法である。Coq < Check (2,true,3).(2, true, 3) : nat * bool * nat

prod の第1成分、第2成分を取得する為の関数 fst, snd があるが、実際はパターンマッチで分解する事が多い。sum A B 型は、値が A 型あるいは B 型の値のどちらかであることを示す型である。下記の様に使う。Coq < Definition test_sum (s:sum nat bool) :=Coq < match s withCoq < | inl n => nCoq < | inr true => 1Coq < | inr false => 0Coq < end.

prod, sum 型については Curry-Howard 対応のところで再度触れる。@tmiya : Coq 入門, 28

Page 29: Coq Tutorial

3.3. 多相型

List型

多相型なデータ構造の例として List を調べる。List は標準ライブラリに定義されておりインポートすれば使用出来る。:: は cons を中置演算子に定義したものである。Type は Set の更に上の型 (Check Set.してみよ) である。

Coq < Require Import List.Coq < Print list.Inductive list (A : Type) : Type :=

nil : list A | cons : A -> list A -> list ACoq < Check (1::2::nil).1 :: 2 :: nil : list nat

関数型言語では、再代入を避けたり、再帰呼び出しとの相性などの事情から、データ列を扱う際は配列ではなくリストを多用する。

@tmiya : Coq 入門, 29

Page 30: Coq Tutorial

3.3. 多相型

Listに対する再帰関数List に対する再帰関数を定義する場合は、List を nil と x::xs とにパターンマッチで場合分けすることを考える。

Coq < Fixpoint append{A:Type}(xs ys:list A):=Coq < match xs withCoq < | nil => ysCoq < | x::xs’ => x::(append xs’ ys)Coq < end.Coq < Eval compute in (append (1::2::nil) (3::4::nil)).

Coq < Fixpoint olast{A:Type}(xs:list A):option A :=Coq < match xs withCoq < | nil => NoneCoq < | a::nil => Some aCoq < | _::xs’ => olast xs’Coq < end.Coq < Eval compute in (olast (1::2::3::nil)).

@tmiya : Coq 入門, 30

Page 31: Coq Tutorial

3.3. 多相型

課題3:Listへの関数

1. リストの長さを与える関数 len{A:Type}(xs:list A):nat を定義せよ。Eval compute in (len (1::2::3::nil)). などで確認せよ。

2. list bool の入力を受け取り、全要素が true の時 true を返す関数 all_true(xs:list bool):bool を定義せよ。但し nil に対しては true を返すとせよ。

3. リストの先頭要素 x があれば Some x を、空リストに対してはNone を返す、関数 ohead{A:Type}(xs:list A):option A を定義せよ。

4. 自然数 s, n に対して s :: s+1 :: ... :: (s+n-1) :: nil を返す関数 nat_list(s n:nat):list nat を定義せよ。

5. リストを反転する関数 reverse{A:Type}(xs:list A):list A を定義せよ。必要なら append を使え。

@tmiya : Coq 入門, 31

Page 32: Coq Tutorial

4. 定理と証明

真理値表を使った証明前に Bool 型についての De Morgan 則を証明した。その際に行った事は下記の様な真理値表の全ケースについて一致する事を確かめることであった。

P Q ¬(P ∧ Q) ¬P ∨ ¬QF F T TF T T TT F T TT T F F

Bool, nat 型のような Inductive を用いて定義した型については、全ての値を列挙して場合分けする事が可能である。(nat については O とS n に列挙して場合分けしていると考える。)しかし Coq が採用する「直観論理」という論理体系では、Prop に属する「命題の型」については真偽で場合分け出来ない、と考える。(実際、直観論理では上記の2つの命題が等価である事を示せない。)

@tmiya : Coq 入門, 32

Page 33: Coq Tutorial

4. 定理と証明

排中律について排中律というのは「どんな命題 P についても、P か ¬P のどちらかは成り立つ」という (古典)論理の公理である。通常の数学の証明では排中律を自由に使うが、プログラムを書く立場では排中律が役に立たない場合もある。排中律の説明でよく使われる例を示す。「定理:ab が有理数となる様な 無理数 a, bが存在する」

1. a = b =√

2 とする。a, b は無理数であり、ab =√

2√

2 がもし有理数ならば証明終わり。

2. 上の ab が無理数の場合は、a =√

2√

2, b =

√2 とする。a, b は無理

数であり、ab = (√

2√

2)√

2 =√

2√

2√

2=

√2

2= 2 は有理数。

よって√

2√

2 が、有理数 (P)か無理数 (~P)かのどちらかが成り立つならば、ab が有理数となる様な 無理数 a, bが存在する。ではこの証明は「仕様:ab が有理数となる様な 無理数 a, bを計算する」というプログラム開発の役に立つだろうか?直観主義論理はこの排中律を認めない立場の論理体系である。

@tmiya : Coq 入門, 33

Page 34: Coq Tutorial

4. 定理と証明

公理と推論規則に基づく証明直観主義論理では真理値表による証明が使えない。そこで定式化として自然推論と呼ばれる、公理と推論規則による証明を用いる。公理は「正しいと認めて良い命題」であり、推論規則は「(幾つかの)正しい命題を組み合わせて正しい命題を導く規則」である。推論規則の例としていわゆる三段論法の Modus Ponens と呼ばれる規則がある。命題「A」が成り立つ、命題「A ならば B である」が成り立つならば、命題「B」が成り立つ、というものである。(Γ = 仮定 (公理含む)の集合。「仮定 ` 結論」という記法。)

Γ ` A Γ ` A → B (→ 除去)Γ ` B

上記は (上から下の向きに考えると)→除去規則となっている。自然演繹ではこのような推論規則を用いて公理から様々な「正しい命題」を構成する。Coq の証明は逆にこの証明図を下から上へ辿る形で行われる。ゴールにB、仮定に Hab : A → B が存在する時に、apply Hab. という tactic を実行するとゴールが A に変化する。ここで仮定あるいは公理に Ha : Aが存在すれば、exact Ha. を実行すると証明が完了する。

@tmiya : Coq 入門, 34

Page 35: Coq Tutorial

4. 定理と証明

ゴールと仮定が一致する場合Coq の証明で必要な tactic を示す。まず証明のゴールと同じものが、仮定の中にある場合は、tactic の assumption. を使用すると証明が完了する。exact H. あるいは trivial. も同じ目的に使用出来る。

...H : A...------------------------A

仮定の中に無いが既存の定理と一致する場合は exact 定理名. とすれば良い。これは自然演繹の推論規則としては、

A ∈ Γ(仮定)

Γ ` A

に相当する。

@tmiya : Coq 入門, 35

Page 36: Coq Tutorial

4.1. → を含む証明

ゴールに→を含む場合ゴールが A → B (CUI端末では A -> B と表示) の様な形をしている場合は、A を仮定に持って行く為に intro Ha. という tactic を用いる。すると仮定に Ha : A が追加され、ゴールが B となる。Coq の証明では上記の様にゴールを徐々に簡単な形に変形して進める事が多い。これは自然演繹の推論規則としては、

...Γ, A ` B

(→ 導入)Γ ` A → B

に相当する。ゴールが H1 → H2 → · · · → Hn → B の場合は intros H1 H2 ... Hn.と同時に複数 intro することも出来る。また intro. や intros. とすると Coq が自動的に仮定に名前を付けてくれる。逆の働きをする →除去規則は、先に述べた Modus Ponens であり、対応する tactic は apply 仮定名. である。

@tmiya : Coq 入門, 36

Page 37: Coq Tutorial

4.1. → を含む証明

例題:→ のみを含む命題 (1)

ここまで学んだ tactic だけで証明出来る例題を解いてみよう。値は「値の型の型」Set に属していた。命題 P は型であり「命題の型」Prop に属している。命題 P は型であり、 P に属する、P の証明の実体 p : Pが存在する。(型階層としては Set, Prop は同じレベルで、その上にType が存在する。)

Section imp_sample.Variables P Q R : Prop.Theorem imp_sample : (P -> (Q -> R)) -> (P -> Q) -> P -> R.

1 subgoal

============================(P -> Q -> R) -> (P -> Q) -> P -> R

二重線 === の上が仮定、下がゴールである。このゴールを tactic を用いて変形していく。ゴールが → を含む場合は intro(s) を用いる。

@tmiya : Coq 入門, 37

Page 38: Coq Tutorial

4.1. → を含む証明

例題:→ のみを含む命題 (2)

ゴールの → を消す為に intros を行う。大体の証明の最初の tactic はintro(s) である。

imp_sample < intros pqr pq p.1 subgoal

pqr : P -> Q -> Rpq : P -> Qp : P============================R

仮定の中で R を導けそうなものを探すと pqr が良さそうである。従って apply pqr. を入力する。

@tmiya : Coq 入門, 38

Page 39: Coq Tutorial

4.1. → を含む証明

例題:→ のみを含む命題 (3)

ゴールの R を消す為に apply pqr. をを行う。すると pqr のP -> Q -> の部分から、ゴール P と Q が生成される。

imp_sample < apply pqr.2 subgoals

pqr : P -> Q -> Rpq : P -> Qp : P============================P

subgoal 2 is:Q

ここでゴール P は仮定にそのままあるので assumption. を実行する。

@tmiya : Coq 入門, 39

Page 40: Coq Tutorial

4.1. → を含む証明

例題:→ のみを含む命題 (4)

ゴールの P を消す為に assumption. を行う。すると 残りのゴール Qが証明課題となる。

imp_sample < assumption.1 subgoal

pqr : P -> Q -> Rpq : P -> Qp : P============================Q

ゴール Q を導くにはどうすればよいか考えてみよう。

@tmiya : Coq 入門, 40

Page 41: Coq Tutorial

4.1. → を含む証明

例題:→ のみを含む命題 (5)

imp_sample < apply pq.1 subgoal

pqr : P -> Q -> Rpq : P -> Qp : P============================P

imp_sample < assumption.Proof completed.

imp_sample < Qed.

証明が完了したら最後に Qed. を入力する。

@tmiya : Coq 入門, 41

Page 42: Coq Tutorial

4.2. ∧ を含む証明

∧ を含む場合 (1)

仮定が P ∧ Q という形の場合、P と Q という2つの仮定に分解したい。仮定 pq : P /\ Q を分解するには destruct pq as [p q]. という tactic を使う。すると p : P と q : Q の2つの仮定に分解される。単に destruct pq. とした場合は p,q ではなく Coq が適当な名前を付ける。ゴールが P ∧ Q という形をしている場合は、P と Q の両方を示す事が出来れば P ∧Q を示せる。split. という tactic を用いると現在のゴール P /\ Q を2つのサブゴール P, Q に分割する。例として次の定理を証明する。

Coq < Variable P Q R:Prop.Coq < Theorem and_assoc : (P/\Q)/\R -> P/\(Q/\R).1 subgoal

============================(P /\ Q) /\ R -> P /\ Q /\ R

@tmiya : Coq 入門, 42

Page 43: Coq Tutorial

4.2. ∧ を含む証明

∧ を含む場合 (2)

まず intro してゴールの → を除去し、次いで仮定の ∧ を分解する。

and_assoc < intro pqr.1 subgoal

pqr : (P /\ Q) /\ R============================P /\ Q /\ R

and_assoc < destruct pqr as [[p q] r].1 subgoal

p : Pq : Qr : R============================P /\ Q /\ R

@tmiya : Coq 入門, 43

Page 44: Coq Tutorial

4.2. ∧ を含む証明

∧ を含む場合 (3)

次いでゴールを分解し、個々の仮定と一致したら assumption を使用する。; で tactic を連結可能で、split で生成された2つのゴールの両方にassumption を使用する。

and_assoc < split.2 subgoals============================P

subgoal 2 is:Q /\ Rand_assoc < assumption.1 subgoal============================Q /\ R

and_assoc < split; assumption.Proof completed.and_assoc < Qed.

@tmiya : Coq 入門, 44

Page 45: Coq Tutorial

4.3. ∨ を含む証明

∨ を含む場合 (1)

仮定が P ∨ Q という形の場合、P が成立する場合と Q が成立する場合との両方について証明をする必要がある。仮定 pq : P \/ Q を分解するには destruct pq as [pq].— という tactic を使う。すると仮定にp : P が含まれる場合と q : Q の場合の2つのサブゴールに分解される。単に destruct pq. とした場合は p,q ではなく Coq が適当な名前を付ける。ゴールが P ∨ Q という形をしている場合は、P と Q のどちらかを示す事が出来れば P ∨ Q を示せる。left. あるいは right. という tacticを用いて証明出来そうなゴール P あるいは Q のどちらかを選択する。例として次の定理を証明する。

Coq < Variable P Q R:Prop.Coq < Theorem or_assoc : (P\/Q)\/R -> P\/(Q\/R).1 subgoal

============================(P \/ Q) \/ R -> P \/ Q \/ R

@tmiya : Coq 入門, 45

Page 46: Coq Tutorial

4.3. ∨ を含む証明

∨ を含む場合 (2)

まず intro してゴールの → を除去し、次いで仮定の ∨ を分解する。

and_assoc < intro pqr.or_assoc < destruct pqr as [[p|q]|r].3 subgoals

p : P============================P \/ Q \/ R

subgoal 2 is:P \/ Q \/ Rsubgoal 3 is:P \/ Q \/ R

@tmiya : Coq 入門, 46

Page 47: Coq Tutorial

4.3. ∨ を含む証明

∨ を含む場合 (3)ゴールを分解し、個々の仮定と一致したら assumption を使用。or_assoc < left.3 subgoalsp : P============================P

or_assoc < assumption.2 subgoalsq : Q============================P \/ Q \/ R

or_assoc < right; left.2 subgoalsq : Q============================Q

以下、同様に証明すれば OK。@tmiya : Coq 入門, 47

Page 48: Coq Tutorial

4.4. ¬ を含む証明

¬ を含む場合 (1)

直観論理では ¬P は P → False にて定義される。False は

Inductive False : Prop :=

で定義される、値が存在しない型である。ゴールが ~P の場合は、intro p. すると仮定に p : P が追加され、ゴールが False になる。仮定に H : False が存在する場合は、ゴールが何であれ elim H. を実行すると証明が終わる。仮定が np : ~P の形をしている時に、elim np. を行うとゴールが何であれ P に変わる。直観論理では二重否定の除去 (¬¬P を P にする) が使えない (∵ 排中律と等価なので) といった制約もあり、否定の入った命題の証明は多少面倒であり、またそういう定理も後から使いにくかったりする。

@tmiya : Coq 入門, 48

Page 49: Coq Tutorial

4.4. ¬ を含む証明

¬ を含む場合 (2)

例として次の定理を証明する。

Coq < Theorem neg_sample : ~(P /\ ~P).1 subgoal============================~ (P /\ ~ P)

neg_sample < intro.1 subgoalH : P /\ ~ P============================False

@tmiya : Coq 入門, 49

Page 50: Coq Tutorial

4.4. ¬ を含む証明

¬ を含む場合 (3)

neg_sample < destruct H as [p np].1 subgoalp : Pnp : ~ P============================False

neg_sample < elim np.p : Pnp : ~ P============================P

neg_sample < assumption.Proof completed.

@tmiya : Coq 入門, 50

Page 51: Coq Tutorial

4.4. ¬ を含む証明

課題4:命題論理の証明

証明せよ。

Variable A B C D:Prop.Theorem ex4_1 : (A -> C) /\ (B -> D) /\ A /\ B -> C /\ D.Theorem ex4_2 : ~~~A -> ~A.Theorem ex4_3 : (A -> B) -> ~B -> ~A.Theorem ex4_4 : ((((A -> B) -> A) -> A) -> B) -> B.Theorem ex4_5 : ~~(A\/~A).

@tmiya : Coq 入門, 51

Page 52: Coq Tutorial

5. Curry-Howard 対応

Curry-Howard 対応 (1)

先の定理を別のやり方で証明してみよう。定理の名前を少し変えて入力する。

Theorem imp_sample’ : (P -> (Q -> R)) -> (P -> Q) -> P -> R.imp_sample’ < intros pqr pq p.imp_sample’ < Check pq.pq

: P -> Qimp_sample’ < Check (pq p).pq p

: Q

命題 pq の型は P -> Q であることが判る。これは型 P の引数を受け取って型 Q の値を返す関数である。従って、pq に引数 p を与えると結果の型は Q になる。

@tmiya : Coq 入門, 52

Page 53: Coq Tutorial

5. Curry-Howard 対応

Curry-Howard 対応 (2)pqr, pq, p を使って型 R の値を作る方法を考えると下記の様になる。これを exact tactic を用いて与えると証明が完了する。

imp_sample’ < Check (pqr p (pq p)).pqr p (pq p)

: R

imp_sample’ < exact (pqr p (pq p)).Proof completed.

これをみて判る様に証明とは、仮定を引数、ゴールを戻り値、とした関数を書くことに他ならない。実際、定理 imp_sample を Print してみると判る。

Coq < Print imp_sample.imp_sample =fun (pqr : P -> Q -> R) (pq : P -> Q) (p : P) => pqr p (pq p)

: (P -> Q -> R) -> (P -> Q) -> P -> R

@tmiya : Coq 入門, 53

Page 54: Coq Tutorial

5. Curry-Howard 対応

Curry-Howard 対応 (3)

先ほどの例を観た様に証明とプログラムの間には証明 プログラム命題 P 型 P

命題 P → Q 関数 P -> QΓ ` P Γ ` P → Q

(→ 除去)Γ ` Q

関数適用 pq p

Γ, P ` Q(→ 導入)

Γ ` P → Q関数 pq (p:P):Q

命題 P ∧ Q prod 型 (P,Q)

命題 P ∨ Q sum 型{

inl Pinr Q

証明の正しさの検証 コンパイラの型チェック

という対応がある。これを Curry-Howard 対応という。Curry-Howard 対応を考えると「コンパイラを型チェックを通る関数を書く」=「証明を書く」である。複雑な関数を人間が書き下すのは困難なので、Coq は対話的証明という形でそれを支援する。

@tmiya : Coq 入門, 54

Page 55: Coq Tutorial

5. Curry-Howard 対応

Curry-Howard 対応 (4)命題論理の証明の検証であれば、Coq でなく多相型 (ジェネリクス) のある静的型付き言語であれば可能である。例えば Java だと(P → Q → R) → (P → Q) → P → R の証明はinterface Fun<A,B> {public B apply(A a);

}public class P {}public class Q {}public class R {}public class Proof {public R imp_sample(Fun<P,Fun<Q,R>> pqr, Fun<P,Q> pq, P p) {return pqr.apply(p).apply(pq.apply(p));

}}

のコンパイルが通る事で確認出来る。(Java が得意な人は prod 型、sum型 をどのように表現すれば良いか考えてみよ。また ¬ を Java でどう表現するか?)

@tmiya : Coq 入門, 55

Page 56: Coq Tutorial

6. 述語論理

述語論理述語論理では命題論理に加えて、

I 「全ての a : A について P a (∀a : A,P a)」: forall (a:A), P aI 「ある a : A が存在して P a (∃a : A,P a)」: exists (a:A), P a

という量化子を用いた記述が使える。Coq では述語 P とは値 a:A に応じて命題 P a:Prop を返す関数A -> Prop と考える。Coq は一階述語論理だけではなく高階述語論理もサポートしているので、「全ての値 a : A 」だけでは無く「全ての述語 P : A → Prop 」「引数として述語を取る様な述語」なども記述出来る。述語は例えば下記の様に定義出来る。Coq < Definition iszero(n:nat):Prop :=Coq < match n withCoq < | O => TrueCoq < | _ => FalseCoq < end.iszero is defined

@tmiya : Coq 入門, 56

Page 57: Coq Tutorial

6. 述語論理

∀ がある場合 (1)ゴールに forall がある場合は、intro(s) を行う。実は forall x:Xは x:X -> と同じである。Coq < Theorem sample_forall : forall (X:Type)(P Q:X->Prop)(x:X),P x -> (forall y:X, Q y) -> (P x /\ Q x).============================forall (X : Type) (P Q : X -> Prop) (x : X),P x -> (forall y : X, Q y) -> P x /\ Q x

sample_forall < intros X P Q x px Hqy.X : TypeP : X -> PropQ : X -> Propx : Xpx : P xHqy : forall y : X, Q y============================P x /\ Q x

@tmiya : Coq 入門, 57

Page 58: Coq Tutorial

6. 述語論理

∀ がある場合 (2)仮定に forall y:X がある場合は、y に任意の X 型の変数を代入したものを得る事が出来る。sample_forall < split. (* ゴールを P x と Q x とに *)sample_forall < assumption. (* P x は仮定 px そのまま *)1 subgoal

X : TypeP : X -> PropQ : X -> Propx : Xpx : P xHqy : forall y : X, Q y============================Q x

sample_forall < apply (Hqy x). (* Hqy の y に x を代入 *)Proof completed.

@tmiya : Coq 入門, 58

Page 59: Coq Tutorial

6.2. ∃ がある場合

∃ がある場合 (1)

Coq < Theorem sample_exists : forall (P Q:nat->Prop),Coq < (forall n, P n) -> (exists n, Q n) ->Coq < (exists n, P n /\ Q n).

sample_exists < intros P Q Hpn Hqn.1 subgoal

P : nat -> PropQ : nat -> PropHpn : forall n : nat, P nHqn : exists n : nat, Q n============================exists n : nat, P n /\ Q n

@tmiya : Coq 入門, 59

Page 60: Coq Tutorial

6.2. ∃ がある場合

∃ がある場合 (2)仮定に exists がある場合は、仮定に destruct を行う。sample_exists < intros P Q Hpn Hqn.P : nat -> PropQ : nat -> PropHpn : forall n : nat, P nHqn : exists n : nat, Q n============================exists n : nat, P n /\ Q n

sample_exists < destruct Hqn as [n’ qn’].P : nat -> PropQ : nat -> PropHpn : forall n : nat, P nn’ : natqn’ : Q n’============================exists n : nat, P n /\ Q n

@tmiya : Coq 入門, 60

Page 61: Coq Tutorial

6.2. ∃ がある場合

∃ がある場合 (3)ゴールに exists x:X がある場合は、具体的な x:X を用いてexists x. を行う。sample_exists < destruct Hqn as [n’ qn’].:Hpn : forall n : nat, P nn’ : natqn’ : Q n’============================exists n : nat, P n /\ Q n

sample_exists < exists n’.:Hpn : forall n : nat, P nn’ : natqn’ : Q n’============================P n’ /\ Q n’ (* 以下証明してみよ *)

@tmiya : Coq 入門, 61

Page 62: Coq Tutorial

6.2. ∃ がある場合

課題5:述語論理の証明証明せよ。

Theorem ex5_1 : forall (A:Set)(P:A->Prop),(~ exists a, P a) -> (forall a, ~P a).

Theorem ex5_2 : forall (A:Set)(P Q:A->Prop),(exists a, P a \/ Q a) ->(exists a, P a) \/ (exists a, Q a).

Theorem ex5_3 : forall (A:Set)(P Q:A->Prop),(exists a, P a) \/ (exists a, Q a) ->(exists a, P a \/ Q a).

Theorem ex5_4 : forall (A:Set)(R:A->A->Prop),(exists x, forall y, R x y) -> (forall y, exists x, R x y).

Theorem ex5_5 : forall (A:Set)(R:A->A->Prop),(forall x y, R x y -> R y x) ->(forall x y z, R x y -> R y z -> R x z) ->(forall x, exists y, R x y) -> (forall x, R x x).

@tmiya : Coq 入門, 62

Page 63: Coq Tutorial

6.3. = を含む証明

= を含む証明 (1)

最も重要な述語は値が等しい事を示す eq (= も使用可) である。

Inductive eq (A : Type) (x : A) : A -> Prop :=refl_equal : x = x

Coq ではI 型が等しい (nat と bool では駄目)

I コンストラクタが等しい (nat でも O と S n では駄目)

I コンストラクタ引数が等しい (S m と S n なら m = n が必要)

の場合のみ、等号が成り立つ。ゴールが

============================n = n

になったときは、apply (refl_equal n). としても良いが通常はtactic の reflexivity. を用いる。

@tmiya : Coq 入門, 63

Page 64: Coq Tutorial

6.3. = を含む証明

= を含む証明 (2)

等号を含む簡単な式を証明する。plus の定義は Print plus. で確認可能。式を簡単にする為には tactic の simpl. を使う。

Coq < Theorem plus_0_l : forall n, 0 + n = n.plus_0_l < intro n.n : nat============================0 + n = n

plus_0_l < simpl.n : nat============================n = n

plus_0_l < reflexivity.Proof completed.

@tmiya : Coq 入門, 64

Page 65: Coq Tutorial

6.4. 帰納法

帰納法 (1)

同様に ∀n : nat, n + 0 = n を証明出来るだろうか?実は simpl. を使ってもうまくいかない。

============================n + 0 = n

plus_0_r < simpl.============================n + 0 = n

これは plus n m の n の値で場合分けして再帰している為である。この定理を証明する為には n に関する帰納法:

1. n = 0 の時、n + 0 = n が成り立つ。2. n = n′ の時、n + 0 = n が成り立つならば、n = S n′ でも

n + 0 = n が成り立つ。を用いる。

@tmiya : Coq 入門, 65

Page 66: Coq Tutorial

6.4. 帰納法

帰納法 (2)

n に関する帰納法を使用する為には induction n as [|n’]. またはinduction n. という tactic を使う。Coq 内部では次の定理 nat_indが呼び出される。(P に現在のゴール)

Coq < Check nat_ind.nat_ind : forall P : nat -> Prop, P 0 ->(forall n : nat, P n -> P (S n)) ->forall n : nat, P n

この nat_ind は nat のコンストラクタ O : nat と S : nat -> natの形から自動的に生成される。実は Inductive を使って定義した型、例えば bool などにも bool_ind は存在する。

bool_ind : forall P : bool -> Prop,P true -> P false ->forall b : bool, P b

@tmiya : Coq 入門, 66

Page 67: Coq Tutorial

6.4. 帰納法

帰納法 (3)induction n as [|n’]. を使用すると、n が 0 と S n’ の場合の証明課題が生成される。前者は reflexivity. で OK (simpl.は自動で実行)。Coq < Theorem plus_0_r : forall n:nat, n + 0 = n.plus_0_r < induction n as [|n’].2 subgoals============================0 + 0 = 0

subgoal 2 is:S n’ + 0 = S n’

plus_0_r < reflexivity.1 subgoaln’ : natIHn’ : n’ + 0 = n’============================S n’ + 0 = S n’

@tmiya : Coq 入門, 67

Page 68: Coq Tutorial

6.4. 帰納法

帰納法 (4)n = S n’ の証明課題では n = n’ では成立するという仮定 IHn’ が存在するので、これを使う事を考える。S n’ + 0 = S n’ を証明するため simpl. を使うと plus の定義よりS (n’ + 0)= S n’ になる。ここで IHn’ を使って n’ + 0 を n’ に書き換えるには rewrite IHn’. と rewrite を使う。============================S n’ + 0 = S n’

plus_0_r < simpl.IHn’ : n’ + 0 = n’============================S (n’ + 0) = S n’

plus_0_r < rewrite IHn’.IHn’ : n’ + 0 = n’============================S n’ = S n’

@tmiya : Coq 入門, 68

Page 69: Coq Tutorial

6.4. 帰納法

課題6:m + n = n + m の証明コマンド SearchAbout を使うと定義済みの定理を探す事が出来る。

Coq < SearchAbout plus.plus_n_O: forall n : nat, n = n + 0plus_O_n: forall n : nat, 0 + n = nplus_n_Sm: forall n m : nat, S (n + m) = n + S mplus_Sn_m: forall n m : nat, S n + m = S (n + m)mult_n_Sm: forall n m : nat, n * m + n = n * S m

定義済みの定理は仮定と同じ様に rewrite で使用出来る。(例えばrewrite <- plus_n_Sm n’ m. など。rewrite H. はゴールの中の Hの左辺を右辺に書き換える。右辺を左辺に書き換える場合はrewrite <- H. )上記の適切な定理を用いて下記を証明せよ。

Theorem plus_comm : forall m n:nat, m + n = n + m.

@tmiya : Coq 入門, 69

Page 70: Coq Tutorial

6.4. 帰納法

帰納法 (5)

帰納法を使った証明はは自然数 nat 以外の帰納型、例えば list A などにも使用する。下記の定理を証明せよ。

Theorem length_app : forall (A:Type)(l1 l2:list A),length (l1 ++ l2) = length l1 + length l2.

list のコンストラクタ cons は引数を2つ取るため、induction l1 as [|a l1’]. などの様に2つ書く (あるいはinduction l1.)。この定理の場合は問題無いが、induction を使用する前に無闇に他の変数 (例えばこの場合は l2) を intro すべきで無い。

1. intros A l1 l2. induction l1 as [|a l1’].

2. intros A l1. induction l1 as [|a l1’]. intro l2.

の場合で帰納法の仮定 IHl1’ がどう違うか見比べ、後者の方がより一般的な仮定となっている事を確かめよ。(Coqtop の場合も証明を Undo.で1手戻れる。)

@tmiya : Coq 入門, 70

Page 71: Coq Tutorial

6.4. 帰納法

課題7:リストに関する証明 (1)List をインポートし、リストの連結 append とリストの反転 reverseを行う関数を定義する。Require Import List.Fixpoint append{A:Type}(l1 l2:list A):=match l1 with| nil => l2| a::l1’ => a::(append l1’ l2)end.Fixpoint reverse{A:Type}(l:list A):=match l with| nil => nil| a::l’ => append (reverse l’) (a::nil)end.

ここで下記の定理を証明したい。Theorem reverse_reverse : forall (A:Type)(l:list A),reverse (reverse l) = l.

@tmiya : Coq 入門, 71

Page 72: Coq Tutorial

6.4. 帰納法

課題7:リストに関する証明 (2)

下記補題を証明し、それを用いて reverse_reverse を証明せよ。

Lemma append_nil : forall (A:Type)(l:list A),append l nil = l.

Lemma append_assoc : forall (A:Type)(l1 l2 l3:list A),append (append l1 l2) l3 = append l1 (append l2 l3).

Lemma reverse_append : forall (A:Type)(l1 l2:list A),reverse (append l1 l2) = append (reverse l2) (reverse l1).

@tmiya : Coq 入門, 72

Page 73: Coq Tutorial

7. 依存型

依存型 (1)

ここまで紹介した話は、値を計算する関数の書き方と、関数の戻り値が仕様を満たす証明の方法、であった。Coq ではこの2つを統合した関数を定義出来る。例として、natの引き算について考えよう。minus(n m:nat) は n ≤ mの場合は 0 を返す仕様とする事が多い。しかしその場合 n − m + m = n とはならない (理由を考えよ)。ここで次の様な関数 sub を考える。

Definition sub(m n:nat)(H:le n m) : {x:nat|x+n=m}.

この関数は、1. 引数 H として n ≤ m の証明を要求する。従って n ≤ m と証明出来る場合しか関数を呼び出せない。

2. 戻り値として値 x = m− n だけではなく、x + n = m である証明と組になっている。(従って後続の計算で証明が必要な時に使用出来る)

であり、上記のチェックは実行時ではなくコンパイル時に行える。@tmiya : Coq 入門, 73

Page 74: Coq Tutorial

7. 依存型

依存型 (2)Coq では証明モードを利用して関数を定義する事が出来る。今回は証明を楽にする為に nat に関するライブラリ Arith パッケージを使用する。Require Import Arith. (* Arith をインポート *)Definition sub(m n:nat)(H:le n m) : {x:nat|x+n=m}.1 subgoalm : natn : natH : n <= m============================{x : nat | x + n = m}

まず m, n が既に intro されて都合が悪いので generalize dependentを使って戻す。sub < generalize dependent m.sub < generalize dependent n.============================forall n m : nat, n <= m -> {x : nat | x + n = m}

@tmiya : Coq 入門, 74

Page 75: Coq Tutorial

7. 依存型

依存型 (3)以下はこれまでと同様に証明する。eapply, erewrite は引数を推論する機能のある apply, rewrite で便利。最後は Qed. ではなく Defined.を使う。induction n as [|n’].(* n=0 *)intros m H. exists m. eapply plus_0_r.(* n=S n’ *)induction m as [|m’]; intro H.(* m=0 *)assert(H’: ~(S n’ <= 0)). eapply le_Sn_0.elim H’. assumption.(* m=S m’ *)assert(H’: n’ <= m’). eapply le_S_n. assumption.assert(IH: {x:nat|x+n’=m’}). eapply IHn’. assumption.destruct IH as [x IH]. exists x.erewrite <- plus_n_Sm. rewrite IH. reflexivity.

Defined.

@tmiya : Coq 入門, 75

Page 76: Coq Tutorial

7. 依存型

依存型 (4)

定義された sub は Print sub. などを行うと証明を含む複雑な式になっている。しかし sub から OCaml コードを抽出したものは簡単なコードになっており、よく見ると minus の定義とさほど違わない事が判る。

Coq < Extraction sub.(** val sub : nat -> nat -> nat **)let rec sub m = function| O -> m| S n0 ->

(match m with| O -> assert false (* absurd case *)| S n1 -> let iH = sub n1 n0 in Obj.magic iH)

OCaml 版のコードでは、仮定 H が満たされない場合は例外を投げる振る舞いとなり、戻り値の証明は単に無視される。

@tmiya : Coq 入門, 76

Page 77: Coq Tutorial

7. 依存型

依存型 (5)

実際にこの関数で計算をしてみる。引数 H に与える証明を用意してから呼び出す。

Theorem le_2_5 : le 2 5.Proof. repeat eapply le_n_S. repeat constructor. Qed.

Eval compute in (sub 5 2 le_2_5). (* 長い証明付きの値 *)Eval compute in (proj1_sig (sub 5 2 le_2_5)).                         (* = 3:nat *)

{x|x+n=m} から実際の値 x を取り出す為には、値と証明の組の第1成分を取り出す関数 proj1_sig を用いる。

@tmiya : Coq 入門, 77

Page 78: Coq Tutorial

7. 依存型

課題8:minus を使って定義実は sub は既存の関数 minus を使って簡単に定義することも出来る。実装せよ。

Definition sub’(m n:nat)(H:le n m) : {x:nat|x+n=m}.Proof.(* minus を使って定義せよ *)

Defined.

Coq で関数と証明を扱う場合は1. minus の様な通常の値を返す関数を書き、minus が満たす性質の定理を別途証明する。

2. minus の様な関数を予め作成し、それを sub’ の様に証明付き関数でラップする。

3. refine tactic を使う事で証明を後回しにして関数を書き下す。(refine の使い方は本コースの範囲外)

4. sub の様に証明を書いて関数を定義する。など様々な方法がある。

@tmiya : Coq 入門, 78

Page 79: Coq Tutorial

8. まとめ

本コースで扱わなかった事

本コースは Coq 入門コースということで下記の事柄に付いては扱わなかった。

I 自動証明の tactic の使い方 (auto, ring, omega など)

I 各種ライブラリ (Arith, List, String などの使い方)

I Haskell, OCaml などのプログラムの抽出I より高度な証明 (inversion, refine の使用、整楚帰納法など)

I tactic の開発やカスタマイズI 型クラスや Module を使っての仕様と実装の分離

@tmiya : Coq 入門, 79

Page 80: Coq Tutorial

8. まとめ

自習用資料I Interactive Theorem Proving and Program Development. Coq’Art:

The Calculus of Inductive Constructions, Yves Berrot and PierreCasteran (Springer-Verlag) : 唯一の書籍。Coq の解説書。

I Certified Programming with Dependent Types, Adam Chlipala(http://adam.chlipala.net/cpdt/) : Coq を用いてプログラムを書く事を目標とした実用的な教科書。

I 「2010年度後期・数理解析・計算機数学 III」(http://www.math.nagoya-u.ac.jp/~garrigue/lecture/2010_AW/index.html) : 名古屋大学の Garrigue 先生の授業テキスト PDF。日本語。自習教材としてお薦め。

I Coq in a Hurry, Yves Bertot(http://cel.archives-ouvertes.fr/docs/00/47/58/07/PDF/coq-hurry.pdf) : 簡潔にまとめられたチュートリアル

I 2nd Asian-Pacific Summer School on Formal Methods(http://formes.asia/cms/coqschool/2010) : サマースクールの教材がダウンロード可能

@tmiya : Coq 入門, 80

Page 81: Coq Tutorial

8. まとめ

Coq 勉強会 ProofCafe

名古屋で毎月第4土曜日に開催されている Coq 勉強会。前述の CPDTをテキストにしています。Coq を実際に業務に使用している ITプランニング社の今井宜洋さん他、名古屋の関数型言語界隈の方が多数参加されています。参加者の方々はCoq から Ruby, Clojure, Scala, Javascript のコードを生成するプログラムを公開するなど Coq の実用面での実績が豊富です。参加の際は予め自分のノートパソコンに Coq を導入して参加する事をお勧めします。開催情報は http://coq.g.hatena.ne.jp/keyword/ProofCafe をご覧下さい。

@tmiya : Coq 入門, 81

Page 82: Coq Tutorial

8. まとめ

形式手法勉強会 Formal Methods Forum

Coq に限らず様々な形式手法についての勉強会を (ほぼ)月1回、豆蔵セミナールーム (新宿)にて行っています。基本的には、参加者同士で自分が知っている話題に付いて話すという勉強会です。また Google groupメーリングリストでも随時質問可能です。Coq については、ほぼ毎回、何らかの話題について話をしています。2010年度は前述の Certified Programming with Dependent Types を皆で読みました。まだ余り成果は多く有りませんが、Coq の証明付きの正規表現ライブラリなどを作成しました。勉強会開催情報やメンバー間の情報交換は Google group(http://groups.google.co.jp/group/fm-forum) にて行っています。参加希望の方は登録をお願いします。参加の際は予め自分のノートパソコンに Coq を導入して参加する事をお勧めします。

@tmiya : Coq 入門, 82