Gen_J: 帰納法の仮定の一般化



Require Export Poly_J.

前章ではdouble関数が単射であることの証明をしました。この証明を始める方法は少々デリケートです。
      intros n. induction n.

で始めればうまくいきます。 しかし
      intros n m. induction n.

で始めてしまうと、帰納段階の途中でつまってしまいます...


Theorem double_injective_FAILED : n m,
     double n = double m
     n = m.
Proof.
  intros n m. induction n as [| n'].
  Case "n = O". simpl. intros eq. destruct m as [| m'].
    SCase "m = O". reflexivity.
    SCase "m = S m'". inversion eq.
  Case "n = S n'". intros eq. destruct m as [| m'].
    SCase "m = O". inversion eq.
    SCase "m = S m'".
      assert (n' = m') as H.
      SSCase "Proof of assertion".
      Admitted.

何がいけなかったのでしょうか?

帰納法の仮定を導入した時点で m をコンテキストに導入してしまっていることが問題です。直感的に言うと、これはCoqに「ある特定の nm について考えよう」と教えるようなものです。そのため、この特定の nm について double n = double m ならば n = m を証明しなければなりません。

次のタクティックス induction n はCoqに「このゴールの n に関する帰納法で示します」と伝えます。 なので、命題
  • P n = "double n = double m ならば n = m"
がすべてのnについて成り立つことを
  • P O

    (すなわち、"double O = double m ならば O = m")

  • P n P (S n)

    (すなわち、 "double n = double m ならば n = m" が成り立つならば " double (S n) = double m ならば S n = m").
2つめの文を見ると、これは奇妙なことを言っています。 それによると特定の m について
  • "double n = double m ならば n = m"
が成り立つならば
  • "double (S n) = double m ならば S n = m".
が証明できることになります。

これがどう奇妙かを説明するために、特定の m 、例えば 5 について考えてみましょう。 するとこの文は
  • Q = "double n = 10 ならば n = 5"
が成り立つならば
  • R = "double (S n) = 10 ならば S n = 5".
が証明できると言っています。

しかし Q を知っていても、Rを証明するのには何の役にたちません! (もし Q から R を示そうとすると「double (S n) = 10...を仮定すると...」のようなことを言わないといけませんが、これは途中でつまってしまいます。 double (S n)10 があることは、 double n10であるかどうかについては何も教えてくれません。なのでQ はここでは役にたちません。)

まとめると、mがすでにコンテキストにある状態でnに関する帰納法による証明がうまくいかないのは、すべてのnと単一のmの関係を示そうとしてしまうかからです。

double_injective のいい証明では、inductionnに対して使う時点ではmをゴールに残しています。

Theorem double_injective' : n m,
     double n = double m
     n = m.
Proof.
  intros n. induction n as [| n'].
  Case "n = O". simpl. intros m eq. destruct m as [| m'].
    SCase "m = O". reflexivity.
    SCase "m = S m'". inversion eq.
  Case "n = S n'".
    intros m eq.
    destruct m as [| m'].
    SCase "m = O". inversion eq.     SCase "m = S m'".
      assert (n' = m') as H.
      SSCase "Proof of assertion". apply IHn'.
        inversion eq. reflexivity.
      rewrite H. reflexivity. Qed.

帰納法によって証明しようとしていることが、限定的すぎないかに注意する必要があることを学びました。 もしnmの性質に関する証明をnに関する帰納法で行ないたい場合は、mを一般的なままにしておく必要があるかもしれません。

しかし、この戦略がいつもそのまま使えるわけではありません。ときには、ちょっとした工夫が必要です。 例えば、double_injectivenではなくmに関する帰納法で示したいとします。

Theorem double_injective_take2_FAILED : n m,
     double n = double m
     n = m.
Proof.
  intros n m. induction m as [| m'].
  Case "m = O". simpl. intros eq. destruct n as [| n'].
    SCase "n = O". reflexivity.
    SCase "n = S n'". inversion eq.
  Case "m = S m'". intros eq. destruct n as [| n'].
    SCase "n = O". inversion eq.
    SCase "n = S n'".
      assert (n' = m') as H.
      SSCase "Proof of assertion".
Admitted.

mに関する帰納法の問題点は、最初にnをintroしなければいけないことです。 (もし何も導入せずにinduction mをしても、Coqは自動的にnをintroします!) どうしたらいいでしょうか?

1つめの方法は、補題の文を書き換えてnより先にmがくるようにします。 これはうまくいきますが、いい方法ではありません。 特定の証明戦略のために補題の文をめちゃくちゃにしたくありません。 補題の文はできるかぎり明確かつ自然な形であるべきです。

その代わりに、いったんすべての限量変数を導入し、そのうちいくつかをコンテキストから取りゴールの先頭に置くことで、再び一般化します。 これはgeneralize dependentタクティックスによって実現できます。

Theorem double_injective_take2 : n m,
     double n = double m
     n = m.
Proof.
  intros n m.
  generalize dependent n.
  induction m as [| m'].
  Case "m = O". simpl. intros n eq. destruct n as [| n'].
    SCase "n = O". reflexivity.
    SCase "n = S n'". inversion eq.
  Case "m = S m'". intros n eq. destruct n as [| n'].
    SCase "n = O". inversion eq.
    SCase "n = S n'".
      assert (n' = m') as H.
      SSCase "Proof of assertion".
        apply IHm'. inversion eq. reflexivity.
      rewrite H. reflexivity. Qed.

この定理の非形式な証明を見てみましょう。なお nを限量化したまま帰納法によって命題を証明する箇所は、形式的な証明ではgeneralize dependentを使う箇所に対応します。

Theorem: すべての自然数 nm について、 double n = double m ならば n = m

Proof: mnatとする。 mに関する帰納法によって、 すべてのn に対して double n = double m ならば n = m を示す。
  • 最初に m = 0 と仮定し、ndouble n = double m をみたす数とし、 n = 0 を示す。

    m = 0なので、doubleの定義よりdouble n = 0n について2つの場合分けが考えれる。 n = 0 ならば、それが示したいことなので、すでに終了している。 そうでなくてn = S n'となるn'が存在する場合、矛盾を導くことで証明する。 doubleの定義によりn = S (S (double n'))だが、これは仮定 dobule n = 0 と矛盾する。

  • そうでない場合、m = S m' と仮定し、nは再び double n = double m をみたす数とする。 n = S m'を示すために、 帰納法の仮定「 すべての数 s に対して double s = double m'ならばs = m'」を用いる。

    m = S m'doubleの定義により、double n = S (S (double m'))nに関して2つの場合分けが考えられる。

    n = 0ならば、定義によりdouble n = 0となり、矛盾を導ける。 なので、n = S n'となるn'があると仮定すると、再びdoubleの定義により、 S (S (double n')) = S (S (double m'))。 ここでinversionによりdouble n' = dobule m'

    帰納法の仮定をn'をあてはめることで、n' = m'という結論を導ける。 S n' = nかつS m' = mなので、これにより示せる。

練習問題: ★★★ (gen_dep_practice)

mに関する帰納法で以下を示しなさい。

Theorem plus_n_n_injective_take2 : n m,
     n + n = m + m
     n = m.
Proof.
Admitted.

lに関する帰納法で示しなさい。

Theorem index_after_last: (n : nat) (X : Type) (l : list X),
     length l = n
     index (S n) l = None.
Proof.
Admitted.

練習問題: ★★★, optional (index_after_last_informal)

index_after_lastのCoqによる証明に対応する非形式的な証明を書きなさい。

Theorem: すべてのSet X, リスト l : list X, 自然数nに対して、length l = n ならば index (S n) l = None

Proof:

練習問題: ★★★, optional (gen_dep_practice_opt)

lに関する帰納法で示しなさい。

Theorem length_snoc''' : (n : nat) (X : Type)
                              (v : X) (l : list X),
     length l = n
     length (snoc l v) = S n.
Proof.
Admitted.

練習問題: ★★★, optional (app_length_cons)

app_lengthを使わずにl1に関する帰納法で示しなさい。

Theorem app_length_cons : (X : Type) (l1 l2 : list X)
                                  (x : X) (n : nat),
     length (l1 ++ (x :: l2)) = n
     S (length (l1 ++ l2)) = n.
Proof.
Admitted.

練習問題: ★★★★, optional (app_length_twice)

app_lengthを使わずにl1に関する帰納法で示しなさい。

Theorem app_length_twice : (X:Type) (n:nat) (l:list X),
     length l = n
     length (l ++ l) = n + n.
Proof.
Admitted.