「型の理論」と証明支援システム -- coqの世界

Post on 22-Apr-2015

1.583 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

TRANSCRIPT

「型の理論」と証明支援システム -- Coq の世界

@maruyama097丸山不二夫

Agenda

はじめに Part I : 「型の理論」をふりかえる Part II : Curry-Howard 対応について Part III: Coq 入門

はじめに 現在、 HoTT(Homotopy Type Theory) と呼

ばれる新しい型の理論とそれを基礎付けるUnivalent Theory と呼ばれる新しい数学理論が、熱い関心を集めている。

Univalent Theory は、数学者の Vladimir Voevodsky が、論理学者 Per Martin-Löf の型の理論を「再発見」したことに始まる。

この新しい分野は、 21 世紀の数学とコンピュータサイエンスの双方に、大きな影響を与えていくだろう。

はじめに 「証明支援システム」は、人間の証明をコン

ピュータが支援するシステムである。ただ見方を変えれば、それは、コンピュータの証明を人間が支援しているシステムだと考えることも出来る。

コンピュータの進化が進んでいる方向の先には知性を持つ機械の実現があると筆者は考えている。「証明支援システム」での人間と機械の、証明をめぐる双対的な「共生」は、それに至る一つの段階なのだと思う。

はじめに 小論は、 HoTT を直接の対象とはしていな

い。HoTT にいたる型の理論のオーバービューを与え、 Martin-Löf の型の理論に基づいた証明支援システム Coq の初等的な紹介を行うことを目的にしている。

おりしも、 Coq は、今年 2013 年に、 ACMの SIGPLAN の Programming Languages Software Award を授賞した。

型の理論と証明支援システムに対する関心が広がることを期待したい。

Part I

「型の理論」をふりかえる

Part I「型の理論」をふりかえる 型の理論  ~1910  

Russell 集合論の逆理と型の理論 計算可能性の原理 〜 1930

Church  型のないラムダ計算   型を持つラムダ計算  Church 1940 年 型付きラムダ計算と論理との対応

Curry-Howard   Dependent Type Theory

Martin-Löf    1970~ Homotopy Type Theory

Voevodsky

型の理論  ~1910Russell 集合論の逆理と型の理論

型の理論の歴史は古い。それは、 Russellによる集合論のパラドックスの発見に端を発する 100 年以上前の理論にさかのぼる。

Frege の公理 Cantor の集合概念

「集合とは、我々の直観あるいは思考の明確で異なった対象を一つの全体にした集まりである。その対象は、集合の要素と呼ばれる。」

Frege の定式化( Comprehension Axiom )ある述語 Φ について Φ(x) が成り立つ全ての要素 x を集めた集合 y が存在する。

Russell の逆理 先の Φ(x) に、 ~(x∈x) を取ろう。

この Φ によって定義される集合を R とすれば、R={ x | ~(x∈x) }R は、自分自身を要素として含まない全ての要素からなる集合になる。

R∈R だろうか? この時、 R は自分自身を要素に含んでいるので、 R の要素ではない。だから、 R∈R ならば ~(R∈R) となって矛盾する。

それでは、 ~(R∈R) だろうか? この時、 Rの定義から、この R は R の要素でなければならない。 ~(R∈R) ならば R∈R となって矛盾する。

Russell の矛盾の分析 Russell は、定義されるべきクラス自身を含

んだ、全てのクラスからなるクラスを考える自己参照的な定義、非述語的な定義 (impredicative definition) が問題の原因だと考えた。

「全体は、その全体という言葉によってのみ定義される要素を含んではいけない。ある全体の全ての要素という言葉によってのみ定義されうるものは、その全体の要素にはなり得ない。」

Russell は、対象と述語に型( degree とorder からなる)を導入して、こうした述語の適用を排除しようとした。

Russell の型の理論 全ての個体は型 i を持つ。 述語 P(x1,x2,...xn) は、 x1,x2,...xn  がそれぞ

れ持つ型 i1,i2,...in に応じて、型 (i1,i2,...in) を持つ。

ある型を持つ述語 P は、この型の階層構造の中で、その型以下の型を持つ個体に対してのみ適用出来る。

「全て」を表す全称記号は、その型以下の型を持つ個体の上を走る。

集合論の逆理に対するもうひとつのアプローチ Russell の型の理論とは、別のスタイルでの

集合論の逆理に対する対応が、 Zermelo–Fraenkel の公理的集合論( ZF )では、行われている。

ZF では、 Frege の Comprehension Axiomにかえて、次の公理が採用されている。

集合論の逆理の発見の波紋数学・論理学の基礎に対する反省 集合論での逆理の発見は、 Russell の型の理

論の導入、 ZF による集合論の公理化の動きとともに、数学・論理学の基礎に対する反省を活発なものにした。

疑われたのは次のような推論の正当性である。 ~∀x~P(x) ==> ∃xP(x)「ある述語 P が成り立たないと全ての x について言うことが否定できるなら、 P を満たすある x が存在する。」 ここでは、あるものの存在が、存在しないと仮定すると矛盾することを示すことで証明されている。

直観主義・構成主義の登場 こうした間接的な存在証明に疑いを持ち、こ

うしたものを論理から排除しようという動きが 20 世紀の初頭から生まれてくる。それが、直観主義・構成主義と呼ばれるものである。

この立場では、 P(x) を満たすある x の存在は、 P(a) が成り立つある a を示すことで、始めて与えられることになる。

この立場は、その後の数学・論理学に深い影響を与えた。現代のコンピュータ・サイエンスの基礎となっている型の理論も、後述するように、この立場に立っている。

計算可能性の原理 〜 1930 年代Church  型のないラムダ計算  

「計算可能性」については、 1930 年代にGödel, Turing, Church, Post らによる多くの成果が集中している。 Church のラムダ計算の理論は、 Turing の Turingマシンとともに、現代のコンピュータの基礎理論に大きな影響を与えた。

Church–Turing thesis

“一般帰納関数は、実効的に計算可能である”というこの経験的な事実は、 Church を次のようなテーゼの記述に導いた。同じテーゼは、チューリングの計算機械の記述のうちにも、暗黙のうちに含まれている。

“THESIS I. 全ての実効的に計算可能な関数(実効的に決定可能な述語)は、一般帰納関数である。

1943 年  Kleene“Recursive Predicates and Quantifiers”

関数の表記としての λ 記法 t = x2+y としよう。この t を、

x の関数としてみることを λx.t で表し、y の関数としてみることを λy.t で表わそう。

これは、普通の関数で考えると、    λx.t は、 f(x)=x2+y     λy.t は、 g(y)=x2+yと表記することに相当する。

t = x2+y は、また、 x と y の二つの変数の関数とみることが出来る。これを λxy.t と表そう。    λxy.t は、 h(x, y)=x2+y と表記することに相当する。

抽象化としての λ 表記 λ 記法を使うことによって、上記の f, g, h の

ような関数の具体的な名前を使わなくても、関数のある特徴を抽象的に示すことが出来る。これを λ による抽象化と呼ぶ。

λ による抽象化の例 λx.(x2 + ax +b) : x の関数としてみた x2 + ax +b

λa.(x2 + ax +b) : a の関数としてみた x2 + ax +b

λb.(x2 + ax +b) : b の関数としてみた x2 + ax +b

λxa.(x2 + ax +b) : x と a の関数としてみた x2 + ax +b

λxab.(x2 + ax +b) : x と a と b の関数としてみた x2 + ax +b

関数の値の計算 関数の値の計算を λ 表記で考えよう。 t = x2+y を

x の関数とみた時、 x=2 の時の t の値は 4+yy の関数とみた時、 y=1 の時の t の値は x2+1x,y の関数とみた時、 x=2, y=1 の時の値は、5

λ で抽象化された λx.t, λy.t が、例えば、 x=2, y=1 の時にとる値は、それぞれ、 4+y, x2+1といった関数を値として返す関数であることに注意しよう。

λxy.t の時、二つの変数 x, y に値を与えると、この関数は、はじめて具体的な値をとる。

λ 式での値の適用 λ 式 t に、ある値 s を適用することを、 λ 式 t

の後ろに、値 s を並べて、 t s のように表す。 例えば、次のようになる。

λx.t 2 = λx.(x2+y) 2 = 22+y = 4+yλy.t 1 = λy.(x2+y) 1 = x2+1λxy.t 2 1 = λxy.(x2+y) 2 1 = 22+1 = 5

変数への値の代入 関数 f(x) が、 x = a の時にとる値は、 f(x)

の中に現れる x に、 x = a の値を代入することで得ることが出来る。これは、通常、 f(a) と表わされる。

名前のない λ 式 t での変数 xへの値 a の代入による関数の値の計算を、次のように表現する。      t [x:=a]

もっとも単純な λ 式である、 x 自体が変数である時、代入は次のようになる。      x [x:=a] = a

適用と代入 もう少し、適用と代入の関係を見ておこう。 λ 式 λx.tへの、 a の適用 (λx.t) a とは、代

入 t[x:= a] を計算することである。 (λx.t) a = t[x:=a]

先の例での値の適用の直観的な説明は、代入を用いると、次のように考えることが出来る。λx.t 2 = λx.(x2+y) 2 = (x2+y) [x:=2] = 22+y = 4+yλy.t 1 = λy.(x2+y) 1       = (x2+y)[y:=1]=x2+1

λ 式の形式的定義 これまでは関数とその引数、引数に具体的な値が与えられた時の関数の値をベースに、 λ式とその値の計算を説明してきたが、ここでは、 λ 式の形式的な定義を与えよう。(正確なものではなく、簡略化したものであることに留意)

1. 変数 x,... は λ 式である。2. t が λ 式で、 x が変数であるなら、 λx によ

る抽象化 λx.t は λ 式である。 (abstraction) 3. t, s が λ 式であるなら、 tへの s の適用 t s

は、 λ 式である。 (application)

λ 計算の形式的定義 次の三つのルールを利用して、 λ 式を ( 基本的

には単純なものに ) 変換することを λ 計算という。

1. α-conversion:抽象化に用いる変数の名前は、自由に変更出来る。例えば、λx.(x2+1) => λy.(y2+1) => λz.(z2+1)

2. β-reduction:代入による計算ルール  (λx.t) a => t[x:=a]

3. η-conversion:λx.(f x) => fx で抽象化された f(x) は、 f に等しいということ。

β-reduction と代入ルール 実際の λ 計算で、大きな役割を果たすのは β-

reduction である。その元になっている代入ルールを少し詳しく見ておこう。

1. x[x := N] ≡ N2. y[x := N] ≡ y, if x ≠ y3. (M1 M2)[x := N] ≡ (M1[x := N])

(M2[x := N])4. (λx.M)[x := N] ≡ λx.M5. (λy.M)[x := N] ≡ λy.(M[x := N]), if x ≠ y,

provided y ∉ FV(N)

ちょっと変わった λ 式 1 λ 式  Ω := (λx.xx) (λx.xx) を考えよう。

これを計算してみる。(λx.xx) (λx.xx) = (λx.xx) (λy.yy) = xx[x:=λy.yy] = (λy.yy) (λy.yy) = (λx.xx) (λx.xx) となって、同じ λ 式が現れ、計算が終わらない。

ちょっと変わった λ 式 2 Y := λg.(λx.g (x x)) (λx.g (x x)) とする。

このとき、 Y g を計算してみよう。Y g = λg.(λx.g (x x)) (λx.g (x x)) g= λf.(λx.f (x x)) (λx.f (x x)) g= (λx.g (x x)) (λx.g (x x)) = (λy.g (y y)) (λx.g (x x)) = g (y y)[y:=(λx.g (x x))] = g ((λx.g (x x)) (λx.g (x x)))= g ( Y g )すなわち、 Y g = g ( Y g ) となって、 Y gは、 g の不動点を与える。

型を持つラムダ計算 Church 1940 年  

現在の ML, Haskell といった関数型言語は、この Church の型を持つラムダ計算に基礎をおいている。

ラムダ計算への型の導入 型 σ から型 τへの関数は、型 σ → τ を持

つ。 ある λ 式 e が型 τ を持つことを、 e : τ と

表す。 α, β, η の変換ルールは同じものとする

この表記の下で、 λ 式の型の定義を次のように行う。1. 単純な変数 vi は型を持つ。  vi : τ

2. e1 : ( σ → τ ) で、 e2 : σ なら、 (e1 e2) : τ

3. x : σ で e : τ なら、 (λxσ.e) : ( σ → τ )

型のないラムダ計算と単純な型を持つラムダ計算の違い 先に見た型のない λ 計算で成り立つ性質の大部分は、型を持つ λ 計算でも成り立つ。

ただ、両者のあいだには違いもある。型のない λ 計算では、任意の λ 式に対して任意の λ 式の適用を許していたが、型を持つ λ計算では、 λ 式の適用に型による制限が入っている。

型を持つラムダ計算では、 xσxσ という適用は許されない。許されるのは、 xσ→τ xσ という型を持つ λ 式どうしの適用のみである。先の Ω := (λx.xx) (λx.xx) は、型を持つラムダ計算では許されない λ 式である。

単純な型を持つラムダ計算の特徴 単純な型を持つラムダ計算は、こうした点で

は、型を持たないラムダ計算より表現力が弱いにもかかわらず、次のような重要な特徴を持つ。

単純な型を持つラムダ計算では、変換による計算は、必ず停止する。

型を持つラムダ計算での計算 あるオブジェクト a がある型 A を持つと

いう判断を、  a : A  と表わそう。 型を持つラムダ計算の理論では、計算は、

次の形をしている。 関数 λx:A.b[x] を、型 A に属するオブジェクト a を適用して b[a] を得る。

計算の下で、全てのオブジェクトはユニークな値を持つ。また、計算で等しいオブジェクトは、同一の値を持つ。

型を持つラムダ計算での正規のオブジェクトとその値 ある型に属するオブジェクトは、型の計

算ルールによって、値が求まるなら、正規オブジェクトと呼ばれる。

例えば、 1 + 1 は、自然数の型に属するが正規ではない。 2 は、正規のオブジェクトである。

ある型 A の正規オブジェクト v は、それ以上計算ルールの適用がが出来ず、それ自身を値として持つ。 この時、 v : A と書く。

a : A

型、オブジェクト、値

v : A

オブジェクト

  値

型    

計算

正規オブジェクト

Curry-Howard型付きラムダ計算と論理との対応

ラムダ計算に対する関心が、ふたたび活発化するのは、 20 年近くたった 1960 年代からだと思う。理由ははっきりしている。コンピュータが現実に動き出したからである。この時期の代表的な成果は、 Howard の Curry-Howard 対応の研究である。

Curry の発見 既に 1934 年に、 Curry は、型付を持つラム

ダ計算と直観主義論理とのあいだに、対応関係があることを発見していたという。

ここでは、型を持つラムダ計算の型に登場する矢印 -> が、論理式で、「 A ならば B 」の含意を意味する “ A -> B” の中に出てくる矢印 -> との間に、対応関係があることが大きな役割を果たす。

Curry, Haskell (1934), "Functionality in Combinatory Logic”http://www.ncbi.nlm.nih.gov/pmc/articles/PMC1076489/pdf/pnas01751-0022.pdf

Howard による発展 Howard は、この Curry の発見を、さら

に深く考えた。 1969 年の彼の論文のタイトルが示すよう

に、論理式は型付きラムダ計算の型と見なすことが出来るのである。ただし、彼のこの論文が公開されたのは、 1980 年になってからのことらしい。Howard,Williams (1980) "The formulae-as-types notion of construction”http://www.cs.cmu.edu/~crary/819-f09/Howard80.pdf

“Propositions as Types”“Proofs as Terms”

Howard の洞察は、” Propositions as Types”, “Proofs as Terms” ( これを、 PATというらしい)として、次にみる Martin-Löfの型の理論に大きな影響を与えた。

同時に、 Curry-Howard 対応は、型付きラムダ計算をプログラムと見なせば、” Proof as Program” としても解釈出来る。

こうした観点は、今日の Coq 等の証明支援言語の理論的基礎になっている。

Dependent Type TheoryMartin-Löf    1970 年 ~

現在の「型の理論」の基本が出来上がるのは、 1980 年代になってからである。その中心人物は、 Martin-Löf である。現在の Coq, Agda という証明支援システムは、彼の Dependent Type の理論に基礎付けられている。

型の命題への拡張 Church の単純な型を持つラムダ計算の体系

は、基本的には、型 A, 型 B に対して、型 A → Bで表現される関数型の型しか持たない。

それに対して、 Martin-Löf は、論理的な命題にも、自然なスタイルで型を定義した。例えば、 A が型であり、 B が型であれば、 A∧Bも A→B も A∨B も型であるというように。もちろん、それぞれは異なる型である。詳しくは後述する。

ある a が型 A を持つ時、 a : A で表す。

同一性への型の付与 Martin-Löf は、式 a = b にも型を与える。

   a =A b : Id(A a b)型 Id(A a b) を持つ式は、 a と b は等しいという意味を持つ。

a =A b の A は、型 A の中での同一性であることを表している。すなわち、 a : A で b : A で、かつ a = b の時にはじめて、 a =A b は型 Id(A a b)を持つ。

ここでは式の同一性について述べたが、式の同一性と型の同一性は、異なるものである。

型の理論の記述 Martin-Löf の型の理論は、次のことを示す、四つの形式で記述される。

1. ある対象 a が、型であること α : Type

2. ある表現式 a が、型 α の要素であること a : α

3. 二つの表現式が、同じ型の内部で、等しいこと   a = b : α

4. 二つの型が、等しいこと   α = β   : Type

Dependent Type

これまで、 A∧B, A → B, A∨B といった、元になる型 A, B を指定すると新しい型が決まるといったスタイルで型を導入してきた。

これとは異なる次のようなスタイル、型 A そのものではなく型 A に属する要素 a : A に応じて新しい型 B(a) が変化するような型の導入が可能である。

例えば、実数 R 上の n 次元のベクトル Vec(R,n) と n+1 次元のベクトルVec(R,n+1) は、別の型を持つのだが、これは n に応じて型が変わると考えることが出来る。

Dependent Type

こうした型を Dependent Type と呼び、次のように表す。     Π(x:A).B(x)

Dependent Type は、ある型 A の値 a にディペンドして変化する型である。

先の例のベクトルの型は、次のように表される。     Π(x:N).Vec(R,n)これは、全ての n について Vec(R,n) を考えることに対応している。

型の理論では、全称記号は、 Dependent Type として導入される。

Dependent Type とPolymorphism

Dependent Type の例を、もう一つあげよう。n 次元のベクトルは、自然数 N 、整数 Z 、実数 R 、複素数 C 上でも定義出来る。{ N, Z, R, C } : T とする時、次のように定義される Dependent Type を考えよう。      Π( x : T)Vec(x,n)

これは、次元 n は同じだが、定義された領域が異なる別の型 Vec(N,n),Vec(Z,n),Vec(R,n), Vec(C,n) を考えることに相当する。

こうして、 Polymorphic な関数は、 Dependent Type として表現されることが分かる。

Inductive Type

型の理論では、基本的な定数と関数から、新しい型を帰納的に定義することが出来る。こうした型を Inductive Type (帰納的型)と呼ぶ。

次は、そうした帰納的な定義の例である。ここでは、自然数の型 nat が、ゼロを意味する定数 O と successer 関数を表す関数 Sで、帰納的に定義されている。

Inductive nat : Type := | 0 : nat | S : nat -> nat.

関数型言語の基礎としての型の理論 Martin-Löf の型の理論は、型を持つラムダ計

算の拡張として、 ML, Haskell 等の現在の関数型言語に理論的基礎を与えた。

同時に、それは、 Curry-Howard 対応によって、 Coq, Agda 等の証明システムの理論的な基礎をも与えることになった。

 Homotopy Type TheoryVoevodsky 現在進行中

新しい型の理論 HoTT

Homotopy Type Theory (HoTT) は、数学者 Voevodsky が中心となって構築した、新しい型の理論である。

HoTT は、数学の一分野であるホモトピー論やホモロジー代数と、論理学・計算機科学の一分野である型の理論とのあいだに、深い結びつきがあるという発見に端を発している。

ここで取り上げられている型の理論は、 Martin-Löf の Dependent Type Theory である。

HoTT での a : A の解釈 論理=数学的には、 a : A には、様々な解釈があ

る。ここでは、他の解釈と比較して、 HoTT での a : A の解釈を見てみよう。

1. 集合論 : Russell の立場A は集合であり、 a はその要素である。

2. 構成主義 : Kolmogorov の立場A は問題であり、 a はその解決である

3. PAT: Curry & Howard の立場A は命題であり、 a はその証明である。

4. HoTT: Voevodsky の立場A は空間であり、 a はその点である。

HoTT での同一性の解釈空間 A の中で、点 a と点 b をつなぐ道がある時、a と b は、同じものと見なす。道自体は、連続的に変化しうる。その同一性は、homotopy が与える

HoTT での Dependent Type の解釈

(Ex)x∈B

B の値で、パラメーター化された E

Univalent Theory と Coq

Voevodsky は、 HoTT を武器に、 Univalent Theory という数学の新しい基礎付け・再構成を始めている。興味深いのは、彼が、こうした理論展開を、数学論文ではなく、 Coq のプログラムの形で GitHub で公開していることである。https://github.com/vladimirias/Foundations/

次の論文は、型の理論や HoTT にあまりなじみのない一般の数学者向けの、 Voevodskyの理論 Coq ライブラリーの解説である。http://arxiv.org/pdf/1210.5658v1.pdf

Part II

“Proposition as Type”“Proof as Term”

Curry-Howard 対応について

Part II“Proposition as Type”

Curry-Howard 対応について 論理式の意味を考える 証明について考える Propositions as Types and

Proofs as Terms (PAT) 命題とその証明のルール 型とその要素のルール Quantifier と Equality

Curry-Howard 対応について

Curry-Howard 対応について Curry-Howard 対応は、「型の理論」の最も重要な発見の一つである。

Curry-Howard 対応は、論理と型の理論との間の、驚くべき対応関係を明らかにした。すなわち、論理における命題は、型の理論の型に対応し、論理における証明は、型の理論でのある型の要素に対応する。

Curry-Howard 対応は、“ Proposition as Type”  “ Proof as Term” の頭文字をとって、 PAT と呼ばれることがある。

型と命題、証明と要素の「双対性」 型の理論の、中心的な概念は、型と命題、

証明と要素の「双対性」である。 p が命題 P の証明であることを、 p : P

と表してみよう。 この「 p が命題 P の証明である」とい

う判断を表す p : P は、「 p は、型 P の要素である」という判断を表していると見なすことが出来るし、また、逆の見方も出来るのである。

型と命題、要素と証明の「双対性」

「 p は、命題 P の証明である」

「 p は、型 P の要素である」

p : P

証明        命題

要素         型      

論理式の意味を考える

Due to Per Martin-Lof“ON THE MEANINGS OF THE LOGICALCONSTANTS AND THE JUSTIFICATIONS OF THE LOGICAL LAWS”http://docenti.lett.unisi.it/files/4/1/1/6/martinlof4.pdf

論理式の意味?   A∧B  :  A かつ B   A∨B  :  A または B   A→B  :  A ならば B   ~A   : A ではない  ∀ xP(x) : 全ての x について P ( x )  ∃ xP(x) : ある x が存在して P ( x )

    A  の意味 「 A は真である。」

「私は「 A は真である」ことを知っている。」

私は「 A は真である」ことを知っている。

ただ、その前に、知っていることがある。 「私は「 A は論理式である」ことを知っ

ている。」

対象 行為

論理式の表現・構成についての判断の構造

私は A が論理式である ことを知っている

表現 判断の形式

判断

明証的な判断

論理式の正しさについての判断の構造

私は A が真である ことを知っている

論理式 判断の形式

判断

明証的な判断

判断と命題 判断の概念は、常に、命題の概念に先立

つ。 判断における論理的帰結の概念は、命題に

おける含意より先に、説明されなければならない。

証明とは何か? 証明とは、判断を明証なものにするもの。 証明すること=知ること=理解して把握す

ること 知るようになる=知識を得ること 証明と知識は、同じもの

証明について考える

Due to Per Martin-Lof“Intuitionistic Type Theory”Bibliopolis 1980

証明の解釈 Kolmogorov は、命題 a → b の証明に、

「命題 a の証明を命題 b の証明に変換する方法を構築すること」という解釈を与えた。

このことは、 a → b の証明を、 a の証明からb の証明への関数と見ることが出来るということを意味する。

このことは、同時に、命題をその証明と同一視出来ることを示唆する。

型はこうした証明の集まりを表現し、そうした証明の一つは、対応する型の、一つの項と見なすことが出来る。

命題の証明は、何から構成されるか? ⊥ A ∧ B A ∨ B A → B (∀x)B(x)

(∃ x)B(x)

なしA の証明と B の証明の両方A の証明、あるいは、 B の証明A の証明から B の証明を導く方法任意の a に対して B(a) の証明を与える方法ある a に対する B(a) の証明

命題の証明は、何から構成されるか?形式的に ⊥ A ∧ B

A ∨ B

A → B

(∀x)B(x)

(∃ x)B(x)

noneA の証明である a と、B の証明である b のペア (a,b)A の証明である i(a) 、あるいは、B の証明である j(b)A の証明である a に対して、B の証明 b(a) を与える (λx)b(x)任意の a に対してB の証明 b(a) を与える (λx)b(x)ある a と、 B(a) の証明である b のペア (a,b)

Propositions as Types and Proofs as Terms (PAT)

Due to Simon Thompson“Type Theory & Functional Programming”1999

Curry-Howard の対応関係をどう証明するか? ここでは、 Simon Thompson のやり方に従

う。 まず、 p : P が 「 p が命題 P の証明であ

る」という判断を表すとして、そうした判断が従うべきルールを形式的に記述する。

ついで、 p : P が 「 p が型 P の要素である」という判断を表すとして、そうした判断が従うべきルールを形式的に記述する。

そうすると、前者と後者の形式的記述が、その解釈は別にして、まったく同じものであることが分かる。

命題とその証明のルール

命題とその証明のルール Formation ルール

システムの証明は、どのような形をしているか?

Introduction / Elimination ルールどのような表現が、命題の証明になるか?

Computation ルール表現は、どのようにして単純な形に還元されるのか?

∧ を含む命題の証明のルール A が命題であり、 B が命題であれば、 A∧B

は命題になる。 p が命題 A の証明であり、かつ、 q が命題 B

の証明であれば、 p,q のペア( p 、 q )は、命題 A∧B の証明である。

r が命題 A∧B の証明であれば、ペア r の第一項 fst r は、命題 A の証明であり、ペア r の第二項 snd r は命題 B の証明である。

ペア( p 、 q )について、 fst(p,q)=p であり、 snd(p,q)=q である。

∧ を含む命題の証明のルール Formation ルール

  A は命題である   B は命題である      (A∧B) は命題である

Introduction ルール  p : A q : B (p,q) : (A∧B)

Elimination ルール  r : (A∧B) r : (A∧B) fst r : A snd r : B

(∧ F )

(∧ I )

(∧ E1 ) (∧ E2 )

∧ を含む命題の証明のルール Computation ルール

  fst (p,q) → p snd (p,q) → q

→ を含む命題の証明のルール A が命題であり、 B が命題であれば、 A→B

は命題になる。 x が命題 A の証明だと仮定して、 e が命題 B

の証明だとしよう。この時、命題 A→B の証明は、 x:A である x の関数としてみた e 、 (λx:A).e である。

q が命題 A→B の証明で、 a が命題 A の証明だとすれば、関数 q に a を適用した (q a)は、命題 B の証明である。

この時、 ((λx:A).e) a =   e[a/x]  ( e の中の x を全て a で置き換えたもの) になる。

→ を含む命題の証明のルール Formation ルール

  A は命題である    B は命題である      (A→B) は命題である

Introduction ルール     [x : A]        e : B(λx:A).e : (A→B)

(→ F )

(→ I )

→ を含む命題の証明のルール

Elimination ルール  q : (A→B) a : A (q a) : B

Computation ルール((λx:A).e) a →   e[a/x]

(→ E )

