純粋型システムとλキューブ

公開日: 2022/10/08


この記事では、型システムの統一的な定式化を与える 純粋型システム と呼ばれる枠組みを紹介し、それをもとに λキューブ として8つの型システムを導入する。

非形式的な説明

抽象化の方法

型システムにおいて重要な対象は関数である。関数は入力を受け取って出力を返すものと見ることもできるが、より一般的にはプログラムの一部を変数として 抽象化(abstract) ないし パラメータ付け(parameterize) したものと見ることができる。

さて、どのようなプログラムの中で何を抽象化するかについては、複数の場合がありえる。どの抽象化の方法を許容するかによって、その型システムの表現力が様々に変わることになる。ここでは、プログラムの要素として項と型を考え、合計2×2の4通りを例とともに見る1

① 項の中の項を抽象化

これは通常の関数のことである。同じような処理を何度も書くことを避けるために、プログラムの一部を変数にして特定の処理を関数にするのはよくある手法である。これを「項の中の項を抽象化する」と言い直しているだけである。

以下の例は自然数に1を足す関数を表している。なお、自然数の型 Nat やその上の演算は予め定義されているものとする。

plus_one : Nat  Natplus_one n = n + 1

このような関数は plus_one = λ(n : Nat). n + 1 のようにλ記号を使って書かれることもある。

② 項の中の型を抽象化

これはいわゆる 多相関数(polymorphic function) である。型によらず同じ処理をする関数を1つにまとめるようなイメージである。例えば、以下の関数 id は任意の型の項を受け取り、それをそのまま返す恒等関数である。

id : Π(A : Type). A  Aid A x = x

Type は「型の型」のようなものであり(詳細は後述)、A は型が代入されるような変数になっている。これに型を渡すことによって、idA の項を受け取る関数になる2。以下の例では、Aとして自然数の型 Nat を入力している。

id_nat : Nat  Natid_nat = id Natone : Natone = id_nat 1

③ 型の中の項を抽象化

これは「項に依存する型」ということで 依存(関数)型(dependent (function) type) と呼ばれ、非形式的には入力の値によって出力の型が変わるような関数の型と説明できる(依存型が入力を受け取るわけではないので注意)。より具体的には、依存型Πx:A.Bに属する項fは、入力a:Aに対して出力f(a):B[x:=a]を返す。

なお、依存型は通常の関数型の一般化であると言える。実際、依存型Πx:A.BであってxFV(B)であるようなものは、関数型ABと見なせる。

以下に依存型の例を挙げるが、関数型プログラミングに慣れていないと分かりにくいと思われるので、読み飛ばしても構わない。浮動小数点数Floatのリスト型を拡張して、次のように「長さn(固定長)のFloatリスト型」を定義することを考える。

data FloatList : Nat  Type where    nil : FloatList 0    cons : Π(n : Nat). Float  FloatList n  FloatList (n + 1)

このとき、cons は依存型に属する項になっているため、例えば cons 2 は長さ2のリストと値を結合する関数となり、それ以外の長さの入力は受け付けない。

list : FloatList 3list = cons 2 0.125 (cons 1 0.24 (cons 0 0.375 nil))    -- list = [0.125 , 0.25 , 0.375]

このようにして、リストを入力として受け取る関数の仕様をより詳細に記述できる。例えばリストの間で内積を定義したい場合には、入力として受け取る2つのリストの長さが等しいことを依存型によって保証できる。

dot : Π(n : Nat). FloatList n  FloatList n  Floatdot = ...

なお、カリー・ハワード同型対応に基づいて命題を型と見なすならば、依存関数型は1階述語論理における全称量化に対応する。 例として、自然数上の加法の可換性を表す命題

P =def m,nN.m+n=n+m

を考えると、これは依存型 Π(m n : Nat). ... として表現できる。よって、例えば1+2=2+1という命題は「依存型 P12 を入力として渡して得られる型」と見なせる。さらに、「命題の証明」は「型に属する項」に対応するので、命題Pの証明(すなわち項 f : P )に対して1,2を入力として渡せば、1+2=2+1の証明が得られる(下のコードを参照)。

