3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TAPLの3章の問題をRocq(Coq)で形式化

3
Posted at

この記事は株式会社 proof ninja の助成を受けて書いています.

研究室の輪読で Benjamin Pierce, Types and Programming Languages (通称TAPL) の3章を読んでいるときに,証明が厄介な部分があったので,Rocq (Coq) をうまく使うと証明が楽になるぞという例として書いてみました.

今回は3章の真偽値と if 式のみがあるシンプルな型なし言語が対象です.言語と簡約規則を以下で定めます.

Inductive term :=
  Tru
| Fls
| IfThenElse : term -> term -> term -> term.

Notation "'If' c1 'Then' c2 'Else' c3" := (IfThenElse c1 c2 c3)
(at level 200, right associativity, format
"'[v   ' 'If'  c1 '/' '[' 'Then'  c2  ']' '/' '[' 'Else'  c3 ']' ']'").

Reserved Notation "x --> y" (at level 60).

Inductive step : term -> term -> Prop :=
  EIfTrue : forall t2 t3, (If Tru Then t2 Else t3) --> t2
| EIfFalse : forall t2 t3, (If Fls Then t2 Else t3) --> t3
| EIf : forall t1 t1' t2 t3, t1 --> t1' -> (If t1 Then t2 Else t3) --> (If t1' Then t2 Else t3)
where "x --> y" := (step x y).

term が今回扱う項を定め,step x y は項 x が項 y に簡約されることを意味します.
x --> ystep x y を表すように notation を作っています.

では,この簡約の行き先が一意であること,すなわち deterministic であることを示します.

Theorem step_deterministic : forall t t', t --> t' -> forall t'', t --> t'' -> t' = t''.

教科書では t --> t' の帰納法と場合分けを駆使した証明が書いてあるのですが,この手の場合分けは機械的にできるので機械的にやりましょう.

まず,TruFls はこれ以上簡約されないので Tru --> tFls --> t といった場合はありえません.また,(If t1 Then t2 Else t3) --> tEIf よりさらに場合分けができます.
Tru --> t(If t1 Then t2 Else t3) --> t の型を持った仮定 H があるときは inversion H で証明を進められます.この処理を Ltac で書きます.

Ltac auto_inversion0 :=
    match goal with
      H : step Tru _ |- _ => inversion H
    | H : step Fls _ |- _ => inversion H
    | H : step (IfThenElse _ _ _) _ |- _ => inversion H
    end.

一旦この auto_inversion0 を使って証明を進めてみます.

Theorem step_deterministic : forall t t', t --> t' -> forall t'', t --> t'' -> t' = t''.
Proof.
  intros t t' H.
  induction H; intros; auto_inversion0.
  t2, t3, t'' : term
  H : (If Tru
          Then t2 
          Else t3) --> t''
  t0, t1 : term
  H1 : t0 = t2
  H2 : t1 = t3
  H0 : t2 = t''
  ============================
  t'' = t''

goal 2 (ID 147) is:
 t2 = (If t1'
          Then t2 
          Else t3)
goal 3 (ID 195) is:
 t'' = t''
goal 4 (ID 244) is:
 t3 = (If t1'
          Then t2 
          Else t3)
goal 5 (ID 292) is:
 (If t1'
     Then t'' 
     Else t3) = t''
goal 6 (ID 334) is:
 (If t1'
     Then t2 
     Else t'') = t''
goal 7 (ID 383) is:
 (If t1'
     Then t2 
     Else t3) = (If t1'0
                    Then t2 
                    Else t3)

一個目のサブゴールは自明な等式です.
二個目のサブゴールの仮定を見ると Tru --> t1' のような仮定があり,これも auto_inversion0 で処理できることがわかります.
ここで,auto_inversion0 のパターンマッチに当てはまるような仮定がなくなるまで inversion を繰替えすように Ltac を書き換えてみます.

Ltac auto_inversion1 :=
  repeat match goal with
      H : step Tru _ |- _ => inversion H
    | H : step Fls _ |- _ => inversion H
    | H : step (IfThenElse _ _ _) _ |- _ => inversion H; clear H; auto
    end.

match の前に repeat を追加し,さらに三つ目の分岐のときに clear H; auto を追加しています.
clear H を追加しないと H : step (IfThenElse _ _ _) _ の形の仮定を無限に inversion してしまい,repeat が停止しなくなってしまいます.
auto は自明なサブゴールを消すためについでに入れてみました.

auto_inversion1 を使って証明をやり直してみましょう.

Theorem step_deterministic : forall t t', t --> t' -> forall t'', t --> t'' -> t' = t''.
Proof.
  intros t t' H.
  induction H; intros; auto_inversion1.
3 goals (ID 466)
  
  t1, t1', t2, t3 : term
  H : t1 --> t1'
  IHstep : forall t'' : term, t1 --> t'' -> t1' = t''
  t'', t0, t4 : term
  H2 : Tru = t1
  H3 : t0 = t2
  H4 : t4 = t3
  H1 : t2 = t''
  ============================
  (If t1'
      Then t'' 
      Else t3) = t''

goal 2 (ID 467) is:
 (If t1'
     Then t2 
     Else t'') = t''
goal 3 (ID 468) is:
 (If t1'
     Then t2 
     Else t3) = (If t1'0
                    Then t2 
                    Else t3)

一個目のサブゴールは何が起きているでしょうか.仮定の H2 の行から等式が続いており,見にくいので,こういうときは私は subst タクティクで一旦整理します.
subst した後のサブゴールは以下です.

3 goals (ID 482)
  
  t1', t3 : term
  IHstep : forall t'' : term, Tru --> t'' -> t1' = t''
  H : Tru --> t1'
  t'' : term
  ============================
  (If t1'
      Then t'' 
      Else t3) = t''

goal 2 (ID 467) is:
 (If t1'
     Then t2 
     Else t'') = t''
goal 3 (ID 468) is:
 (If t1'
     Then t2 
     Else t3) = (If t1'0
                    Then t2 
                    Else t3)

subst した後は Tru --> t1' が仮定に現れました.これも自動で処理したいところです.
そのために auto_inversion1 の中に subst も追加します.

Ltac auto_inversion2 :=
   repeat match goal with
      H : step Tru _ |- _ => inversion H
    | H : step Fls _ |- _ => inversion H
    | H : step (IfThenElse _ _ _) _ |- _ => inversion H; clear H; auto; subst
    end.
    
Theorem step_deterministic : forall t t', t --> t' -> forall t'', t --> t'' -> t' = t''.
Proof.
  intros t t' H.
  induction H; intros; auto_inversion2.
1 goal (ID 505)
  
  t1, t1', t2, t3 : term
  H : t1 --> t1'
  IHstep : forall t'' : term, t1 --> t'' -> t1' = t''
  t1'0 : term
  H5 : t1 --> t1'0
  ============================
  (If t1'
      Then t2 
      Else t3) = (If t1'0
                     Then t2 
                     Else t3)

サブゴールが一個だけになりました.この結論の両辺は t1't1'0 の違い除いて等しいです.このサブゴールを t1' = t1'0 に帰着させるには f_equal タクティクが便利です.
以下が f_equal 適用後です.

  t1, t1', t2, t3 : term
  H : t1 --> t1'
  IHstep : forall t'' : term, t1 --> t'' -> t1' = t''
  t1'0 : term
  H5 : t1 --> t1'0
  ============================
  t1' = t1'0

このサブゴールは帰納法の仮定である IHstep で証明できます.普通に証明を書いて終わらせてもいいですが,折角なので auto_inversion を最後のステップも証明できるように少し改造します.

Ltac auto_inversion3 :=
   repeat match goal with
      H : step Tru _ |- _ => inversion H
    | H : step Fls _ |- _ => inversion H
    | H : step (IfThenElse _ _ _) _ |- _ => inversion H; clear H; subst; try f_equal; auto
    end.

subst の後に try f_equal を追加し,さらに元々あった auto の位置を変えて最後に持ってきました.try tac はゴールにタクティク tac が適用できるならば適用し,できないなら何もしないという意味です.
これを使うと,以下のようになります.

Theorem step_deterministic : forall t t', t --> t' -> forall t'', t --> t'' -> t' = t''.
Proof.
  intros t t' H.
  induction H; intros; auto_inversion3.
Qed.

すべて証明が終わりました.
このような半自動の証明方法では「この場合がまだ残っていて…」「この場合は示すべきことがこういう形になって…」というようなことを一切考えておらず,とっても楽です.


次に,Exercise 3.5.13 (2)を考えていきます.この問題では E-Funny2 という新しい規則を追加した簡約を考えるので,Rocq でも書いてみます.

Reserved Notation "x -->' y" (at level 60).

Inductive step' : term -> term -> Prop :=
  EIfTrue' : forall t2 t3, (If Tru Then t2 Else t3) -->' t2
| EIfFalse' : forall t2 t3, (If Fls Then t2 Else t3) -->' t3
| EIf' : forall t1 t1' t2 t3, t1 -->' t1' -> (If t1 Then t2 Else t3) -->' (If t1' Then t2 Else t3)
| EFunny2 : forall t1 t2 t2' t3,
    t2 -->' t2' -> (If t1 Then t2 Else t3) -->' (If t1 Then t2' Else t3)