∨ を含む命題の証明のルール A が命題であり、 B が命題であれば、 A∨B

は命題になる。 q が命題 A の証明であれば、 q は命題 A∨B

の証明を、 r が命題 B の証明であれば、 r も命題 A∨B の証明を与えるように見える。

ここで、 q に「 q は∨で結ばれた命題の左の式の証明である」という情報を付け加えたものを、 inl q とし、同様に、 r に「 r は∨で結ばれた命題の右の式の証明である」という情報を付け加えたものを、 inr r と表すことにしよう。

∨ を含む命題の証明のルール その上で、 q が命題 A の証明であれば、 inl

q が命題 A∨B の証明を、 r が B の証明であれば、 inr r が命題 A∨B の証明を与えると考える。

このことは、命題 A∨B が証明されるのは、命題 A または命題 B のどちらかが証明される場合に限ることになる。

例えば、命題 A∨~A が証明されるのは、命題A または命題 ~A のどちらかが証明された場合だけということになる。この体系では、一般に「排中律」はなりたたない。

∨ を含む命題の証明のルール 今、 p が命題 A∨B の証明で、 f が命題 A→C

の証明で、 g が命題 B→C の証明とした時、命題 C の証明がどのような形になるか考えよう。

p が命題 A∨B の命題 A の証明であるか( inl p )命題 A∨B の命題 B の証明であるか (inr p) の場合に応じて、命題 C の証明の形は変わる。

前者の場合、 p は命題 A の証明であるので、f:A→C と組み合わせれば、命題 C が導ける。後者の場合、 p は命題 B の証明であるので、g:B→C と組み合わせれば、命題 C が導ける。

∨ を含む命題の証明のルール Formation ルール

  A は命題である    B は命題である      (A∨B) は命題である

Introduction ルール   q : A   r : B inl q : (A∨B) inr r : (A∨B)

Elimination ルールp : (A∨B) f : (A→C )  g : ( B→C ) cases p f g : C

(∨ F )

(∨ I1 ) (∨ I2 )

(∨ E )

∨ を含む命題の証明のルール Computation ルール

cases (inl q) g f →   f qcases (inr r) g f → g r

⊥ の証明のルール ⊥は、矛盾を表す。 矛盾を導く証明のルールは、存在しない。 もし、 p が矛盾の証明なら、どんな命題も証

明可能になる。

⊥ の証明のルール Formation ルール

  ⊥は命題である Elimination ルール

   p : ⊥ abortA p : A

(⊥ F )

(⊥ E )

Assumption のルール Formation ルール

A は命題である x : A 

( AS )

型とその要素のルール

型とその要素のルール Formation ルール

システムの型は、どのような形をしているか?

Introduction / Elimination ルールどのような表現が、型の要素となるか?

Computation ルール表現は、どのようにして単純な形に還元されるのか?

∧ を含む要素の型のルール A が型であり、 B が型であれば、 A∧B は型

になる。 p が型 A の要素であり、かつ、 q が型 B の要

素であれば、 p,q のペア( p 、 q )は、型A∧B の要素である。

r が型 A∧B の要素であれば、ペア r の第一項 fst r は、型 A の要素であり、ペア r の第二項snd r は型 B の要素である。

ペア( p 、 q )について、 fst(p,q)=p であり、 snd(p,q)=q である。

∧ を含む型のルール Formation ルール

  A は型である    B は型である      (A∧B) は型である

Introduction ルール  p : A q : B (p,q) : (A∧B)

Elimination ルール  r : (A∧B) r : (A∧B) fst r : A snd r : B

(∧ F )

(∧ I )

(∧ E1 ) (∧ E2 )

∧ を含む型のルール Computation ルール

  fst (p,q) → p snd (p,q) → q

∧ を含む要素の型のルール 型として見ると、 A∧B は、二つの型の直積

である。 fst, snd は、直積から直積を構成する要素へ

の射影演算子である。 通常のプログラム言語からみれば、 A∧B

は、「レコード型」と見ることが出来る。

→ を含む要素の型のルール A が型であり、 B が型であれば、 A→B は型

になる。 x が型 A の要素だと仮定して、 e が型 B の要

素だとしよう。この時、型 A→B の要素は、x:A である x の関数としてみた e 、 (λx:A).eである。

q が型 A→B の要素で、 a が型 A の要素だとすれば、関数 q に a を適用した (q a) は、型B の要素である。

この時、 ((λx:A).e) a =   e[a/x]  ( e の中の x を全て a で置き換えたもの) になる。

→ を含む型のルール Formation ルール

  A は型である    B は型である      (A→B) は型である

Introduction ルール     [x : A]        e : B(λx:A).e : (A→B)

(→ F )

(→ I )

→ を含む型のルール

Elimination ルール  q : (A→B) a : A (q a) : B

Computation ルール((λx:A).e) a →   e[a/x]

(→ E )

→ を含む要素の型のルール 型 A→B の要素は、型 A から型 Bヘの関数で

ある。 Introduction ルールは、ドメインに x:A と

いう制限を加えた、 λ 計算の λ abstractionである。

Elimination ルールは、 λ 計算の関数の適用( application )の型を与える。

Computation ルールは、 λ 計算の β還元規則である。

∨ を含む型の要素のルール A が型であり、 B が型であれば、 A∨B は型

になる。 q が型 A の要素であれば、 q は型 A∨B の要

素を、 r が型 B の要素であれば、 r も型 A∨Bの要素を与えるように見える。

ここで、 q に「 q は∨で結ばれた型の左の型の型である」という情報を付け加えたものを、 inl q とし、同様に、 r に「 r は∨で結ばれた型の右の型の要素である」という情報を付け加えたものを、 inr r と表すことにしよう。

∨ を含む型の要素のルール その上で、 q が型 A の要素であれば、 inl q

が型 A∨B の要素を、 r が型 B の証明であれば、 inr r が型 A∨B の要素を与えると考える。

このことは、型 A∨B が要素を持つのは、型A または型 B のどちらかが要素を持つ場合に限ることになる。

例えば、型 A∨~A が要素を持つのは、型 Aまたは型 ~A のどちらかが要素を持つ場合だけということになる。この体系では、一般に「排中律」はなりたたない。

∨ を含む型の要素のルール 今、 p が型 A∨B の要素で、 f が型 A→C の要

素で、 g が型 B→C の要素とした時、型 C の要素がどのような形になるか考えよう。

p が型 A∨B の型 A の要素であるか( inl p )型 A∨B の型 B の要素であるか (inr p) の場合に応じて、型 C の要素の形は変わる。

前者の場合、 p は型 A の要素であるので、 f:A→C と組み合わせれば、型 C が導ける。後者の場合、 p は型 B の要素であるので、 g:B→C と組み合わせれば、型 C が導ける。

∨ のルール Formation ルール

  A は型である    B は型である      (A∨B) は型である

Introduction ルール   q : A   r : B inl q : (A∨B) inr r : (A∨B)

Elimination ルールp : (A∨B) f : (A→C )  g : ( B→C ) cases p f g : C

(∨ F )

(∨ I1 ) (∨ I2 )

(∨ E )

∨ を含む型のルール Computation ルール

cases (inl q) g f →   f qcases (inr r) g f → g r

∨ を含む型のルール 型 A∨B は、型 A と型 B の直和である。 通常のプログラム言語では、値に名前のタグ

がつけられたレコード型と考えてよい。 Elimination ルールは、その名前による場合

分けに相当する。 Computation ルールは、 p に A のタグがつ

いていれば、 f p を計算して C の要素を求め、 p に B のタグがついていれば、 g p を計算して、 C の要素を求めることを表している。

⊥ の型のルール Formation ルール

  ⊥は型である Elimination ルール

   p : ⊥ abortA p : A

(⊥ F )

(⊥ E )

⊥ の型のルール このルールは、もし p が空である型の要素で

あれば、プログラムは中断すべきことを表している。

Assumption のルール Formation ルール

A は型である x : A 

( AS )

Quantifier と Equality

∀ を含む命題の証明のルール

Formation ルール [x : A]     A は命題である   P は命題である     (∀x:A).P  は命題である

Introduction ルール      [x : A]              p : P      (λx:A).p : (∀x:A) P  

(∀ F )

…(∀ I )

∀ を含む命題の証明のルール

Elimination ルールa : A   f : (∀x:A) P        f a : P[a/x]

Computation ルール(( λx : A) . p) a = p[a/x]

(∀ E )

∃ を含む命題の証明のルール Formation ルール

[x : A]     A は命題である   P は命題である     (∃x:A).P  は命題である

Introduction ルール   a : A p : P[a/x](a,p) : (∃x:A) .P

(∃ F )

(∃ I )

∃ を含む命題の証明のルール Elimination ルール

p : (∃x: A).P p : (∃x: A).P Fst p : A Snd p : A

Computation ルールFst (p, q) = p Snd (p, q) = q

(∃ E’1 ) (∃ E’2 )

等号 = A を含む型のルール

I(A, a, b) a =A b

Formation ルールA は型である   a : A b : A   I(A, a, b) は型である

Introduction ルール        a : A r(a) : I(A, a, a)

( IF )

( II )

等号 = A を含む型のルール

Elimination ルールc : I(A, a, b) d : C(a, a, r(a)) J(c, d) : C(a, b, c)

Computation ルール    J ( r(a) 、 d) = d

( IE )

Part III

Coq 入門

Part IIICoq 入門 数学の証明とコンピュータ Coq とはどんな言語か? 関数型プログラム言語としての Coq

