More than 5 years have passed since last update.
「The Little Prover」のCoqでの実現---「定理証明手習い」の「公理」をCoqで証明してみた
「The Little Prover」のCoqでの実現
2017/08/02
2017/08/07 シンボルをstringを使うようにした。
2017/08/09 case: eqP を使うようにした。
2017/08/11 prove_nil タクティクを完成した。
2017/10/21 「定理証明手習い」発刊記念。
2019/07/24 Stringの定義をマージした。
@suharahiromichi
この文書のソースコードは以下にあります。
はじめに
「The Little Prover」(以下 TLP)[1] を読んで、Coqの上でそれを実現しようとした話です。
TLPで証明しているプログラムをGallinaに移植して証明することは簡単です[2]。
しかし、これからやろうとするのは、TLPの対象言語であるLispの「意味」をCoqで実現しようと思います。
それによって、
-
Coqはinductiveに定義したデータ型はinductionができることを原理としています。
これに対して、TLPはinductionはハードコードされています。
結果として、帰納法の公理からすべての定理を導くことができず、多くの「公理」を導入しています。
帰納法の公理を使って、TLPの「公理」を証明することで、TLPの正しさが検証できるでしょう。 -
TLPはLispのプログラム(当然S式を返す)がnilでないことを証明する。
これをもって、そのプログラムの意味する論理式が真であるとします。
Coqは論理式(Prop型)でなければ証明できないので、
これをLispのプログラムをCoqの論理式に埋め込むことで実現します。
CoqのMathcomp/SSReflect拡張を使用しているので、それについては[3]を参照してください。また、見ただけでは解らないようなタクティカル("by case=> -> ->" など) の使用は避けています。
FrommathcompRequireImportall_ssreflect.RequireImportString.SetImplicitArguments.UnsetStrictImplicit.UnsetPrintingImplicitDefensive.SetPrintAll.文字列の定義
バニラCoqの定義を使用し、eqTypeのインスタンスにすることで、「==」が使えるようになります。
Definitionstring_eqMixin:=@EqMixinstringString.eqbString.eqb_spec.Canonicalstring_eqType:=@EqTypestringstring_eqMixin.OpenScopestring_scope.Check"aaa"="aaa":Prop.Check"aaa"=="aaa":bool.Check"aaa"=="aaa":Prop.SectionTLP.S式の定義
S式をInductiveなデータ型として定義します。ここでは、S式にことをTLPにあわせて Star型と呼ぶことにします。
Atomic
アトムどうしのbooleanの等式を定義したうえで、それが、論理式(ライプニッツの等式、Propの等式)と同値であることを証明することで、boolとPropの間での行き来ができるようになります。つまり、「==」と「=」の両方を使うことができるようになります。これをリフレクションといいます[4]。
アトムとしては、シンボルの他に自然数もとれるようにします。
Inductiveatomic:Type:=|ATOM_NAT(n:nat)|ATOM_SYM(s:string).DefinitioneqAtom(ab:atomic):bool:=match(a,b)with|(ATOM_NATn,ATOM_NATm)=>n==m|(ATOM_SYMs,ATOM_SYMt)=>s==t(* eqString *)|_=>falseend.Lemmaatomic_eqP:forall(xy:atomic),reflect(x=y)(eqAtomxy).Proof.move=>xy.apply:(iffPidP).-case:x;case:y;rewrite/eqAtom=>xy;move/eqP=>H;by[rewriteH|||rewriteH].-move=>H;rewriteH.case:yH=>nH1;byrewrite/eqAtom.Qed.Definitionatomic_eqMixin:=@EqMixinatomiceqAtomatomic_eqP.Canonicalatomic_eqType:=@EqTypeatomicatomic_eqMixin.List型 (L-EXP)
Inductivelist:Type:=|L_ATOMofatomic|L_CONSofatomic&list.Star型 (S-EXP)
「Star型は、アトム、または、Star型のふたつ要素を連結(Cons)したもの」と再帰的に定義できます。これがinductiveなデータ型です。
Inductivestar:Type:=|S_ATOMofatomic|S_CONSofstar&star.Coqはinductiveなデータ型に対して、inductionできるようになります。そのために、star_ind という公理が自動的に定義されます。これは、TLPの第7賞で説明されている "star induction" と同じものです。
Coqによる証明でも、この公理を直接使用することはなく、star型のデータに対して、inductionタクティクまたはelimタクティクを使用すると、この公理が適用されます。
Checkstar_ind:forallP:star->Prop,(foralla:atomic,P(S_ATOMa))->(forallx:star,Px->forally:star,Py->P(S_CONSxy))->foralls:star,Ps.Star型についても、booleanの等号を定義して、論理式の等号にリフレクションできるようにします。
FixpointeqStar(xy:star):bool:=match(x,y)with|(S_ATOMa,S_ATOMb)=>a==b(* eqAtom *)|(S_CONSx1y1,S_CONSx2y2)=>eqStarx1x2&&eqStary1y2|_=>falseend.LemmaeqConsxyx'y':(x=x'/\y=y')->S_CONSxy=S_CONSx'y'.Proof.case=>HxHy.byrewriteHxHy.Qed.Lemmastar_eqP_1:forall(xy:star),eqStarxy->x=y.Proof.elim.-move=>a.elim=>b.+move/eqP=>H.(* ATOM どうし *)byrewriteH.+done.(* ATOM と CONS *)-move=>xHxyHy.elim.+done.(* CONS と ATOM *)+move=>x'IHxy'IHy.(* CONS と CONS *)move/andP.case=>Hxx'Hyy'.apply:eqCons.split.*byapply:(Hxx').*byapply:(Hyy').Qed.Lemmastar_eqP_2:forall(xy:star),x=y->eqStarxy.Proof.move=>xyH;rewrite-H{H}.elim:x.-bymove=>a/=.-move=>xHxy'Hy/=.byapply/andP;split.Qed.Lemmastar_eqP:forall(xy:star),reflect(x=y)(eqStarxy).Proof.move=>xy.apply:(iffPidP).-byapply:star_eqP_1.-byapply:star_eqP_2.Qed.Definitionstar_eqMixin:=@EqMixinstareqStarstar_eqP.Canonicalstar_eqType:=@EqTypestarstar_eqMixin.Star型のboolの等式について、反射律と対称律が成立することを証明ておきます。リフレクションを使用してPropの等式に変換することで、簡単に証明できます。
この補題はあとで使用します。
Lemmarefl_eqStar(x:star):(x==x).Proof.byapply/eqP.Qed.Lemmasymm_eqStar(xy:star):(x==y)=(y==x).Proof.byapply/idP/idP;move/eqP=>H;rewriteH.Qed.シンボルをS式の中に書くときに簡単になるような略記法を導入します。「'T」などと書くことができるので、quoted literal のように見えますが、「'」は記法(notation)の一部であることに注意してください。
Definitions_quote(s:string):star:=(S_ATOM(ATOM_SYMs)).Notation"\' s":=(S_ATOM(ATOM_SYMs))(atlevel60).Notation"'T":=(S_ATOM(ATOM_SYM"T")).Notation"'NIL":=(S_ATOM(ATOM_SYM"NIL")).Notation"'FOO":=(S_ATOM(ATOM_SYM"FOO")).Notation"'BAR":=(S_ATOM(ATOM_SYM"BAR")).Notation"'BAZ":=(S_ATOM(ATOM_SYM"BAZ")).Notation"'?":=(S_ATOM(ATOM_SYM"?")).Check'NIL:star.Check\'"aaa":star.Check\'"FOO":star.Check'FOO:star.Compute\'"FOO"=='FOO.(* true *)自然数については、(S式の)アトムとの相互変換をする関数を用意します。
Definitions2n(x:star):nat:=matchxwith|S_ATOM(ATOM_NATn)=>n|_=>0end.Definitionn2s(n:nat):star:=S_ATOM(ATOM_NATn).組込関数の定義
DefinitionCONS(xy:star):=S_CONSxy.DefinitionCAR(x:star):star:=matchxwith|S_ATOM_=>'NIL|S_CONSx_=>xend.DefinitionCDR(x:star):star:=matchxwith|S_ATOM_=>'NIL|S_CONS_y=>yend.DefinitionATOM(x:star):star:=matchxwith|S_ATOM_=>'T|S_CONS__=>'NILend.IFとEQUALは、booleanの等式で判定します。
「IF」というラベルが使えないようなので、「_IF」とします。
Definition_IF(qae:star):star:=ifq=='NILtheneelsea.DefinitionEQUAL(xy:star):star:=ifx==ythen'Telse'NIL.Fixpoints_size(x:star):nat:=matchxwith|S_CONSab=>s_sizea+s_sizeb+1|_=>0end.DefinitionSIZE(x:star):star:=S_ATOM(ATOM_NAT(s_sizex)).EvalcomputeinSIZE(S_CONS'T(S_CONS'T'T)).(* S_ATOM (ATOM_NAT 2) *)DefinitionLT(xy:star):star:=ifs2nx<s2nythen'Telse'NIL.EvalcomputeinLT(n2s2)(n2s3).(* 'T *)DefinitionPLUS(xy:star):star:=n2s(s2nx+s2ny).EvalcomputeinPLUS(n2s2)(n2s3).(* S_ATOM (ATOM_NAT 5) *)埋め込み
S式を論理式(Prop)に埋め込めるようにします。このとき、Lispの真偽の定義から、
「真」 iff 「'NILでないS式」
としなければいけません。実際には、S式からbooleanの等式 (x != 'NIL) へのコアーションを定義します。これは、一旦boolを経由することで、論理式(Prop)の否定も扱えるようにするためです。
Coercionis_not_nil(x:star):bool:=x!='NIL.(* ~~ eqStar x 'NIL *)Check(ATOM'T):star.Check(ATOM'T):Prop.Check~(ATOM'T):Prop.Check(ATOM(CONS'T'T)):star.Check(ATOM(CONS'T'T)):Prop.Check~(ATOM(CONS'T'T)):Prop.Set Printing Coercions.を使うと、コアーションがどのように使われているか分かります。
Checkis_true(is_not_nil(ATOM'T)):Prop.Check~is_true(is_not_nil(ATOM'T)):Prop.のちの証明で便利なように、'NILに関する補題を証明しておきます。
_ != _ は ~~(_ == _) の略記なので、次はis_not_nil と同じことです。が、忘れがちなので定義しておきます。
Lemmanot_nil_Eq:~~(q=='NIL)=q.Proof.done.Qed.二重否定を解消するための補題です。
Lemmanot_not_nil__nil_Eq:~~(q!='NIL)=(q=='NIL).Proof.bycase:(q=='NIL).Qed.Lemmanot_q__q_nil_E(q:star):~q<->q='NIL.Proof.split.-rewrite/is_not_nil=>/negP.byrewritenot_not_nil__nil_E=>/eqP.-bymove=>Hq;rewriteHq.Qed.Lemmaq__q_not_nil_E(q:star):q<->q<>'NIL.Proof.rewrite/is_not_nil.split.-move=>H.byapply/eqP.-bymove/eqP.Qed.タクティク
case_if (不使用)
_IFやEQUALを分解するためのタクティクを定義します。このなかではコアーションが機能しないことに注意してください。
ほとんどの場合、case: eqP または case: ifP で置き換えることができるので、使用していない。
Ltaccase_if:=matchgoalwith|[|-is_true(is_not_nil(if?Xthen_else_))]=>caseHq:X=>//end.prove_nil
ゴールおよび前提の NIL に関する命題を q=NIL または q<>NIL に変換するものです。前提とゴールが同じでなくても、前提に q=NILとq<>NILの両方があるなら、矛盾からdoneで証明が終わることができます。
なお、~~(q==NIL) は q!=NIL と同じなので、条件としてあらわれません。
Ltacprove_nil:=repeatmatchgoalwith|[H:is_true(?q!='NIL)|-_]=>move/eqPinH|[H:is_true(is_not_nil?q)|-_]=>move/q__q_not_nil_EinH|[H:is_true(?q=='NIL)|-_]=>move/eqPinH|[H:~is_true(is_not_nil?q)|-_]=>move/not_q__q_nil_EinH|[|-is_true(?q!='NIL)]=>apply/eqP|[|-is_true(is_not_nil?q)]=>apply/q__q_not_nil_E|[|-is_true(?q=='NIL)]=>apply/eqP|[|-~is_true(is_not_nil?q)]=>apply/not_q__q_nil_Eend.IFとEQUALの補題
TLPの定理は、(EQUAL X Y) または (IF Q 'T (EQUAL X Y)) または (IF Q (EQUAL X Y) 'T)のかたちをしているので、それをCoqの等式と同値であることを証明しておきます。これらは、TLPの定理の証明や、その定理を使うときに使用します。
Lemmaequal_eq{xy:star}:(EQUALxy)->x=y.Proof.rewrite/EQUAL.bycase:eqP.Qed.LemmaifAP{qxy:star}:(_IFq(EQUALxy)'T)<->(q->x=y).Proof.split.-rewrite/_IF/EQUAL.case:eqP=>Hq_nil.+move=>_Hq.byprove_nil.+bycase:eqP.-move=>H.rewrite/_IF;case:eqP=>//Hnot_nil_q.(* q <> NIL *)rewrite/EQUAL;case:ifP=>//=>Hx_ne_y.(* x <> y) *)exfalso.applynegbTinHx_ne_y.move/negPinHx_ne_y.apply:Hx_ne_y.apply/eqP.apply:H.move/eqPinHnot_nil_q.byprove_nil.Qed.LemmaifEP{qxy:star}:(_IFq'T(EQUALxy))<->(~q->x=y).Proof.split.-rewrite/_IF/EQUAL.case:eqP=>Hq_nil.+bycase:eqP.+move=>_Hnq.byprove_nil.-move=>H.rewrite/_IF;case:eqP=>//Hq_nil.(* q = NIL *)rewrite/EQUAL;case:eqP=>//=>Hx_ne_y.(* x <> y) *)exfalso.apply:Hx_ne_y.apply:H.byprove_nil.Qed.(*
Set Printing Coercions.
*)J-Bobの「公理」の証明
ここまでに用意した道具を使って、証明をおこないます。
Theoremequal_same(x:star):(EQUALxx).Proof.rewrite/EQUAL.byrewriterefl_eqStar.Qed.Theorematom_cons(xy:star):(EQUAL(ATOM(CONSxy))'NIL).Proof.done.Qed.Theoremcar_cons(xy:star):(EQUAL(CAR(CONSxy))x).Proof.rewrite/EQUAL.bycase:eqP.Qed.Theoremcdr_cons(xy:star):(EQUAL(CDR(CONSxy))y).Proof.rewrite/EQUAL.bycase:eqP.Qed.Theoremequal_swap(xy:star):(EQUAL(EQUALxy)(EQUALyx)).Proof.rewrite{2}/EQUAL{2}/EQUAL.rewrite{1}symm_eqStar.byrewriteequal_same.Qed.Theoremequal_if(xy:star):(_IF(EQUALxy)(EQUALxy)'T).Proof.byrewrite/_IF;case:eqP;move/eqP.Qed.Theoremif_true(xy:star):(EQUAL(_IF'Txy)x).Proof.byrewrite/EQUAL;case:eqP.Qed.Theoremif_false(xy:star):(EQUAL(_IF'NILxy)y).Proof.byrewrite/EQUAL;case:eqP.Qed.Lemmal_if_same(xy:star):(_IFxyy)=y.Proof.case:x.-case.+done.(* NAT *)+rewrite/_IF=>s.bycase:eqP.(* SYM *)-done.(* CONS *)Qed.Theoremif_same(xy:star):(EQUAL(_IFxyy)y).Proof.rewritel_if_same.byrewriteequal_same.Qed.Theoremif_nest_A(xyz:star):(_IFx(EQUAL(_IFxyz)y)'T).Proof.rewrite/_IF;case:eqP=>//.byrewriteequal_same.Qed.Theoremif_nest_E(xyz:star):(_IFx'T(EQUAL(_IFxyz)z)).Proof.rewrite/_IF;case:eqP=>//.byrewriteequal_same.Qed.Lemmal_cons_car_cdr(x:star):(ATOMx)='NIL->(CONS(CARx)(CDRx))=x.Proof.move=>Hn.caseHc:x;rewrite/CONS=>//.byrewriteHcinHn.(* 前提の矛盾 *)Qed.Theoremcons_car_cdr(x:star):(_IF(ATOMx)'T(EQUAL(CONS(CARx)(CDRx))x)).Proof.rewrite/_IF;case:eqP=>Hq//.rewritel_cons_car_cdr//.byrewriteequal_same.Qed.Lemmal_size_car(x:star):ATOMx='NIL->s_size(CARx)<s_sizex.Proof.elim:x.-bymove=>aHc//.-move=>xHxyHyHc/=.(* s_size x < s_size x + s_size y + 1 *)byrewriteaddn1ltnSleq_addr.Qed.Lemmal_size_cdr(x:star):ATOMx='NIL->s_size(CDRx)<s_sizex.Proof.elim:x.-bymove=>aHc//.-move=>xHxyHyHc/=.(* s_size y < s_size x + s_size y + 1 *)rewrite[s_sizex+s_sizey]addnC.byrewriteaddn1ltnSleq_addr.Qed.Theoremsize_car(x:star):(_IF(ATOMx)'T(EQUAL(LT(SIZE(CARx))(SIZEx))'T)).Proof.apply/ifEP=>Hq.rewrite/LT/=l_size_car.-by[].-byapply/not_q__q_nil_E.Qed.Theoremsize_cdr(x:star):(_IF(ATOMx)'T(EQUAL(LT(SIZE(CDRx))(SIZEx))'T)).Proof.apply/ifEP=>Hq.rewrite/LT/=l_size_cdr.-by[].-byapply/not_q__q_nil_E.Qed.「公理」を書換規則として使う
TLPでは、以上の「公理」を問題プログラムの 書換 に使用します。Coqの場合、書換 は、X = Y または Q -> (X = Y) のかたちでなければなりません。後者の場合、書換えにともなって、新しいゴールとしてQが追加されます。
「公理」の書換への変換
EQUALのかたちをした「公理」を等式に変換するには、equal_eq を使います。
SectionTLP_REWRITE_CHECK.Variablesqxy:star.SectionTLP_REWRITE_CHECK_0.Variablee:(EQUALxy).Checkequal_eqe:x=y.EndTLP_REWRITE_CHECK_0._IF式かたちをした「公理」を等式に変換するには、(iffLR ifAP) または (iffLR ifEP) を使います。
SectionTLP_REWRITE_CHECK_1.(** (_IF Q (EQUAL X Y) 'T) の場合。 *)Variableifa:(_IFq(EQUALxy)'T).CheckiffLRifAPifa:q->x=y.EndTLP_REWRITE_CHECK_1.SectionTLP_REWRITE_CHECK_2.(** (_IF Q 'T (EQUAL X Y)) の場合。 *)Variableife:(_IFq'T(EQUALxy)).CheckiffLRifEPife:~q->x=y.EndTLP_REWRITE_CHECK_2.EndTLP_REWRITE_CHECK.書換の例
SectionTLP_REWRITE_SAMPLE.Variablesxyz:star.Checkequal_eq(equal_samex):x=x.CheckiffLRifAP(if_nest_Axyz):x->(_IFxyz)=y.CheckiffLRifEP(size_cdrx):~ATOMx->(LT(SIZE(CDRx))(SIZEx))='T.EndTLP_REWRITE_SAMPLE.EndTLP.参考文献
[1] Daniel P. Friedman, Carl Eastlund, "The Little Prover", MIT Press, 2015.
https://mitpress.mit.edu/books/little-prover
中野圭介監訳、「定理証明手習い」、ラムダノート、2017。
https://www.lambdanote.com/collections/littleprover
[2] 「The Little Prover の memb?/remb をCoqで解いてみる(サブリスト改訂版)」
https://github.com/suharahiromichi/coq/blob/master/prog/coq_membp_remb_3.v
[3] Mathematical Components resources
http://ssr.msr-inria.inria.fr/
[4] 「リフレクションのしくみをつくる」
http://qiita.com/suharahiromichi/items/9cd109386278b4a22a63
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme
