CoqIntroを読む
前回のつづき。POPL08チュートリアルのうちの基礎部分CoqIntro.vをやってみる。
CoqIntroはboolとnatからなる式言語を定義しているが、それが興味深い表現になっている。
言語定義
まず、構文定義
Inductive tm : Set := | tm_true : tm | tm_false : tm | tm_if : tm -> tm -> tm -> tm | tm_zero : tm | tm_succ : tm -> tm | tm_pred : tm -> tm | tm_iszero : tm -> tm.
ここはまあ普通のデータ型定義だ。面白くなるのは次。bool値をあらわすデータの定義
Inductive bvalue : tm -> Prop := | b_true : bvalue tm_true | b_false : bvalue tm_false.
bvalueの型が関数型になっている。まず、普通ならtmと同様Setでやりたいところなので、ここで混乱する。
そこで、いろいろCheckしてみる。
- Check bvalue
- : tm -> Prop
- Check bvalue tm_true
- : Prop
- Check bvalue tm_zero
- : Prop
- Check b_true tm_true
- The expression "b_true" of type "bvalue tm_true" cannot be applied to the term "tm_true" : "tm"
- Check b_true
- : bvalue tm_true
b_true : bvalue tm_true : Propとなってる関係が見える。Propなので即真か偽かになるわけではなく、Propデータと見たほうがいいようだ。Prop->boolにする判定自体はどうするのかまだわからない。
bvalue tm_trueは何らかのデータで、b_trueはそのデータに属すというシンボルとして見てもいいのかな。(bvalue tm_true)を表現するオブジェクトがb_trueというふうに。
また、別の視点でbvalueという型のパラメータとしてtm_trueやtm_falseしか入らないと考えると、なんとなくGADTを思い出す(値そのものは型パラメータに使えなかったとは思うが):
data TrueT data FalseT class BoolT t ... instance BoolT TrueT where ... instance BoolT FalseT where ... data Tm t where TmTrue :: Tm TrueT TmFalse :: Tm FalseT ... data BValue t where BTrue :: BValue (Tm TrueT) BTrue :: BValue (Tm FalseT)
GADTでは、どこかでパラメータ型の値を取り出す関数が定義されるが、これはあまり考えにくいかも。
でつぎが数値のデータ定義
Inductive nvalue : tm -> Prop := | n_zero : nvalue tm_zero | n_succ : forall t, nvalue t -> nvalue (tm_succ t).
bvalueに似ているが、n_succでforallが出てくる。ここでもCheck。
- Check n_zero
- : nvalue tm_zero
- Check nvalue tm_zero
- : Prop
- Check nvalue tm_true
- : Prop
- Check n_succ
- : forall t : tm, nvalue t -> nvalue (tm_succ t)
- Check forall t : tm, nvalue t -> nvalue (tm_succ t)
- Prop
forall t:tm, nvalue t -> nvalue (tm_succ t)がPropになるのは最後のnvalue (tm_succ t)がPropだからだろう。この式自体は任意のtm型のtに対して生成される型で、nvalue tつまりPropから、nvalue (tm_succ t)つまりPropを返す型を生成するという感じなのだろう。ということは、n_succはtm型の式を受け取ることができると予想できる。
- Check n_succ tm_zero
- : nvalue tm_zero -> nvalue (tm_succ tm_zero)
- Check n_succ tm_zero n_zero
- : nvalue (tm_succ tm_zero)
- Check n_succ tm_true
- : nvalue tm_true -> nvalue (tm_succ tm_true)
- Check n_succ tm_true n_zero
- Error: The term "n_zero" has type "nvalue tm_zero" while it is expected to have type "nvalue tm_true"
というように式として成り立てる。n_succ (tm_succ tm_zero)なども同様にできあがってるものとみなせるが、たぶんその第二引数に使える構造は(n_succ tm_zero n_zero)だけになる。
bvalueでつけたProp解釈でいうと、nvalue tにあたるPropを受け取ってnvalue (tm_succ t)にあたるPropを返す関数オブジェクトが(n_succ t)ということになる。
このnvalueを先のようにGADT風に考えるとどうなるかな。
data Zero = Zero data Succ a = Succ a class NatT t where ... instance NatT Zero where ... instance (Nat a) => NatT (Succ a) where ... data Tm t where TmZero :: Tm Zero TmSucc :: Tm a -> Tm (Succ a) ... data NValue t where NZero :: NValue (Tm Zero) NSucc :: (Nat aT) => (Tm a) -> NValue (Tm a) -> NValue (Tm (Succ a))
NSuccの引数はTmなのでそろそろくるしいか。
次はvalueの定義。
Definition value (t : tm) : Prop := bvalue t \/ nvalue t.
これはあまり不思議ではないが、\/はboolの計算ではなくPropの連結で、結果は連結したPropデータ。value tが式かどうかを判定するデータ。bvalueとnvalueでパターン的に無理そうな気もするが
つぎはeval。これも最終的にPropを作るInductiveで定義している。
Inductive eval : tm -> tm -> Prop := | e_iftrue : forall t2 t3, eval (tm_if tm_true t2 t3) t2 | e_iffalse : forall t2 t3, eval (tm_if tm_false t2 t3) t3 | e_if : forall t1 t1' t2 t3, eval t1 t1' -> eval (tm_if t1 t2 t3) (tm_if t1' t2 t3) | e_succ : forall t t', eval t t' -> eval (tm_succ t) (tm_succ t') | e_predzero : eval (tm_pred tm_zero) tm_zero | e_predsucc : forall t, nvalue t -> eval (tm_pred (tm_succ t)) t | e_pred : forall t t', eval t t' -> eval (tm_pred t) (tm_pred t') | e_iszerozero : eval (tm_iszero tm_zero) tm_true | e_iszerosucc : forall t, nvalue t -> eval (tm_iszero (tm_succ t)) tm_false | e_iszero : forall t t', eval t t' -> eval (tm_iszero t) (tm_iszero t').
定義自体には、eval a bがパターンaならbに評価するルールとみなせ、簡約っぽい雰囲気がある。証明としては命題にあたる部分は型におくのはわかってはいるが、直感的に納得しにくいのは、チェックのためのパターンマッチだけでしか使わないというふうにはわりきらず、プログラムのデータ値としてどうなるのか想像しようとしてできにくいところだったりする。
とりあえずCheck.
- Check: eval tm_true tm_zero
- : Prop
- Check e_iftrue tm_zero tm_false
- : eval (tm_if tm_true tm_zero tm_false) tm_zero
- Check eval (tm_if tm_true tm_zero tm_false) tm_zero
- : Prop
bvalue,nvalueと同じような違和感があるが、まあいいか。ここまでくるとGADT風にはもう書けないな。
eval_manyもうよくわからない。
Inductive eval_many : tm -> tm -> Prop := | m_refl : forall t, eval_many t t | m_step : forall t t' u, eval t t' -> eval_many t' u -> eval_many t u.
- Check m_refl tm_true
- : eval_many tm_true tm_true
- Check m_step tm_zero tm_true tm_false
- : eval tm_zero tm_true -> eval_many tm_true tm_false -> eval_many tm_zero tm_false
簡約不能をあらわす正規形になってるかどうかの判定normal_form
Definition normal_form (t : tm) : Prop := ~ exists t', eval t t'.
evalの右側に相当するものが無いと正規形。
証明
定義の後半はよくわからない状態になったけど、証明に進む。
補題1.
Lemma e_succ_pred_succ : forall t, nvalue t -> eval (tm_succ (tm_pred (tm_succ t))) (tm_succ t).
これは簡単だが、前日と違い、autoでは解決しない。
intro t. intro H. apply e_succ. apply e_predsucc. exact H.
coqtopログ
Coq < Lemma e_succ_pred_succ : forall t, nvalue t -> eval (tm_succ (tm_pred (tm_succ t))) (tm_succ t). 1 subgoal ============================ forall t : tm, nvalue t -> eval (tm_succ (tm_pred (tm_succ t))) (tm_succ t) e_succ_pred_succ < intro t. 1 subgoal t : tm ============================ nvalue t -> eval (tm_succ (tm_pred (tm_succ t))) (tm_succ t) e_succ_pred_succ < intro H. 1 subgoal t : tm H : nvalue t ============================ eval (tm_succ (tm_pred (tm_succ t))) (tm_succ t) e_succ_pred_succ < apply e_succ. 1 subgoal t : tm H : nvalue t ============================ eval (tm_pred (tm_succ t)) t e_succ_pred_succ < apply e_predsucc. 1 subgoal t : tm H : nvalue t ============================ nvalue t e_succ_pred_succ < exact H. Proof completed. e_succ_pred_succ < Qed. intro t. intro H. apply e_succ. apply e_predsucc. exact H. e_succ_pred_succ is defined e_succ_pred_succ <
apply e_succは、明示的に引数を指定すると、
apply e_succ with (t := (tm_pred (tm_succ t))) (t' := t)
ということになる。こうすると、
eval (tm_pred (tm_succ t)) t -> eval (tm_succ (tm_pred (tm_succ t))) (tm_succ t)
に変換されるので、->の右辺の現証明対象が成り立つには->の左辺の仮定側が成り立っていればよいことになり、証明対象は仮定側の
eval (tm_pred (tm_succ t)) t
に入れ替わる。
補題2.
Coq < Lemma m_succ_pred_succ : forall t, nvalue t -> eval_many (tm_succ (tm_pred (tm_succ t))) (tm_succ t). 1 subgoal ============================ forall t : tm, nvalue t -> eval_many (tm_succ (tm_pred (tm_succ t))) (tm_succ t) m_succ_pred_succ <
証明。これもautoではむり。
m_succ_pred_succ < intro t. 1 subgoal t : tm ============================ nvalue t -> eval_many (tm_succ (tm_pred (tm_succ t))) (tm_succ t) m_succ_pred_succ < intro H. 1 subgoal t : tm H : nvalue t ============================ eval_many (tm_succ (tm_pred (tm_succ t))) (tm_succ t) m_succ_pred_succ <
intro二回でforallと->が消え、次にパターン的にapply m_stepになるのはわかる。が、m_stepは3引数
| m_step : forall t t' u, eval t t' -> eval_many t' u -> eval_many t u.
であり、t := (tm_succ (tm_pred (tm_succ t))), u := (tm_succ t)はすぐわかるが、t'はパターン内には直接埋まっていないため、明示的にt'を与える必要がある。つまり、eval (tm_succ (tm_pred (tm_succ t))) t'さらにパターンで分解できるようなt'を探す。これは先の補題1 e_succ_pred_succの->の右辺が、eval (tm_succ (tm_pred (tm_succ t))) (tm_succ t) であることを覚えていれば、t' := tm_succ tを与えればいいことがわかる。
m_succ_pred_succ < apply m_step with (t' := (tm_succ t)). 2 subgoals t : tm H : nvalue t ============================ eval (tm_succ (tm_pred (tm_succ t))) (tm_succ t) subgoal 2 is: eval_many (tm_succ t) (tm_succ t) m_succ_pred_succ <
m_stepには->が二つあるので満たさなくてはいけない仮定であるsubgoalは二つできる。
また、applyはパターンとしても書けて、
apply (m_step (tm_succ (tm_pred (tm_succ t))) (tm_succ t) (tm_succ t)) .
でもいい。
あとはそのまま、
m_succ_pred_succ < apply e_succ_pred_succ. 2 subgoals t : tm H : nvalue t ============================ nvalue t subgoal 2 is: eval_many (tm_succ t) (tm_succ t) m_succ_pred_succ < exact H. 1 subgoal t : tm H : nvalue t ============================ eval_many (tm_succ t) (tm_succ t) m_succ_pred_succ < apply m_refl. Proof completed. m_succ_pred_succ < Qed. auto. intro t. intro H. apply m_step with (t' := tm_succ t). apply e_succ_pred_succ. exact H. apply m_refl. m_succ_pred_succ is defined Coq <
練習用補題
証明戦略としては
- introできるまでintro
- その後は定義や補題からゴールの形になっているパターンを探してapplyしていく
- applyする場合で引数が必要なときは、次のパターン展開できそうなパターンを探して使う
- ゴールと同じものが仮定中にあれば、exact
Coq < Lemma m_one : forall t1 t2, eval t1 t2 -> eval_many t1 t2. 1 subgoal ============================ forall t1 t2 : tm, eval t1 t2 -> eval_many t1 t2 m_one < intros t1 t2 H. 1 subgoal t1 : tm t2 : tm H : eval t1 t2 ============================ eval_many t1 t2 m_one < auto. 1 subgoal t1 : tm t2 : tm H : eval t1 t2 ============================ eval_many t1 t2 m_one < apply (m_step t1 t2 t2). 2 subgoals t1 : tm t2 : tm H : eval t1 t2 ============================ eval t1 t2 subgoal 2 is: eval_many t2 t2 m_one < exact H. 1 subgoal t1 : tm t2 : tm H : eval t1 t2 ============================ eval_many t2 t2 m_one < apply (m_refl t2). Proof completed. m_one < Qed. intros t1 t2 H. auto. apply (m_step t1 t2 t2). exact H. apply (m_refl t2). m_one is defined Coq <
Coq < Lemma m_two : forall t1 t2 t3, eval t1 t2 -> eval t2 t3 -> eval_many t1 t3. 1 subgoal ============================ forall t1 t2 t3 : tm, eval t1 t2 -> eval t2 t3 -> eval_many t1 t3 m_two < intros t1 t2 t3 H H1. 1 subgoal t1 : tm t2 : tm t3 : tm H : eval t1 t2 H1 : eval t2 t3 ============================ eval_many t1 t3 m_two < apply (m_step t1 t2 t3). 2 subgoals t1 : tm t2 : tm t3 : tm H : eval t1 t2 H1 : eval t2 t3 ============================ eval t1 t2 subgoal 2 is: eval_many t2 t3 m_two < exact H. 1 subgoal t1 : tm t2 : tm t3 : tm H : eval t1 t2 H1 : eval t2 t3 ============================ eval_many t2 t3 m_two < apply (m_step t2 t3 t3). 2 subgoals t1 : tm t2 : tm t3 : tm H : eval t1 t2 H1 : eval t2 t3 ============================ eval t2 t3 subgoal 2 is: eval_many t3 t3 m_two < exact H1. 1 subgoal t1 : tm t2 : tm t3 : tm H : eval t1 t2 H1 : eval t2 t3 ============================ eval_many t3 t3 m_two < apply (m_refl t3). Proof completed. m_two < Qed. intros t1 t2 t3 H H1. apply (m_step t1 t2 t3). exact H. apply (m_step t2 t3 t3). exact H1. apply (m_refl t3). m_two is defined Coq <
Coq < Lemma m_iftrue_step : forall t t1 t2 u, eval t tm_true -> eval_many t1 u -> eval_many (tm_if t t1 t2) u. 1 subgoal ============================ forall t t1 t2 u : tm, eval t tm_true -> eval_many t1 u -> eval_many (tm_if t t1 t2) u m_iftrue_step < intros t t1 t2 u H H1. 1 subgoal t : tm t1 : tm t2 : tm u : tm H : eval t tm_true H1 : eval_many t1 u ============================ eval_many (tm_if t t1 t2) u m_iftrue_step < apply (m_step (tm_if t t1 t2) (tm_if tm_true t1 t2) u). 2 subgoals t : tm t1 : tm t2 : tm u : tm H : eval t tm_true H1 : eval_many t1 u ============================ eval (tm_if t t1 t2) (tm_if tm_true t1 t2) subgoal 2 is: eval_many (tm_if tm_true t1 t2) u m_iftrue_step < apply e_if. 2 subgoals t : tm t1 : tm t2 : tm u : tm H : eval t tm_true H1 : eval_many t1 u ============================ eval t tm_true subgoal 2 is: eval_many (tm_if tm_true t1 t2) u m_iftrue_step < exact H. 1 subgoal t : tm t1 : tm t2 : tm u : tm H : eval t tm_true H1 : eval_many t1 u ============================ eval_many (tm_if tm_true t1 t2) u m_iftrue_step < apply (m_step (tm_if tm_true t1 t2) t1 u). 2 subgoals t : tm t1 : tm t2 : tm u : tm H : eval t tm_true H1 : eval_many t1 u ============================ eval (tm_if tm_true t1 t2) t1 subgoal 2 is: eval_many t1 u m_iftrue_step < apply e_iftrue. 1 subgoal t : tm t1 : tm t2 : tm u : tm H : eval t tm_true H1 : eval_many t1 u ============================ eval_many t1 u m_iftrue_step < exact H1. Proof completed. m_iftrue_step < Qed. intros t t1 t2 u H H1. apply (m_step (tm_if t t1 t2) (tm_if tm_true t1 t2) u). apply e_if. exact H. apply (m_step (tm_if tm_true t1 t2) t1 u). apply e_iftrue. exact H1. m_iftrue_step is defined Coq <
ちなみにcoqtopでのアンドゥはUndo.になる。
今日は、ここまで。