List を Coq でプログラムする 証明支援システムとしての Coq

基本的な tactic について tactic を使って見る n x m = m x n を証明する 証明に必要な情報を見つける 二分木についての定理の証明

Coq からのプログラムの抽出 あらためて、 p : P ( 証明 : 命題 ) を考える

数学の証明とコンピュータ

誤っていた「証明」 「フェルマーの定理」は、最終的に

は、 1995 年の Andrew Wiles の論文によって証明されたが、フェルマー自身は、この定理を証明出来たと信じていた。それが、誤った「証明」であったのは確かである。

Wiles 自身も、 95 年の論文以前に一度、「証明した」とする発表を行うが、誤りが見つかり、それを撤回している。

世紀の難問である「リーマン予想」は、何度か「証明」が発表されている。今までのところ、それは誤って「証明」だった。

正しいことの判定が難しい証明 Grigory Perelman は、 2002 年から 2003

年にかけて arXiv に投稿した論文で、「三次元のポアンカレ予想」を解いたと主張した。

arXiv は、学会の論文誌とは異なり、掲載の可否を決める査読が基本的にないので、発表の時点では、彼の論文以外に、彼の証明が正しいという裏付けはなかった。

彼の証明の検証は、複数の数学者のグループで取り組まれ、最終的に彼の証明の正しさが「検証」されたのは、 2006 年のことだった。

非常に膨大な証明 「有限単純群の分類問題」は、 Daniel

Gorenstein をリーダーとする数学者の集団によって解決され、 20 世紀の数学の大きな成果の一つと呼ばれている。

ただ、その「証明」は、 100 人以上の数学者が、 50 年のあいだに数百の論文誌に発表した、膨大な量の「証明」の総和である。そのページ数は一万ページを超えると言われている。(「全部を読んでる数学者は、1人もいない。」というのがジョークなのか本当なのか、分からない。)

「四色問題」のコンピュータによる解決 1974 年、 イリノイ大学の Kenneth Appel

と Wolfgang Haken は、当時の最先端のコンピュータを使って、「四色問題」を解いた。

コンピュータの稼働時間は、 1200 時間に及び、夜間の空き時間しか利用が認められなかったので半年がかりの計算だったと言う。

コンピュータが人手にはあまる膨大な計算を行ったのは事実だが、当時、その計算の正しさの検証しようのないことを理由に、こうした「証明」を疑問視する声も一部にはあった。

来年は、「四色問題」解決の 40周年にあたる。

「四色問題」「 Feit-Thompsonの定理」の Coq による証明 2004 年、 Georges Gonthier は、 Coq を使って「四色問題」を解いた。

Georges Gonthier は、また、 2013 年、有限群分類の重要な定理である、「 Feit-Thompson の定理」の Coq による形式的証明を与えた。

15,000 の定義、 4,300 の定理、 17万行のソースからなるこの証明を、彼はチーム作業で、 6 年かけて完成させた。

Univalent Theory での Coq の利用 先にも見たように、 Voevodsky

は、 Univalent Theory という数学理論を、Coq のプログラムの形で、 GitHub で公開している。https://github.com/vladimirias/Foundations/

Coq とはどんな言語か?

Coq 言語の開発 Coq の開発は、 Thierry Coquand と

Gérard Huet によって 1984 年から、 INRIA ( Institut National de Recherche en Informatique et en Automatique フランス国立情報学自動制御研究所)で開始された。その後、 Christine Paulin-Mohring が加わり、 40 人以上の協力者がいる。

最初の公式のリリースは、拡張された素朴な帰納タイプの CoC 4.10 で 1989 年。 1991年には、 Coq と改名された。

それ以来、ユーザーのコミュニティは増大を続けており、豊かな型付き言語として、また、インタラクティブな定理証明システムとして、 Coq のコンセプトのオリジナリティとその様々な特徴に魅了されている。

今年 2013 年には、 ACM の SIGPLAN のProgramming Languages Software Awardを授賞した。

Award 授賞理由 Coq 証明支援システムは、 機械でチェック

された形式的推論に対して、豊かなインタラクティブな開発環境を与えている。

Coq は、プログラム言語とシステムの研究に深いインパクトを与え、その基礎へのアプローチをそれまで全く予想もされなかった規模と確実さのレベルへと拡大し、そして、それらを現実のプログラミング言語とツールにまで押し広げた。

Coq は、プログラム言語研究のコミュニティで研究ツールとして広く受け入れられている。そのことは、 SIGPLAN のカンファレンスの論文成果の多くが、 Coq で開発され検証されていることをみても明らかである。

Coq は、また、急速に、多くの有力な大学の提供する講座で、プログラミング言語の基礎を教える為の主要なツールの一つとして選ばれている。そして、それをサポートする多くの書籍が登場しようとしている。

最後だが重要なことは、こうした成功が、 Coq がそれに基づいている、非常に豊かな表現力を備えた基本的な論理である、 dependent type theory に対する関心の幅広い波が、急速に広がるのを助けていることである。

ソフトウェア・システムとして Coq は、 20年以上にわたって開発が継続してきた。これは、本当に印象的な、研究ドリブンの開発とその維持の偉業である。 Coq チームは開発を続け、新しいリリースの度に、表現力でも利用のしやすさの点でも目覚ましい改善をもたらしてきた。

一言で言って、 Coq は、数学・セマンティックス・プログラムの検証の、形式的な正確性の保証の新しい時代への移行の中で、本質的に重要な役割を果たしているのである。

Coq のインストールと起動 Coq は、次のサイトから入手出来る。

http://coq.inria.fr/

コマンドラインから、 coqtop と打ち込むと Coq が起動する。

coqide で、 IDE環境を利用することが出来る。

関数型プログラム言語としてのCoq

型のチェックと定義

Coq < Check 2 + 3.2 + 3 : nat

Coq < Check 2.2 : nat

Coq < Check (2 + 3) + 3.(2 + 3) + 3 : nat

Coq < Definition three := 3.three is defined

型のチェックと定義

Coq < Definition add3 (x : nat) := x + 3.add3 is defined

Coq < Check add3.add3 : nat -> nat

Coq < Check add3 + 3.Error the term "add3" has type "nat -> nat"while it is expected to have type "nat”

Coq < Check add3 2.add3 2 : nat

値の計算

Coq < Compute add3 2.= 5 : nat

Coq < Definition s3 (x y z : nat) := x + y + z.s3 is defined

Coq < Check s3.s3 : nat -> nat -> nat -> nat

Coq < Compute s3 1 2 3=6 : nat

関数の定義と引数

Coq < Definition rep2 (f : nat -> nat)(x:nat) := f (f x).rep2 is defined

Coq < Check rep2.rep2 : (nat -> nat) -> nat -> nat

Coq < Definition rep2on3 (f : nat -> nat) := rep2 f 3.rep2on3 is defined

Coq < Check rep2on3.rep2on3 : (nat -> nat) -> nat

無名関数の定義

Coq < Check fun (x : nat) => x + 3.fun x : nat => x + 3 : nat -> nat

Coq < Compute (fun x:nat => x+3) 4. = 7 : nat

Coq < Check rep2on3 (fun (x : nat) => x + 3).rep2on3 (fun x : nat => x + 3) : nat

Coq < Compute rep2on3 (fun (x : nat) => x + 3) . = 9 : nat

帰納的データ型の定義の例

Inductive bool : Set := | true : bool | false : bool.

Inductive nat : Set := | O : nat | S : nat -> nat.

bool 型の定義 ( enumerated type )

自然数型の定義 (recursive type)

帰納的データ型の定義の例

Inductive list (A : Type) : Type := | nil : list A | cons : A -> list A -> list A.

Inductive natBinTree : Set :=| Leaf : natBinTree| Node (n:nat)(t1 t2 : natBinTree).

List 型の定義 (recursive type)

二分木型の定義 (recursive type)

パターン・マッチングによる定義

Definition negb b :=match b with| true => false| false => trueend.

bool値の否定の定義

パターン・マッチングによる定義

Definition tail (A : Type) (l:list A) :=match l with| x::tl => tl| nil => nilend.

Definition isempty (A : Type) (l : list A) :=match l with| nil => true| _ :: _ => falseend.

パターン・マッチングによる定義

Definition has_two_elts (A : Type) (l : list A) :=match l with| _ :: _ :: nil => true| _ => falseend.

Definition andb b1 b2 :=match b1, b2 with| true, true => true| _, _ => falseend.

リカーシブな定義

Fixpoint plus n m :=match n with| O => m| S n' => S (plus n' m)end.

Notation : n + m for plus n m

1+1 = S(O+1)=S(1)=S(S(O))

リカーシブな定義

Fixpoint minus n m := match n, m with| S n', S m' => minus n' m'| _, _ => nend.

Notation : n - m for minus n m

3-2=2-1=1-0=12-3=1-2=0-1=0

リカーシブな定義

Fixpoint mult (n m :nat) : nat :=match n with| 0 => 0| S p => m + mult p mend.

Notation : n * m for mult n m

3*2=2+2*2=2+2+2*1=2+2+2+2*0

List を Coq でプログラムする

list 型の定義

Inductive list (A : Type) : Type := | nil : list A | cons : A -> list A -> list A.

list の基本的な定義は、次のものである。

ここで、 list は、 A : Type なる任意の型に対して定義されていることに注意しよう。 (Polymorphism)

constructor cons に対して次の記法を導入する。

infix “::” := cons (at level 60, ....)

list についての基本的な関数 length

Definition length (A : Type) : list A -> nat := fix length l := match l with | nil => O | _ :: l' => S (length l') end.