proof : Π(m n : Nat). m + n  n + mproof = ...    -- ここに証明を書くproof_one_two : 1 + 2  2 + 1proof_one_two = proof 1 2

このように全称量化された命題の証明を「要素を受け取って証明を返す関数」と見なす考え方は、構成主義におけるBHK解釈(BHK interpretation)に基づく。詳しくは直観主義1階述語論理の記事を参照のこと。

④ 型の中の型を抽象化

これは①(通常の関数)の型バージョンであり、型構成子, 型構築子(type constructor) などと呼ばれる。例えば次の関数は2つの型を受け取り、その直積を返している。

prod : Type  Type  Typeprod A B = A × B

型とカインド

以上のように様々な種類の抽象化を考えるときに注意しなければならないのは、関数に対して適切ではない入力が渡されないようにすることである。通常の関数を扱う程度の範囲では、このことは型が項を分類することによって保証される。例えば plus_one : Nat Nat"abc" : String と型が付いていることにより、plus_one "abc" のような誤った組み合わせを防ぐことができる。

しかし、③や④のように型において抽象化がなされる場合、型に対しても適切な分類を与える必要がある。 例えば上で登場した prod は第1引数に型 A : Type を受け取るべきであるから、prod prod のようなプログラムは排除しなければならない。

このために使われるのが カインド(kind) である。カインドは型を分類するものであり、いわば「型の型」である。カインドの例を以下にいくつか挙げる。

  • Type : 項を分類する型が属するカインド34
    • 属する対象の例: Nat, Nat Nat, Π(A : Type). A A5, Π(n : Nat). FloatList n FloatList n FLoat
    • カインドに分類される対象を指す「型」という用語と区別するために、これらの(項を分類するものとしての)型を特に 真の型(proper type) ということがある。
  • Type Type Type : 2引数の型構成子が属するカインド
    • 属する対象の例: prod
  • Nat Type : 自然数を受け取って型を返す関数が属するカインド
    • 属する対象の例: FloatList

型にとってのカインドは項にとっての型であり、「項 : 型 : カインド」という階層構造ができることになる。これまでに登場した例をまとめて図示すると、下図のようになる(赤い点線の矢印が所属関係を表す)。

項・型・カインドがなす階層構造

以上で4種類の抽象化の方法を導入した。本記事のテーマの1つであるλキューブを理解する上ではこの4つがあれば十分だが、他にも抽象化のパターンは様々に存在する。例えば、

  • カインドの中でも項・型・カインドを抽象化することができる。この場合、カインドを分類するための上位のモノ(例えば、カインド全体の集まり Kind )が必要になる。
  • さらに上位の対象へと分類を与えていくことで、カインドの階層を Type : Type' : Type'' : ... のように重ねていくことができる(このような場合、これらは ソート(sort)宇宙(universe) と呼ばれる)。
  • Type と並列して、型を分類するような別のカインドを導入することもできる6

このような様々な抽象化のあり方を記述できるのが、次に説明する純粋型システムである。

純粋型システム

以下では、加算無限個の変数の集合Varが与えられているものとする。メタ変数として、変数をx,Pなど、項や型をM,N,A,Bなどで表す。

定義. 純粋型システム

純粋型システム(pure type system) を、以下から成る組S,A,Rとする。

  • S: 非空な集合
  • AS2
  • RS3

S,A,Rの元をそれぞれ ソート(sort), 公理(axiom), 規則(rule) という。s1,s2,s3Rに対して、s2=s3のとき(s1,s2)と略記する。

ソートはカインドを一般化したものであり、公理はその間の階層関係を記述したものである。そして規則は、抽象化の方法として何を許容するかを規定する (ただし「公理」「規則」という言葉は一般名詞としても使うので注意されたい)。