where "x -->' y" := (step' x y).

今回はこの step' が局所合流性をみたすことを示します.実際には局所合流性より少し強い以下を示します.

Lemma step'_loc_conf : forall t s s',
    t -->' s -> t -->' s' ->
    exists u, (s = u /\ s' = u) \/ (s = u /\ s' -->' u)
              \/ (s -->' u /\ s' = u) \/ (s -->' u /\ s' -->' u).

この証明はまず t -->' sに関する帰納法を命題

forall s', t -->' s' ->
    exists u, (s = u /\ s' = u) \/ (s = u /\ s' -->' u)
              \/ (s -->' u /\ s' = u) \/ (s -->' u /\ s' -->' u).

に適用します.やってみれば分かるのですが,示す命題を forall s's' を全称量化しないと失敗します.
また,先ほどの auto_inversion3 を,step ではなく新しい step' にも適用できるように少しタクティクを書き換えます.

Ltac auto_inversion step :=
   repeat match goal with
      H : step Tru _ |- _ => inversion H
    | H : step Fls _ |- _ => inversion H
    | H : step (IfThenElse _ _ _) _ |- _ => inversion H; clear H; subst; try f_equal; auto
    end.

auto_inversion の引数として step を渡すようにしました.これで step' に対応させるには auto_inversion step' のようにしてやればよいです.

Lemma step'_loc_conf : forall t s s',
    t -->' s -> t -->' s' ->
    exists u, (s = u /\ s' = u) \/ (s = u /\ s' -->' u)
              \/ (s -->' u /\ s' = u) \/ (s -->' u /\ s' -->' u).
Proof.
  intros t s s' H.
  revert s'.
  induction H; intros; auto_inversion step'.

revert s's' を全称量化してから帰納法をし,auto_inversion step' をしました.
サブゴールは以下のようになります.

10 goals (ID 247)
  
  t3, s' : term
  ============================
  exists u : term,
    s' = u /\ s' = u \/ s' = u /\ s' -->' u \/ s' -->' u /\ s' = u \/ s' -->' u /\ s' -->' u

goal 2 (ID 255) is:
 exists u : term,
   t2 = u /\ (If Tru
                 Then t2' 
                 Else t3) = u \/
   t2 = u /\ (If Tru
                 Then t2' 
                 Else t3) -->' u \/
   t2 -->' u /\ (If Tru
                    Then t2' 
                    Else t3) = u \/ t2 -->' u /\ (If Tru
                                                     Then t2' 
                                                     Else t3) -->' u

(* 省略するけどサブゴールが全部で10個出てくる! *)

たくさんサブゴールが出てくるので,なるべく自動で処理したいです.
まず,存在量化された命題を証明するときは eexists タクティクが便利です.eexists タクティクは existential variable, evar と呼ばれるメタ変数を作り,その変数を使って exists タクティクを適用します.つまり,一個目と二個目のサブゴールに eexists タクティクを適用した結果を見てみると

  t3, s' : term
  ============================
  s' = ?u /\ s' = ?u \/
  s' = ?u /\ s' -->' ?u \/ s' -->' ?u /\ s' = ?u \/ s' -->' ?u /\ s' -->' ?u

goal 2 (ID 1073) is:
 t2 = ?u0 /\ (If Tru
                 Then t2' 
                 Else t3) = ?u0 \/
 t2 = ?u0 /\ (If Tru
                 Then t2' 
                 Else t3) -->' ?u0 \/
 t2 -->' ?u0 /\ (If Tru
                    Then t2' 
                    Else t3) = ?u0 \/ t2 -->' ?u0 /\ (If Tru
                                                         Then t2' 
                                                         Else t3) -->' ?u0

となっています.? から始まる識別子が evar です.
一個目は \/ でつながれた条件の一つ目である s' = ?u /\ s' = ?u が,?us' を単一化すれば自明に成り立つことがわかります.このように evar に単一化を施した上で auto で解けるような命題は eauto タクティクで証明できます.
二個目は \/ でつながれた最後の条件である

t2 -->' ?u0 /\ (If Tru
                   Then t2' 
                   Else t3) -->' ?u0

?u0t2' を単一化すれば成り立ちます./\ の二個目の条件は constructor タクティクで証明できます.タクティク tac で証明できる命題が /\\/ などの論理結合子の中にあるような命題は firstorder tac で証明できます.
つまり,今回は constructoreauto を組み合わせて firstorder (try constructor; eauto を試します.

Lemma step'_loc_conf : forall t s s',
    t -->' s -> t -->' s' ->
    exists u, (s = u /\ s' = u) \/ (s = u /\ s' -->' u)
              \/ (s -->' u /\ s' = u) \/ (s -->' u /\ s' -->' u).
Proof.
  intros t s s' H.
  revert s'.
  induction H; intros; auto_inversion step';
    try solve [eexists; firstorder (try constructor; eauto)].

solve [tac] はタクティク tac でサブゴールを証明できれば証明し,できなければ失敗する,というタクティクです.たとえば eexistsfirstorder はサブゴールの状態を変えますが,solve [ ... ] の中に入れてやることで,証明が完了しないサブゴールには何も適用しないでおく,ということができます.
上の証明スクリプトで証明の状態は以下のようになります.

2 goals (ID 741)
  
  t1, t1', t2, t3 : term
  H : t1 -->' t1'
  IHstep' : forall s' : term,
            t1 -->' s' ->
            exists u : term,
              t1' = u /\ s' = u \/
              t1' = u /\ s' -->' u \/ t1' -->' u /\ s' = u \/ t1' -->' u /\ s' -->' u
  t1'0 : term
  H5 : t1 -->' t1'0
  ============================
  exists u : term,
    (If t1'
        Then t2 
        Else t3) = u /\ (If t1'0
                            Then t2 
                            Else t3) = u \/
    (If t1'
        Then t2 
        Else t3) = u /\ (If t1'0
                            Then t2 
                            Else t3) -->' u \/
    (If t1'
        Then t2 
        Else t3) -->' u /\ (If t1'0
                               Then t2 
                               Else t3) = u \/
    (If t1'
        Then t2 
        Else t3) -->' u /\ (If t1'0
                               Then t2 
                               Else t3) -->' u

goal 2 (ID 1067) is:
 exists u : term,
   (If t1
       Then t2' 
       Else t3) = u /\ (If t1
                           Then t2'0 
                           Else t3) = u \/
   (If t1
       Then t2' 
       Else t3) = u /\ (If t1
                           Then t2'0 
                           Else t3) -->' u \/
   (If t1
       Then t2' 
       Else t3) -->' u /\ (If t1
                              Then t2'0 
                              Else t3) = u \/
   (If t1
       Then t2' 
       Else t3) -->' u /\ (If t1
                              Then t2'0 
                              Else t3) -->' u

サブゴールが残り二個になりました.この二つは手動で証明します.solve [ ... ] の中で使った eexists; firstorder (try constructor; eauto) は残りの証明でも有用だったのでこれも Ltac にしておきました.

Ltac fo := eexists; firstorder (try constructor; eauto).

Lemma step'_loc_conf : forall t s s',
    t -->' s -> t -->' s' ->
    exists u, (s = u /\ s' = u) \/ (s = u /\ s' -->' u)
              \/ (s -->' u /\ s' = u) \/ (s -->' u /\ s' -->' u).
Proof.
  intros t s s' H.
  revert s'.
  induction H; intros; auto_inversion step'; try solve [fo].
  - destruct (IHstep' _ H5).
    destruct H0 as [|[|[]]]; destruct H0; subst; fo.
  - destruct (IHstep' _ H5).
    destruct H0 as [|[|[]]]; destruct H0; subst; fo.
Qed.

ということで,真面目にやると場合分けがたくさんあって大変な証明ですが,ほとんど自動で,かつ命題の中身について真面目に考えることなく証明できました.
TAPLのこの辺の証明を見る度に「Rocqでやったほうが早くない??」と思っていたので,その実証でした.
また,TAPLの模範解答には「二つの導出のペアの帰納法を使う」と書いてあるのですが,実際には普通の(導出一個の)帰納法でよいことが実証されています.

最後に,証明全体のコードをは以下に置きます.
https://gist.github.com/mir-ikbch/0152f50c37c14caac7687242c85f011b

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?