length は、 list の長さを返す。

再帰的な関数の定義には、 fixpoint を使うが、定義の本体に再帰的な定義が現れる場合は、fix を使う。

list についての基本的な関数  app

Definition app (A : Type) : list A -> list A -> list A := fix app l m := match l with | nil => m | a :: l1 => a :: app l1 m end.

app(++) は、二つの list を結合 (append) する。

Infix "++" := app (right associativity, ....

list についての基本的な関数 map

Fixpoint map A B (f : A -> B)(l : list A) : list B :=match l with| nil => nil| a::l' => f a :: map f l'end.

map は、 list の各要素に関数を適用する。

Compute map (fun n => n * n)(1::2::3::4::5::nil).1::4::9::16::25::nil : list nat

list についての基本的な関数 reverse

Fixpoint naive_reverse (A:Type)(l: list A) : list A :=match l with| nil => nil| a::l' => naive_reverse l' ++ (a::nil)end.

reverse は、 list の要素を逆順にする。

Compute naive_reverse (1::2::3::4::5::6::nil).= 6 :: 5 :: 4 :: 3 :: 2 :: 1 :: nil : list nat

証明支援システムとしての Coq

いくつかの命題の集り Γ を仮定すれば、ある命題 A が結論として導かれるという判断を次のように表現しよう。 Γ   |-   A

例えば、次の表現は、命題 P \/ Q と命題〜 Q から、命題 R → R ∧ P が導かれることを表現している。

判断 Γ   |-   A

ある判断の集まり Γ 1 |- A1, Γ2 |- A2, ... Γn |- An から、別の判断 Γ |- B が導かれる時、それを推論ルールと呼び、次のように表す。    Γ 1 |- A1 Γ2 |- A2 ... Γn |- An

       Γ |- B 例えば、次の表現は、一つの推論ルールで

ある。

推論ルール

この推論ルールには、次のような二つの解釈が可能である。

前向きの解釈A が成り立つことが言え、 B が成り立つことが言えれば、 A∧B が成り立つことが言える。

後ろ向きの解釈A∧B が成り立つことが言える為には、 Aが成り立つことが言え、 B が成り立つことが言えなければならない。

推論ルールの二つの解釈

Coq での証明のスタイル Coq では、推論ルールの後ろ向きの解釈

で証明を進める。 すなわち、証明すべき命題(これを Goal

と呼ぶ)を成り立たせる為に必要な命題(これを Sub Goal と呼ぶ)にさかのぼって証明を進める。

ある Goal の全ての Sub Goal が証明出来れば、これ以上の Sub Goal が存在しなくなった時に証明が終わる。

Coq での演繹ルールの適用 tactic

Goal から Sub Goal の導出は、与えられた推論ルールの(後ろ向きの)適用に基づくのだが、 Coq では、その操作を tactic を適用するという。

tactic には、いろいろな種類があり、それぞれの tactics が、 Coq のインタラクティブな証明支援システムの中で、証明を進める為のコマンドとして機能する。

Coq での証明とは、ある Goal に対して、全ての Sub Goal がなくなるような、一連のtactic コマンドを与えることである。

様々なタイプの tactic コマンド

Coq には、非常に沢山の tactic コマンドがある。例えば、つぎのようなタイプの tactic がある。

論理的な(命題論理、一階の述語論理等の)推論ルール、あるいはそれらのルールの組み合わせに対応した基本的な tactic (intro, elim等 ) 。

システムの中で既に証明されている補題・定理を利用する tactic (apply 等 ) 。

a=b という等式を代入して Goal を書き換える tactic (rewrite 等 ) 。

数学的帰納法を使う tactics (induction等 ) 。

基本的な tactic について

... H:A ... ----------- A

tactic   assumption

現在のゴールが次のような形をしているとき、 tactic assumption を用いる。

exact H, trivial tactic を使ってもいい。 この tactic は、次の推論規則に対応してい

る。

... H:A ... ----------- B

... ... ... ----------- A → B

tactic intro

現在のゴールが A→B の形をしている時、tactic intro は次のように働く。

これは、次の推論規則に対応している。

intro H

... H: B→ A ... ----------- A

tactic apply

現在の仮定とゴールが次のような形をしているとき、 tactic apply を用いる。

対応する推論規則は、次になる。

... H: B→ A ... ----------- B

apply H

tactic apply

... H: A1→A2→...An→ A ... ----------------- A

... H: A 1→ ... ... ----------- A 1

apply H

... H: An→... ... ----------- An

...

より一般に、 apply H は n 個のサブゴールを生み出す。

対応する推論規則は。

Section Propositional_Logic.Variables P Q R : Prop.Lemma imp_dist : (P → (Q → R)) → (P → Q) → P → R.Proof.1 subgoalP : PropQ : PropR : Prop------------------------(P → Q → R) → (P → Q) → P → Rintros H H0 p.

intro と apply のサンプル

1 subgoal:P : PropQ : PropR : PropH : P → Q → RH0 : P → Qp : P------------------------Rapply H.

intro と apply のサンプル

2 subgoals:P : PropQ : PropR : PropH : P → Q → RH0 : P → Qp : P------------------------Psubgoal 2 is:Qassumption.

intro と apply のサンプル

intro と apply のサンプル1 subgoal:P : PropQ : PropR : PropT : PropH : P → Q → RH0 : P → Qp : P------------------------Qapply H0;assumption.Proof completedQed.imp dist is dened

... ... ... ------------- A∧B

... ... ... ----------- B

... ... ... ----------- A

tactic split

現在のゴールが A∧B の形をしている時、 tactic split は次のように、二つのサブゴールを作る。

対応する推論規則は。

split

... H1: A H2: B ... ----------- C

... H: A∧B ... ----------- C

tactic destruct

現在の仮定とゴールが次のような形をしているとき、 tactic destruct を用いる。

対応する推論規則。

destruct H as [H1 H2]

Lemma and_comm : P ∧ Q → Q ∧ P.Proof.intro H.1 subgoalP : PropQ : PropH : P ∧ Q------------------------Q ∧ P

destruct と split のサンプル

Lemma and_comm : P ∧ Q → Q ∧ P.Proof.intro H.1 subgoalP : PropQ : PropH : P ∧ Q------------------------Q ∧ P

destruct と split のサンプル

destruct と split のサンプル

destruct H as [H1 H2].1 subgoalP : PropQ : PropH1 : PH2 : Q------------------------Q ∧ Psplit.2 subgoalsP : PropQ : PropH1 : PH2 : Q------------------------Qsubgoal 2 is:P

... ... ... ----------- A∨B

tactic left, right

... ... ... ----------- A

left

現在のゴールが、 A∨B のような形をしているとき、 tactic left あるい right を用いる。

対応する推論規則は、次になる。

現在の仮定が A∨B の形をしている時、 tactic destruct は、二つのサブゴールを作る。

対応する推論規則は。

... A∨B ... ------------- C

... H2: B ... ----------- C

... H1: A ... ----------- C

tactic destruct

destruct H as [H1 H2]

Lemma or_comm : P ∨ Q → Q ∨ P.Proof.intros.P : PropQ : PropH : P ∨ Q------------------------Q ∨ P

left, right, destruct のサンプル

destruct H as [H0 | H0].two subgoalsP : PropQ : PropH : P ∨ QH0 : P------------------------Q ∨ Psubgoal 2 is :Q ∨ P

right; assumption.left; assumption.Qed.

tactic を使って見る

例1A -> (A->B) ->B の証明

Coq < Lemma S1 : forall A B :Prop, A ->(A->B) ->B.1 subgoal ============================ forall A B : Prop, A -> (A -> B) -> B

S1 < intros.1 subgoal A : Prop B : Prop H : A H0 : A -> B ============================ B

Goal から、 forall を消す。→の前提部分を仮説に移す。intros は、複数の intro を同時に適用する。

Γ1 新しい仮定

新しいゴール

S1 < apply H0.1 subgoal A : Prop B : Prop H : A H0 : A -> B ============================ A

S1 < assumption.No more subgoals.

S1 < Qed.intros.apply H0.assumption.

S1 is defined

H0 A->B を利用する。A をゴールに移す。

Γ 2

新しいゴール

仮定には、既に A が含まれている

証明終わり

新しい仮定

例 3(P \/ Q) /\ ~Q) -> ( R -> R /\P) の証明

Coq < Lemma S3: forall P Q R : Prop, (P \/ Q) /\ ~Q -> ( R -> R /\P).1 subgoal ============================ forall P Q R : Prop, (P \/ Q) /\ ~ Q -> R -> R /\ P

S3 < intros.1 subgoal P : Prop Q : Prop R : Prop H : (P \/ Q) /\ ~ Q H0 : R ============================ R /\ P

Goal から、 forall を消す。→の前提部分を仮説に移す。

Γ1 新しい仮定

新しいゴール

S3 < split.2 subgoals P : Prop Q : Prop R : Prop H : (P \/ Q) /\ ~ Q H0 : R ============================ R

subgoal 2 is: P

S3 < assumption.

ゴールの R∧P を、二つのサブゴールに分割する

Γ1 仮定

新しいゴール

もう一つの新しいゴール。仮定部分は表示されていない

ゴールの R は、既に仮定に含まれている。これで、一つのゴールの証明は終わり、証明すべきゴールは、一つになった。

1 subgoal P : Prop Q : Prop R : Prop H : (P \/ Q) /\ ~ Q H0 : R ============================ PS3 < destruct H.1 subgoal P : Prop Q : Prop R : Prop H : P \/ Q H1 : ~ Q H0 : R ============================ P