次に構文に関する定義を行うが、構文的には項や型の間の区別がなされないことに注意する必要がある。 項 : 型 : カインド : ... といった階層的区別は、後述の推論規則によって与えられる。

定義. 前項

純粋型システムS,A,R前項(preterm) の集合Λを以下のBNFで定める。補助記号の括弧は適宜省略する。

A::=xsAAλx:A.AΠx:A.A

自由変数FV(A)や代入操作A[x:=B]は通常通り定義し、またα同値な表現を同一視する。

また、Πx:A.BにおいてxFV(B)のとき、これをABと略記することがある。

定義. 文脈・判断

文脈(context) を以下のBNFで定める。空の文脈は適宜省略するものとする。

Γ::=x:A,Γ

文脈Γ=x1:A1,,xn:An,に対して、その定義域(domain)を集合{x1,,xn}とし、dom(Γ)で表す。

また、文脈ΓM,AΛに対して、ΓM:A判断(judgement) という。

定義. 簡約

前項に対する簡約の簡約基を以下により定める。

(λx:A.B)CB[x:=C]

これによって定まる合同関係をβで表す(1ステップの簡約)。また、この反射推移閉包をβとし(0ステップ以上の簡約)、これにより定まる同値関係を=βで表す。

以上を踏まえて、純粋型システムにおいて判断を導出するための規則を導入しよう。これは通常の型システムにおける型付け規則と同様のものである。

定義. 推論規則・導出可能性

純粋型システムS,A,Rの推論規則を以下のように定義する。ただし、推論規則は一般に次の形式で書くものとする(各J1,,Jnは前提(nN), Jは結論)7

J1JnJ (追加の条件)

(Ax): ソート間の階層関係を与える。

s1:s2 (s1,s2A)

(Var): 変数を導入する。

ΓA:sΓ,x:Ax:A (xdom(Γ))

(Prod): 各ソートに属する対象を形成する。Rの規則によって抽象化の方法が規定されている。

ΓA:s1Γ,x:AB:s2ΓΠx:A.B:s3 (s1,s2,s3R)

(Abs): Prod規則で形成された対象に属する要素を構成する方法を与える(自然演繹の導入則に対応)。

Γ,x:AB:CΓΠx:A.C:sΓλx:A.B:Πx:A.C

(App): Abs規則の要素に値を適用する方法を与える(自然演繹の除去則に対応)。

ΓA:Πx:B.CΓA:BΓAA:C[x:=A]

(Wk): 文脈を水増しする(弱化)。

ΓA:BΓC:sΓ,x:CA:B (xdom(Γ))

(Conv): β同値な対象の間で変換を行う。

ΓA:BΓB:sΓA:B (B=βB)

判断J導出(derivation) を、「判断の有限列J1,,Jn=Jであって、各1inに対して結論がJiで前提が{Jjj<i}に含まれるような推論規則が存在するもの」とする。

判断ΓM:Aの導出が存在するとき、ΓM:A導出可能である(derivable) という。このことを単にΓM:Aと書くことがある。

最後に純粋型システムを1つ定義し、導出の例を示す。導出は通常の証明論のように木構造で書くこととする。

次のように純粋型システムを定義する。これは後述のλキューブにおけるλ2(system F)と等価である。

  • S={Type,Kind}
  • A={Type,Kind}
  • R={(Type,Type),(Kind,Type)}

このもとで、ΠP:Type.PP:Typeを示す(なお、これは冒頭の説明の多相関数 id の型が実際に型であることを表している)。

まず、P:TypePP:Typeを示す(PPΠx:P.Pの略記であることに注意)。

(Prod)(Var)(Ax)Type:KindP:TypeP:Type(Wk)(Var)(Ax)Type:KindP:TypeP:Type(Var)(Ax)Type:KindP:TypeP:TypeP:Type,x:Px:PP:TypePP:Type

最後のステップで規則(Type,Type)が使われている。次に、この導出図をDとして、全体の導出図を以下のように構成できる。

