型付きλ式から証明

命題s

まずは簡単な命題

  1.       s : (A -> (B -> C)) -> (A -> B) -> (A -> C)

をCoqで証明し、(言い換えると、 型が (A -> (B -> C)) -> (A -> B) -> (A -> C) であるλ式 s を作るということです) その証明をPrint命令で表示しましょう。Coqを起動して、 以下を打ち込んでください。(打ち込む代わりに以下をクリップボードに コピーして、Coqの窓の一番上のバーを右クリックして編集-> 貼り付けを選択してもできます。)

Section ML.
Variables A B C:Prop.
Theorem s : (A -> (B -> C)) -> (A -> B) -> (A -> C).
Proof.
intros H Hab Ha.
apply H.
assumption.
apply Hab.
assumption.
Qed.
Print s.

まだSectionを閉じてはいませんが、目的は型付のλ式から証明を読み取ること なので、 s の 内容さえ把握できれば Coqは終了して結構です。(終了の仕方は、Window右上の×印をクリック してもいいですし、Quit.命令でも終了できます。)

Coq < Print s.
s =
fun (H : A -> B -> C) (Hab : A -> B) (Ha : A) => H Ha (Hab Ha)
     : (A -> B -> C) -> (A -> B) -> A -> C


Coq < Quit.

さて、最後のPrint s.命令で、上記のように表示されていたと思います。 そのうち、λ式sは

  1.       fun (H : A -> B -> C) (Hab : A -> B) (Ha : A) => H Ha (Hab Ha)

の部分で、 (A -> B -> C) -> (A -> B) -> A -> C はその型です。では、λ式H,Kからλ式Lが作られることを

H K
L

のような表であらわすことにし、λ式に型もつけて上記のsを作る様子を 表にすると、次のようになります。

H : A -> B -> C Ha : A Hab : A -> B Ha : A
H Ha : B -> C Hab Ha : B
H Ha (Hab Ha) : C
fun (Ha : A) => H Ha (Hab Ha) : A -> C
fun (Hab : A -> B)(Ha : A) => H Ha (Hab Ha) : (A -> B) -> A -> C
fun (H : A -> B -> C)(Hab : A -> B)(Ha : A) => H Ha (Hab Ha) : (A -> B -> C) -> (A -> B) -> A -> C

ここで、H,Ha,Habは型を持った変数です。さて、 型の部分だけ取り出すと、

A -> B -> C A A -> B A
B -> C B
C
A -> C (Aを仮定から除く)
(A -> B) -> A -> C (A -> Bを仮定から除く)
(A -> B -> C) -> (A -> B) -> A -> C (A -> B -> Cを仮定から除く)

あるいは、変数名H,Ha,Habをラベルのようにあつかって、

H : A -> B -> C Ha : A Hab : A -> B Ha : A
B -> C B
C
A -> C (Haを仮定から除く)
(A -> B) -> A -> C (Habを仮定から除く)
(A -> B -> C) -> (A -> B) -> A -> C (Hを仮定から除く)

のように表記することにも慣れておいてください。

いずれにしても、これは命題 s : (A -> (B -> C)) -> (A -> B) -> (A -> C) の証明になっていることがわかります。

また、λ式 M(その型は B)から、 fun (x:A) => M を 作ることは、推論で言うと、論理式 B から論理式 A→B を作り、 仮定から論理式 A を取り去る作業に対応していることがわかります。

型は命題、式は証明

ここまでくると、型の新しい解釈が理解できるようになります。すぐ上のところで「変数名H,Ha,Habをラベルのようにあつかって、」とあるように、「H : A -> B -> C」に関して、「Hは論理式A -> B -> Cの名前」程度にしか触れませんでしたが、「Hは論理式A -> B -> Cの証明」と解釈する見方があるのです。それは上記の「s : (A -> (B -> C)) -> (A -> B) -> (A -> C)」を考えればわかると思います。実際、上記のCoqにおけるPrint s.の出力を見ればわかるように、「s = fun (H : A -> B -> C) (Hab : A -> B) (Ha : A) => H Ha (Hab Ha)」は論理式「(A -> B -> C) -> (A -> B) -> A -> C」の証明と解釈することができます。

定理OrtoAnd

では、「ちょっと証明を」の定理OrtoAndを Print命令で表示したλ式 から、どのように証明を読み取るか説明しましょう。 まず、定理OrtoAndをPrint命令で表示したλ式は、 A,B,C を型がPropである 変数として、
(見やすいように、 fun (a : A) (_ : B) => H (or_introl B a)X とおいて)

OrtoAnd = fun (H : A \/ B -> C) (H0 : A /\ B) => and_ind X H0
: (A \/ B -> C) -> A /\ B -> C

でした。ではまず、 λ式 X がどのように 構成されるか見ましょう。(以下において、 変数 a,H のみ、 その横に型を記しておきます。色が黒いのはご愛嬌。)

  1.      
      or_introl B  
    or_introl B a : A
    H : A \/ B -> C or_introl B a
    H (or_introl B a)
    fun (_ : B) => H (or_introl B a)
    fun (a : A) (_ : B) => H (or_introl B a)

さて、上記の X の 構成では、色々と説明しなくてはならないことがあります。 「λ式の構成」と 「型の付き方」を思い出しながら、 λ式 X を構成する ことを考えると、まずわからないの は or_introl だ と思います。ではこれを説明しましょう。

とは言うものの、このサイトの製作者(以下、私)もよくわかっていません。 実は、 「λ式の構成」と 「型の付き方」の方法 では、Coqにおけるλ式の定義としては足りないようなのです。 では、今までのλ式の構成に、どういった手続きを加えればいいかということですが、 よくわかりもしないのに変な説明をしていたのでは、かえって話をわかりにくく してしまいますので、いっそのこと何も説明しないことにします。 それで or_introl に ついては、「今までの方法に追加された、ある方法で作られたλ式」だと、思って ください。

では何も情報がないかというと、そういうことではありません。Print命令で、 この or_introl の 定義を見てみましょう。命令はPrint or_introl.です。

Coq < Print or_introl.
Inductive or (A : Prop) (B : Prop) : Prop :=
    or_introl : A -> A \/ B | or_intror : B -> A \/ B
For or_introl: Argument A is implicit
For or_intror: Argument B is implicit
For or: Argument scopes are [type_scope type_scope]
For or_introl: Argument scopes are [type_scope type_scope _]
For or_intror: Argument scopes are [type_scope type_scope _]

Coq <

Inductiveなる命令 で or なるλ式が 定義されているようです。その定義の中 に or_introl が 現われているようです。そういう言い方をする と、or_introl の 定義は別にあるんじゃないかと思うかもしれませんが、そうではありませ ん。なぜなら、そもそもPrint or_introl.なる命令で表示されたのですから。 つまり、or_introl は、 oror_intror と 同時に定義されているのです。

Coqの出力の1行目 Inductive or (A : Prop) (B : Prop) : Prop := を見てみましょう。最後のところに 「 := 」があります。と言うことは、 「 := 」以前のものを、 「 := 」以降のもので定義しているというわけです。 つまり、 or (A : Prop) (B : Prop) : Prop or_introl : A -> A \/ B | or_intror : B -> A \/ B で定義しているわけです。

では、 or (A : Prop) (B : Prop) : Prop から見ていくことにしましょう。ここから読み取れるの は、or の 型です。それは、 「or なるλ式 は、引数として(A : Prop)と(B : Prop)の2つをとった上で、結果として型がPropに なる」ということです。 つまり、or の 型は ΠA:Prop.ΠB:Prop.Prop です。 「型付きλ式」での略記に 従えば Prop -> Prop -> Prop です。 (実は、or の 型を知りたいだけなら、Check or.命令だけで知ることができます。)

次に、 or_introl : A -> A \/ B | or_intror : B -> A \/ B を見ましょう。実はこれは、 | で区切られた 2つの文 or_introl : A -> A \/ B or_intror : B -> A \/ B です。とは言っても、これで なぜ or を定義 したことになるのか全くわからないと思います。 原因の1つはnotation(表記法)です。 そこで、以前「まずはいじってみる」でも実行した 命令のUnset Printing Notations.を実行した上で、もう一度Print or_introl. を実行してみましょう。

Coq < Unset Printing Notations.

Coq < Print or_introl.
Inductive or (A : Prop) (B : Prop) : Prop :=
    or_introl : A -> or A B | or_intror : B -> or A B
For or_introl: Argument A is implicit
For or_intror: Argument B is implicit
For or: Argument scopes are [type_scope type_scope]
For or_introl: Argument scopes are [type_scope type_scope _]
For or_intror: Argument scopes are [type_scope type_scope _]

Coq <

上記2つのPrint or_introl.の後のCoqの出力を比べてみてください。実は、 「 A \/ B 」は 「 or A B 」のことなのです。Coqは表記の仕方を自由に変える ことができます。(Notation命令を使うとできるのですが、説明はいずれ。)

さて、Inductive命令で書かれた上記の定義は or (A : Prop) (B : Prop) : Prop or_introl : A -> (or A B) or_intror : B -> (or A B) で定義するということですが、ここで注意があります。それは 「 := 以前で書かれた引数は、 := 以降も引き継ぐ 」というものです。つまり、今回の例で言うと、 or (A : Prop) (B : Prop) : Prop における (A : Prop) (B : Prop) の部分は、 or_introlor_intror の 方にも引き継がれるということです。それも明示して上記定義を もう一度書くと、

  1.       or (A : Prop) (B : Prop) : Prop
  2.       or_introl (A : Prop) (B : Prop) : A -> (or A B)
  3.       or_intror (A : Prop) (B : Prop) : B -> (or A B) で定義する。
ということになります。誤解を防ぐために、 or A Bの両側に括弧をつけておきました。 or を定義するために or_introlor_intror を 用い、その or_introlor_intror を 定義するために or を用いている という感じです。Inductiveなる言葉になじみのある人はそれほど違和感を 感じないかもしれません。

定義はさておき、上記5.から or_introl の 型を読み取りましょう。 「or_introl なるλ式 は、引数として (A : Prop)(B : Prop) の 2つをとった上で、結果として 型が A -> (or A B) に なる」ということです。 つまり、or_introl の 型は

  1.      ΠA:Prop.ΠB:Prop.A -> (or A B)
です。 Coqでの表記に 従えば
  1.      forall A B:Prop, A -> A \/ B
です。 「「型付きλ式」での略記」における 略記をしない なら
  1.      forall A B:Prop, forall a:A, A \/ B
です。 (or_introl の 型を知りたいだけなら、やはりCheck or_introl.命令だけで知ることができます。)

「型がPropになる 変数A」なんて 言い方はまどろっこしいので、簡単に A (:Prop) と表記することにします。

さて、上記7.8.9.のどれを見てもいいですが、型を見る限り、 or_introl は、 A (:Prop)、 B (:Prop)、 a (:A)の 3つを引数にとって型が A \/ B になるλ式です。例えば or_introl A B a( :A \/ B)です。 ところが、上記3. の X の構成の を見ると、(図の3行目に) or_introl B a と いうのがあって、明らかに型が Prop の変数 が1つ足りません (a の型は Prop では ありません)。

ここで、Print or_introl.の出力に戻って みましょう。 Print or_introl.命令の後の3行目に For or_introl: Argument A is implicit と出力されています。これは 引数 A を省略 する、という意味なのです。 or_introl A B a なる λ式においては、 or_introl の 定義からして、第3引数の a の型が 必ず第1引数 A と一致します。 よって、第1引数は第3引数から推測できるので、省略することにしよう、という わけです。そして、省略することに決めてしまった場合は、通常は省略しないと いけません。ですから実は、上記3. の X の構成の は不正確です。例えば次のようにしなければ なりません。(今回も、変数 a,H のみ、その横に型を記しておきます。)

  1.      
    or_introl A B a : A
    H : A \/ B -> C or_introl B a
    H (or_introl B a)
    fun (_ : B) => H (or_introl B a)
    fun (a : A) (_ : B) => H (or_introl B a)

上記10.の図の全てのλ式に型も表示すると、

  1.      
    or_introl:forall A B:Prop, A -> A \/ B A:Prop B:Prop a : A
    H : A \/ B -> C or_introl B a:A \/ B
    H (or_introl B a):C
    fun (_ : B) => H (or_introl B a):B -> C
    fun (a : A) (_ : B) => H (or_introl B a):A -> B -> C

これで X の型も わかりました。 X:A -> B -> C です。

ついでにCoqの表記について。 「fun (_ : B) =>」 の「_」 (アンダーバー)が気になると思います。そこは 「fun (b : B) =>」 と書いても 「fun (y : B) =>」 と書いてもλ式としては同じ(同値)になります。つまり、 「_」 (アンダーバー)に入る文字は、それ以外の場所には現れないわけです。こういうとき は、(使用する文字の節約でしょうか)Coqでは 「_」 (アンダーバー)を表示することになっています。

それはそうと、 λ式 X は何を 証明しているのでしょうか。上記11.の図から、型だけを取り出しましょう。 (但し、 A,B が 命題変数(Prop) であることの宣言の部分はカットします。また、 aH は ラベルの意味を持たせて表示することにします。)

  1.      
    A -> A \/ B a : A  
    H : A \/ B -> C A \/ B  
    C  
    B -> C _(:B)を仮定から除く
    A -> B -> C a(:A)を仮定から除く

A -> A \/ B は 公理であり、 A は 仮定から除かれているので、上記12.から示されているのは結局、

  1.      
    H : A \/ B -> C
    X : A -> B -> C

なる推論が成立するということです。

後は同様にして、定理OrtoAndの証明を OrtoAnd の式 から読み取ることができます。以下概略です。

まず、Print and_ind.命令。

Coq < Print and_ind.
and_ind =
fun A B P : Prop => and_rect (A:=A) (B:=B) (P:=P)
   : forall A B P : Prop, (A -> B -> P) -> A /\ B -> P


Arguments A, B, P are implicit
Argument scopes are [type_scope type_scope type_scope _ _]

Coq <

これで、λ式 and_ind の 型は forall A B P : Prop, (A -> B -> P) -> A /\ B -> P であり、そのうち A,B,P が 引数から省略(implicit)されることがわかります。

ではOrtoAndλ式を構成する手順の図を作りましょう。 λ式 X の部分は 上記13.を使用します。また、図が横に長くなって しまうので、and_ind の 型は省略します。

  1.      
      H : A \/ B -> C
    and_ind A:Prop B:Prop C:Prop X : A -> B -> C
    and_ind X : A /\ B -> C H0 : A /\ B
    and_ind X H0 : C
    fun (H0 : A /\ B) => and_ind X H0 : A /\ B -> C
    fun (H : A \/ B -> C) (H0 : A /\ B) => and_ind X H0
    : (A \/ B -> C) -> A /\ B -> C

上記から型の部分だけを取り出しましょう。 (また、 A,B,C が 命題変数(Prop) であることの宣言の部分はカットします。さらに、ラベルにできる変数は、 図の中に残します。)

  1.      
      H : A \/ B -> C  
    (A -> B -> C) -> A /\ B -> C A -> B -> C
    A /\ B -> C H0 : A /\ B
    C
    A /\ B -> C H0を仮定から除く
    (A \/ B -> C) -> A /\ B -> C Hを仮定から除く

もし、(A -> B -> C) -> A /\ B -> C を公理として採用するなら、これで 定理OrtoAndの証明を読み取ることができました。 ただ上記の15.は、証明に明らかな無駄があります。一度 A /\ B -> C が 証明されているのに、それをばらしてもう一度証明しています。その無駄を除くと 次のようになります。

  1.      
      H : A \/ B -> C
    (A -> B -> C) -> A /\ B -> C A -> B -> C
    A /\ B -> C
    (A \/ B -> C) -> A /\ B -> C Hを仮定から除く

もどる