Γ1 仮定

新しいゴール

仮定の H を分割する。仮定中の∧のdestruct は、仮定を変更する。

Γ2 新しい仮定

新しいゴール

S3 < destruct H.2 subgoals P : Prop Q : Prop R : Prop H : P H1 : ~ Q H0 : R ============================ P

subgoal 2 is: P

S3 < assumption.

Γ3 新しい仮定

新しいゴール

仮定の H を分割する。仮定中の∨のdestruct は、仮定を変更するとともにゴールを枝分かれさせる。

ゴールの P は、既に仮定に含まれている。これで、一つのゴールの証明は終わり、証明すべきゴールは、一つになった。

1 subgoal P : Prop Q : Prop R : Prop H : Q H1 : ~ Q H0 : R ============================ P

S3 < absurd Q.

Γ4 新しい仮定

新しいゴール

Q についての仮定は矛盾している。Q か〜 Q のどちらかが正しい

2 subgoals P : Prop Q : Prop R : Prop H : Q H1 : ~ Q H0 : R ============================ ~ Q

subgoal 2 is: Q

S3 < assumption.

Γ4 新しい仮定

新しいゴール

ゴールの〜 Q は、既に仮定に含まれている。これで、一つのゴールの証明は終わり、証明すべきゴールは、一つになった。

1 subgoal P : Prop Q : Prop R : Prop H : Q H1 : ~ Q H0 : R ============================ Q

S3 < assumption.No more subgoals.

Γ4 新しい仮定

新しいゴール

ゴールの Q は、既に仮定に含まれている。

S3 < Qed.intros.split. assumption. destruct H. destruct H. assumption. absurd Q. assumption. assumption.

S3 is defined

n x m = m x n を証明する

n x m = m x n のn についての帰納法での証明 n=0 の時

0 x m = m x 0 0 = 0 となり、成り立つ。

n x m = m x n が成り立つと仮定して、(n + 1) x m = m x (n + 1) を証明する。左辺 = n x m + m右辺 = m x n + m帰納法の仮定より、 n x m = m x n なので、これを左辺に代入すると、左辺=右辺となる。

ただ、先の証明には、いくつかの前提がある 補題1  0 x m = 0 補題2  m x 0 = 0 補題3  (n + 1) x m = n x m + m 補題4  m x (n +1) = m x n + mこれらの補題を使って

定理  n x m = m x nを証明する。

次の補題が証明されたとしよう Lemma lemma1:

forall n:nat, 0 * n = 0. Lemma lemma2 :

forall n:nat, n * 0 = 0. Lemma lemma3:

forall n m:nat, S n * m = n * m + m. Lemma lemma4:

forall n m:nat, m * S n = m * n + m.

この時、定理の証明は次のように進む

Coq < Theorem Mult_Commute : forall n m:nat, n * m = m * n.1 subgoal ============================ forall n m : nat, n * m = m * n

Mult_Commute < intros.1 subgoal n : nat m : nat ============================ n * m = m * n

forall を消す。

Mult_Commute < induction n.2 subgoals m : nat ============================ 0 * m = m * 0

subgoal 2 is: S n * m = m * S n

Mult_Commute < simpl.2 subgoals m : nat ============================ 0 = m * 0

subgoal 2 is: S n * m = m * S n

Mult_Commute < auto.

n についての帰納法で証明する。

n = 0 の場合。

n で成り立つと仮定して、 n+1 で成り立つことを示す。

0 * m = 0 は、定義からすぐに言えるので、単純化する。

単純な式は、 auto でも証明出来る。

1 subgoal n : nat m : nat IHn : n * m = m * n ============================ S n * m = m * S n

Mult_Commute < rewrite lemma4.1 subgoal n : nat m : nat IHn : n * m = m * n ============================ S n * m = m * n + m

lemma4 : m * S n = m * n + m  を使って、ゴールを書き換える。

Mult_Commute < rewrite lemma3.1 subgoal n : nat m : nat IHn : n * m = m * n ============================ n * m + m = m * n + m

Mult_Commute < rewrite IHn.1 subgoal n : nat m : nat IHn : n * m = m * n ============================ m * n + m = m * n + m

Mult_Commute < reflexivity.No more subgoals.

lemma3 : (S n) * m = n * m + m  を使って、ゴールを書き換える。

IHn : n * m = m * n を使って、ゴールを書き換える。

左辺と右辺は等しい

Mult_Commute < Qed.intros.induction n. simpl. auto.

rewrite lemma4. rewrite lemma3. rewrite IHn. reflexivity.

Mult_Commute is defined

補題1の証明

Coq < Lemma lemma1: forall n:nat, 0 * n = 0.1 subgoal ============================ forall n : nat, 0 * n = 0

lemma1 < intros.1 subgoal n : nat ============================ 0 * n = 0

lemma1 < induction n.2 subgoals ============================ 0 * 0 = 0

subgoal 2 is: 0 * S n = 0

lemma1 < simpl.2 subgoals ============================ 0 = 0

subgoal 2 is: 0 * S n = 0

lemma1 < reflexivity.1 subgoal n : nat IHn : 0 * n = 0 ============================ 0 * S n = 0

lemma1 < constructor.No more subgoals.

lemma1 < Qed.intros.induction n. simpl. reflexivity. constructor.

lemma1 is defined

Fixpoint mult (n m :nat) : nat :=match n with| 0 => 0| S p => m + mult p mend.という定義を考えよう。この時。コンストラクタ0が、この式が成り立つことを示している。先の証明で、 simpl. auto. で証明したのも同じことである。

lemma1 < induction n.2 subgoals ============================ 0 * 0 = 0

subgoal 2 is: 0 * S n = 0

lemma1 < simpl.2 subgoals ============================ 0 = 0

subgoal 2 is: 0 * S n = 0

lemma1 < reflexivity.1 subgoal n : nat IHn : 0 * n = 0 ============================ 0 * S n = 0

lemma1 < constructor.No more subgoals.

lemma1 < Qed.intros.induction n. simpl. reflexivity. constructor.

lemma1 is defined

補題 2 の証明

Coq < Lemma lemma2: forall n:nat, n * 0 = 0.intros.auto with arith.Qed.

単純な式は、 auto でも証明出来る。こちらは、もっとキチンとした書き方。

補題3の証明

Coq < Lemma lemma3: forall n m:nat, S n * m = n * m + m.1 subgoal ============================ forall n m : nat, S n * m = n * m + m

lemma3 < intros.1 subgoal n : nat m : nat ============================ S n * m = n * m + m

lemma3 < simpl.1 subgoal n : nat m : nat ============================ m + n * m = n * m + m

lemma3 < apply plus_comm.No more subgoals.

lemma3 < Qed.intros.simpl.apply plus_comm.

lemma3 is defined

plus_comm : forall n m : nat, n + m = m + nで。和の可換性を示す補題。既に証明されたものとして、証明中で利用出来る。

補題4の証明

Coq < Lemma lemma4: forall n m:nat, m * S n = m * n + m.1 subgoal ============================ forall n m : nat, m * S n = m * n + m

lemma4 < intros.1 subgoal n : nat m : nat ============================ m * S n = m * n + m

lemma4 < symmetry.1 subgoal n : nat m : nat ============================ m * n + m = m * S n

lemma4 < apply mult_n_Sm.No more subgoals.

lemma4 < Qed.intros.symmetry.apply mult_n_Sm.

lemma4 is defined

mult_n_Sm : forall n m : nat, n * m + n = n * S mを利用する。この補題は、意外と証明が難しい。

証明に必要な情報を見つける

証明に必要な定理・補題を見つけるSearch

Coq は、証明に利用出来る定理・補題のデータベースを持っている。

Search コマンドを使うと、それらの名前と型が検索出来る。

Coq < Search le.le_S: forall n m : nat, n <= m -> n <= S mle_n: forall n : nat, n <= nplus_le_reg_l: forall n m p : nat, p + n <= p + m -> n <= mplus_le_compat_r: forall n m p : nat, n <= m -> n + p <= m + pplus_le_compat_l: forall n m p : nat, n <= m -> p + n <= p + mplus_le_compat: forall n m p q : nat, n <= m -> p <= q -> n + p <= m + qnth_le: forall (P Q : nat -> Prop) (init l n : nat), P_nth P Q init l n -> init <= l......

文字列を使って検索するSearchAbout

Coq < SearchAbout "comm".Bool.orb_comm: forall b1 b2 : bool, (b1 || b2)%bool = (b2 || b1)%boolBool.andb_comm: forall b1 b2 : bool, (b1 && b2)%bool = (b2 && b1)%boolBool.xorb_comm: forall b b' : bool, xorb b b' = xorb b' bapp_comm_cons: forall (A : Type) (x y : Datatypes.list A) (a : A), a :: x ++ y = (a :: x) ++ yand_comm: forall A B : Prop, A /\ B <-> B /\ Aor_comm: forall A B : Prop, A \/ B <-> B \/ AMax.max_comm: forall n m : nat, max n m = max m nMin.min_comm: forall n m : nat, min n m = min m nmult_comm: forall n m : nat, n * m = m * nplus_comm: forall n m : nat, n + m = m + nRelation_Definitions.commut: forall A : Type, Relation_Definitions.relation A -> Relation_Definitions.relation A -> Prop........l

パターンを使って検索するSearchPattern