(Prod)(Ax)Type:KindDP:TypePP:TypeΠP:Type.PP:Type

最後のステップで規則(Kind,Type)が使われている。

λキューブ

λキューブとは、PTSの特別な場合として特徴づけられる8つの型システムのことである。以下ではS={,}, A={,}とする。Sの要素はそれぞれ以下を表す。

  • : (真の)型の集まり
  • : カインドの集まり

Rとしてあり得る要素は(s2=s3として)4通りあり、それぞれ冒頭の説明の4つの場合に対応する。

  1. (,): 項の中の項を抽象化(通常の関数)
  2. (,): 項の中の型を抽象化(多相関数)
  3. (,): 型の中の項を抽象化(依存型)
  4. (,): 型の中の型を抽象化(型構成子)

Rの要素の組み合わせを色々と変えることで、以下8つの体系を得る。表には対応する論理体系を載せてあるが、どれも直観主義論理であることに注意されたい。

体系の名前(,)(,)(,)(,)等価な型システム対応する論理体系
λ×××単純型付きλ計算命題論理
λ2××system F2階命題論理
λP××Logical Framework (LF)述語論理
λω××弱高階命題論理
λP2×2階述語論理
λω×system Fω高階命題論理
λPω×弱高階述語論理
λCCalculus of Construction (CC)高階述語論理

これらの型システム間の関係は、しばしば下図のような立方体で表される。これを λキューブ(lambda-cube) という。

(, )(, )(, )λωλCλ2λP2λωλPωλλP

λキューブに属する8つの型システムは、どれも以下のようなよい性質を満たす。

命題. λキューブの性質
  • 割り当ての一意性: ΓM:AかつΓM:AならばA=βA
  • 主部簡約定理: ΓM:AかつMβMならばΓM:A
  • 強正規化性: ΓM:Aならば、Mの任意の簡約列の長さは有限
  • Church-Rosser性: MβM, MβMならば、あるNΛが存在してMβN,MβN
  • 判断ΓM:Aが導出可能か否かは決定可能である

参考文献

Footnotes

  1. 例に使うのはAgda風の擬似コードである(だいたいHaskellみたいなものだと思っていただければよい)。
  2. ここでは多相型には明示的には型を渡す必要があり、このような定義をexplicit polymorphismという。一方で、OCamlのような言語で採用されている多相型では型を引数として渡す必要はなく、引数の項の型から自動的に関数の型が決まる。このような定義をimplicit polymorphismという。
  3. このカインドは、型システム・定理証明支援系によっては SetProp の名前で呼ばれることもある(この2つが別々に存在することもある)。体系によって性質(e.g. 可述性, proof-irrelevance)が異なるので注意が必要である。
  4. Type 自体は型ではない。Type : Type を許容すると、集合論において全ての集合の集まりを集合だとしてしまった場合と同様に、パラドックスが生じる(矛盾に対応する型 の項を構成できる)。
  5. Type に属する型が、Π(A : Type). ... のように Type 全体への量化を含むということは、定義において自分自身が言及されているということであり、これは循環的であり奇妙さを感じるかもしれない。が、さしあたり問題はない。このような型の定義を 非可述的である(impredicative) といい、ある程度制限された形で導入する限りでは、非可述性を許容しても型システムは壊れない(矛盾を引き起こさない)ことが知られている。
  6. 定理証明支援系Coqではこれに相当する措置が取られている。Coqには、ここでの Type に相当するものがProp, SProp, Setの計3つある。これらは全てType0という1つ上のレベルのソートに属する。詳しくはCoqのレファレンスなどを参照のこと。
  7. 「追加の条件」の部分には、形式体系の内部で妥当性が扱われるような言明(ここでは判断ΓM:A)ではなく、体系の外部で判定が行われるような言明が入る。これらが線の上側に(前提と並べて)書かれることもあるが、混同を防ぐために当サイトではそのような書き方は避ける。