Coq < SearchPattern (_ + _ = _ + _).plus_permute_2_in_4: forall n m p q : nat, n + m + (p + q) = n + p + (m + q)plus_permute: forall n m p : nat, n + (m + p) = m + (n + p)plus_comm: forall n m : nat, n + m = m + nplus_assoc_reverse: forall n m p : nat, n + m + p = n + (m + p)plus_assoc: forall n m p : nat, n + (m + p) = n + m + pplus_Snm_nSm: forall n m : nat, S n + m = n + S m........

先の mult_n_Sm は、次のような検索で見つけることが出来る。

Coq < SearchPattern (_ + _ = _ * _).mult_n_Sm: forall n m : nat, n * m + n = n * S m

二分木についての定理の証明

http://users.csc.calpoly.edu/~zwood/teaching/csc471/finalproj26/mma/

二分木の定義

Inductive natBinTree : Set :=| Leaf : natBinTree| Node (n:nat)(t1 t2 : natBinTree).

Definition t0 : natBinTree :=Node 5 (Node 3 Leaf Leaf)(Node 8 Leaf Leaf).

3 8

t0

Fixpoint tree_size (t:natBinTree): nat :=match t with| Leaf => | Node _ t1 t2 => 1 + tree_size t1 + tree_size t2 end.

Require Import Max.Fixpoint tree_height (t: natBinTree) : nat :=match t with| Leaf => 1| Node _ t1 t2 => 1 + max (tree_height t1) (tree_height t2)end.

Compute tree_size t0. = 7: natCompute tree_height t0. = 3 : nat

Definition t1 : natBinTree :=Node 4 (Node 7 Leaf Leaf)(Node 1 Leaf t0).

Compute tree_size t1. = 13 : natCompute tree_height t1. = 5 : nat

4

7 1

t1

3 8

Require Import List.Fixpoint labels (t: natBinTree) : list nat :=match t with| Leaf => nil| Node n t1 t2 => labels t1 ++ (n :: labels t2)end.

Compute labels (Node 9 t0 t0). = 3 :: 5 :: 8 :: 9 :: 3 :: 5 :: 8 :: nil : list nat

3 8

3 8

9

この時、次のことを証明しよう。「木 t のサイズが1でなければ、 t を構成する部分木 t1,t2 が存在する。」

命題の形で表すと次のようになる。 forall t, tree_size t <> 1 -> exists n:nat, exists t1:natBinTree, exists t2:natBinTree, t = Node n t1 t2.

以下は、 Coq での証明である。

Coq < Lemma tree_decompose : forall t, tree_size t <> 1 ->exists n:nat, exists t1:natBinTree,exists t2:natBinTree,t = Node n t1 t2.

============================ forall t : natBinTree, tree_size t <> 1 -> exists (n : nat) (t1 t2 : natBinTree), t = Node n t1 t2

tree_decompose < Proof.tree_decompose < intros t H.1 subgoal t : natBinTree H : tree_size t <> 1 ============================ exists (n : nat) (t1 t2 : natBinTree), t = Node n t1 t2

tree_decompose < destruct t as [ | i t1 t2]

2 subgoals H : tree_size Leaf <> 1 ============================ exists (n : nat) (t1 t2 : natBinTree), Leaf = Node n t1 t2

subgoal 2 is: exists (n : nat) (t3 t4 : natBinTree), Node i t1 t2 = Node n t3 t4

tree_decompose < destruct H.2 subgoals ============================ tree_size Leaf = 1

subgoal 2 is: exists (n : nat) (t3 t4 : natBinTree), Node i t1 t2 = Node n t3 t4

tree_decompose < reflexivity.

1 subgoal i : nat t1 : natBinTree t2 : natBinTree H : tree_size (Node i t1 t2) <> 1 ============================ exists (n : nat) (t3 t4 : natBinTree), Node i t1 t2 = Node n t3 t4

tree_decompose < exists i.1 subgoal i : nat t1 : natBinTree t2 : natBinTree H : tree_size (Node i t1 t2) <> 1 ============================ exists t3 t4 : natBinTree, Node i t1 t2 = Node i t3 t4

tree_decompose < exists t1.

1 subgoal i : nat t1 : natBinTree t2 : natBinTree H : tree_size (Node i t1 t2) <> 1 ============================ exists t3 : natBinTree, Node i t1 t2 = Node i t1 t3

tree_decompose < exists t2.1 subgoal i : nat t1 : natBinTree t2 : natBinTree H : tree_size (Node i t1 t2) <> 1 ============================ Node i t1 t2 = Node i t1 t2

tree_decompose < reflexivity.No more subgoals.

tree_decompose < Qed.intros t H.destruct t as [| i t1 t2]. destruct H. reflexivity.

exists i. exists t1. exists t2. reflexivity.

tree_decompose is defined

Coq <

Coq からのプログラムの抽出

Coq < Extraction Language Haskell.Coq < Require Import List.Coq < Print length.length = fun A : Type =>fix length (l : Datatypes.list A) : nat := match l with | Datatypes.nil => 0 | _ :: l' => S (length l') end : forall A : Type, Datatypes.list A -> nat

Argument A is implicitArgument scopes are [type_scope list_scope]

Coq < Extraction length.length :: (List a1) -> Natlength l = case l of { Nil -> O; Cons y l' -> S (length l')}

Coq < Print app.app = fun A : Type =>fix app (l m : Datatypes.list A) {struct l} : Datatypes.list A := match l with | Datatypes.nil => m | a :: l1 => a :: app l1 m end : forall A : Type, Datatypes.list A -> Datatypes.list A -> Datatypes.list A

Argument A is implicitArgument scopes are [type_scope list_scope list_scope]

Coq < Extraction app.app :: (List a1) -> (List a1) -> List a1app l m = case l of { Nil -> m; Cons a l1 -> Cons a (app l1 m)}

Coq < Extraction Language Ocaml.Coq < Extraction length.(** val length : 'a1 list -> nat **)

let rec length = function| Nil -> O| Cons (y, l') -> S (length l')

Coq < Extraction app.(** val app : 'a1 list -> 'a1 list -> 'a1 list **)

let rec app l m = match l with | Nil -> m | Cons (a, l1) -> Cons (a, (app l1 m))

Coq < Extraction Language Scheme.

Coq < Extraction length.(define length (lambda (l) (match l ((Nil) `(O)) ((Cons y l~) `(S ,(length l~))))))

Coq < Extraction app.(define app (lambdas (l m) (match l ((Nil) m) ((Cons a l1) `(Cons ,a ,(@ app l1 m))))))

あらためて、p : P ( 証明 : 命題 ) を考える

証明の関数としての表現

証明の関数としての表現

Coq < Print S1.S1 = fun (A B : Prop) (H : A) (H0 : A -> B) => H0 H : forall A B : Prop, A -> (A -> B) -> B

Argument scopes are [type_scope type_scope _ _]

Coq < Print S2.S2 = fun (P Q : Prop) (H : forall P0 : Prop, (P0 -> Q) -> Q) (H0 : (P -> Q) -> P) =>H0 (fun _ : P => H (P -> Q) (H P)) : forall P Q : Prop, (forall P0 : Prop, (P0 -> Q) -> Q) -> ((P -> Q) -> P) -> P

Argument scopes are [type_scope type_scope _ _]

Coq < Print S3.S3 = fun (P Q R : Prop) (H : (P \/ Q) /\ ~ Q) (H0 : R) =>conj H0 match H with | conj (or_introl H3) _ => H3 | conj (or_intror H3) H2 => False_ind P ((fun H4 : Q => (fun H5 : ~ Q => (fun (H6 : ~ Q) (H7 : Q) => H6 H7) H5) H2 H4) H3) end : forall P Q R : Prop, (P \/ Q) /\ ~ Q -> R -> R /\ P

Argument scopes are [type_scope type_scope type_scope _ _]

Coq < Print lemma1.lemma1 = fun n : nat =>nat_ind (fun n0 : nat => 0 * n0 = 0) eq_refl (fun (n0 : nat) (_ : 0 * n0 = 0) => eq_refl) n : forall n : nat, 0 * n = 0

Coq < Print lemma2.lemma2 = fun n : nat =>nat_ind (fun n0 : nat => n0 * 0 = 0) eq_refl (fun (n0 : nat) (IHn : n0 * 0 = 0) => IHn) n : forall n : nat, n * 0 = 0

Argument scope is [nat_scope]

Coq < Print lemma3.lemma3 = fun n m : nat => plus_comm m (n * m) : forall n m : nat, S n * m = n * m + m

Argument scopes are [nat_scope nat_scope]

Coq < Print lemma4.lemma4 = fun n m : nat => eq_sym (mult_n_Sm m n) : forall n m : nat, m * S n = m * n + m

Coq < Print mult_n_Sm.mult_n_Sm = fun n m : nat =>nat_ind (fun n0 : nat => n0 * m + n0 = n0 * S m) eq_refl (fun (p : nat) (H : p * m + p = p * S m) => let n0 := p * S m in match H in (_ = y) return (m + p * m + S p = S (m + y)) with | eq_refl => eq_ind (S (m + p * m + p)) (fun n1 : nat => n1 = S (m + (p * m + p))) (eq_S (m + p * m + p) (m + (p * m + p)) (nat_ind (fun n1 : nat => n1 + p * m + p = n1 + (p * m + p)) eq_refl (fun (n1 : nat) (H0 : n1 + p * m + p = n1 + (p * m + p)) => f_equal S H0) m)) (m + p * m + S p) (plus_n_Sm (m + p * m) p) end) n : forall n m : nat, n * m + n = n * S m

Argument scopes are [nat_scope nat_scope]

top related