この節では,MiniML2 の型推論アルゴリズムを設計するために,まず MiniML2 の型システムを定義する.
まず,MiniML2 の式に対しての型推論を考える.MiniML2 では,トップレベル入力として,式だけでなくlet
宣言を導入したが,ここではひとまず式についての型推論のみを考え,let
宣言については最後に扱うことにする.ここでまず MiniML2 の文法を見てほしい.
この言語に対する型を考えるわけなのだが,何を型として扱えばよいだろうか.MiniML2 は関数を含まず,値は整数値とブール値のみなので,整数型と真偽値型があれば良さそうである.したがって,型の構文は,メタ変数を$\tau$として,以下のように定義しよう.
\[\tau ::= \mathbf{int} \mid \mathbf{bool}.\]$\mathbf{int}$は整数型, $\mathbf{bool}$は真偽値型である.
我々がこれから作ろうとする型推論アルゴリズムは,式e
を受け取って,そのe
の型を(くどいようだが)e
を評価することなく 推論する.ここでさらっと「e
の型」と書いたが,この言葉の意味するところはそんなに明らかではない.素直に考えれば「e
を評価して得られる値v
の型」ということになるのだが「じゃあv
の型って何?」「v
の型を定義できたとして,型推論アルゴリズムが正しくその型を推論できていることはどう保証するの?」「e
が停止しないかもしれないプログラムだったら評価して得られる値はどう定義するの?」などの問題点にクリアに答えられるようにアルゴリズムを作りたい.
そのために,型推論アルゴリズムを作る際には,普通 型とは何か,プログラムe
が型$\tau$を持つのはどのようなときか 等をまず厳密に定義し,その型を発見するためのアルゴリズムとして型推論アルゴリズムを定義することが多い.このような,型に関する定義やアルゴリズムを含む体系を 型システム (type system) と呼ぶこまけぇこたぁいいんだよ!についての注.
こまけぇこたぁいいんだよ!について: 大体動けばいいんだよ,こまけぇこたぁいいんだよ!という考えもあるだろうが,だいたい動くと思って作ったものが動かないことはよくある.また,このように数学の俎上に乗るように体系を作っておくことで,「型がつくプログラムが実行時エラーを起こすことはない」(型システムの健全性 (soundness of a type system))とか「型推論アルゴリズムの結果は正しい」(型推論アルゴリズムの健全性 (soundness of a type inference algorithm))等の,型システム自体の性質に関する議論を数学を用いて厳密に行うことができる.また,型システムを設計するパターンをこのように大体決めておくことで,健全性の証明もある程度パターン化することができ,新しい言語に対する新しい型システムがきれいに容易に分かりやすく設計できるようになる.
「式$e$が型$\tau$を持つ」のような,式と型の間の関係のことを 型判断 (type judgment) と呼ぶ「判断」という言葉について.普通式と型の間の関係は$e : \tau$と書くので,ここでもこれに習おう.何が正しい型判断で,何が間違った型判断なのかをあとで定義するのだが,例えば「式1+1
は$\mathbf{int}$を持つ」ように型システムを作りたいので,$1+1 : \mathbf{int}$は正しい型判断になるように,式if 1 then 2+3 else 4
は型がつかないプログラムなので,$\mathbf{if}\ 1\ \mathbf{then}\ 2+3\ \mathbf{else}\ 4 : \tau$はいかなる$\tau$についても正しくない型判断となるようにしたい.
「判断」という言葉について: フレーゲかカントに由来するらしい.裏は取っていない.(誰か裏が取れたら教えて下さい.)
しかし,MiniML2 に意図通りに型判断を定義するのに$e$と$\tau$だけでは実は情報が足りない.一般に式には自由変数が現れるからである.例えば「式$x+2$は$\mathbf{int}$を持つ」は正しい判断にしたいだろうか.「それは$x$の型による」としか言いようがない.($x$が$\mathbf{int}$型であれば正しい判断にしたいし,$x$が$\mathbf{bool}$型であれば正しい型判断と認めたくはないだろう.)このため,自由変数を含む式に対しては,それが持つ型を何か仮定しないと型判断は下せないことになる.
この状況は,MiniML2 インタプリタを実装したときに,自由変数の値への束縛を環境というデータ構造で管理したのと相似である.いわば,我々は今 自由変数の型への束縛 を管理するデータ構造を必要としているのである.この,変数の型への束縛を管理するデータ構造を 型環境 (type environment) と呼ぶ.(型環境を表すメタ変数として$\Gamma$を用いる.)これを使えば,変数に対する型判断は,例えば
$\Gamma(x) = \mathbf{int}$ の時 $x: \mathbf{int}$ である
のように設計すればよさそうである.このことを考慮に入れて,型判断は,$\Gamma \vdash e : \tau$ と記述し,
型環境 $\Gamma$ の下で式 $e$ は型 $\tau$ を持つ
と読む.また,空の型環境を$\emptyset$で表す.型判断に用いた${\vdash}$ は数理論理学などで使われる記号で,「〜という仮定の下で判断〜が導出・証明される」くらいの意味である.インタプリタが(変数を含む)式の値を計算するために環境を使ったように,型推論器が式の型を計算するために型環境を使っていると考えればよい.
$\Gamma(x_1)=\tau_1,\dots,\Gamma(x_n)=\tau_n$を満たし,それ以外の変数については型が定義されていないような型環境を$x_1 : \tau_1,\dots,x_n : \tau_n$と書く.
型判断$\Gamma \vdash e : \tau$が成り立つような$\Gamma$と$\tau$が存在するときに,$e$に 型がつく (well-typed),あるいは$e$は 型付け可能 (typable) という.逆にそのような$\Gamma$と$\tau$が存在しないときに,$e$は 型がつかない (ill-typed),あるいは$e$は 型付け不能 (untypable) という.
型判断を導入したからには「正しい型判断」を定義しなければならない.これには 型付け規則 (typing rule) を使うのが定石である.これは,記号論理学の証明規則に似た「正しい型判断」の導出規則で
<型判断>1 ... <型判断>n
----------------------<規則名>
<型判断>
という形をしている.横線の上の<型判断>1 ... <型判断>n
を規則の 前提 (premise),下にある <型判断>
を規則の 結論 (conclusion) と呼ぶ.例えば,以下は加算式の型付け規則である.
この,型付け規則の直感的な意味(読み方)は, 前提の型判断が全て導出できたならば,結論の型判断を導出してよい ということである.導出規則についての注
導出規則についての注(この脚注は意味がわからなければ飛ばして良い.)厳密には規則 T-Plus
はメタ変数$e_1,e_2,\Gamma$を具体的な式や型環境に置き換えて得られる(無限個の)導出規則の集合を表したものである.例えば,$\emptyset \vdash 1 : \mathbf{int}$ という型判断が既に導出されていたとしよう.T-Plus
の $\Gamma$ を $\emptyset$ に,$e_1$, $e_2$ をともに,$1$に具体化することによって,規則の インスタンス (instance)
が得られる.この具体化された規則を使うと,型判断$\emptyset \vdash 1 + 1 : \mathbf{int}$が導出できる.
以下に,MiniML2 の型付け規則を,その背後にある直観とともに示す.(付言するが,ここでの説明は,あくまで理解の助けにするための型付け規則の背後にある直観であって,型付け規則自体ではない.)
$\Gamma(x) = \tau$であれば,$\Gamma$のもとで式$x$が型$\tau$を持つという判断を導出してよい.$\Gamma$が式の中の自由変数の型を決めているということを表現している.
整数リテラル$n$は,いかなる型環境の下でも型$\mathbf{int}$を持つ.そりゃそうですね.
また,式true
と式false
は,いかなる型環境の下でも型$\mathbf{bool}$を持つ.これらは直観的に理解できると思う.
型環境$\Gamma$の下で式$e_1$と式$e_2$が型$\mathbf{int}$を持つことが導出できたならば,$\Gamma$の下で式$e_1 + e_2$が$\mathbf{int}$を持つことを導出してよい.加算が整数の上の演算であることからこのような規則になっている.
式$e_1 * e_2$も同様に整数の上の演算なので,型環境$\Gamma$の下で式$e_1$と式$e_2$が型$\mathbf{int}$を持つことが導出できたならば,$\Gamma$の下で式$e_1 * e_2$が$\mathbf{int}$を持つことを導出してよい.
型環境$\Gamma$の下で式$e_1$と式$e_2$が型$\mathbf{int}$を持つことが導出できたならば,$\Gamma$の下で式$e_1 < e_2$が$\mathbf{bool}$を持つことを導出してよい.これらは式$e_1 < e_2$が整数の比較演算で,返り値がブール値であることから設けられた規則である.
if
式に関する規則型環境$\Gamma$の下で式$e$が$\mathbf{bool}$を持ち,式$e_1$と式$e_2$が同一の型$\tau$を持つならば,$\mathbf{if}\ e\ \mathbf{then}\ e_1\ \mathbf{else}\ e_2$がその型$\tau$を持つことを導出してよい.式$e$は$\mathbf{if}$式の条件部分なので,型$\mathbf{bool}$を持つべきであることは容易に納得できるであろう.
式$e_2$と式$e_3$が同一の型$\tau$を持つべきとされていること,if
式全体としてその型$\tau$を持つとされていることについては少し注意が必要である.これは,条件式$e_1$がtrue
とfalse
のどちらに評価されても実行時型エラーが起こらないようにするために設けられている条件である.これにより,実際は絶対に実行時型エラーが起こらないのに型付け可能ではないプログラムが生じる. たとえば,
(if true then 1 else false) + 3
というプログラムを考えてみよう.このプログラムは,if
式が必ず1
に評価されるため,実行時型エラーは起こらない.しかし,このif
式のthen
節の式1
には型$\mathbf{int}$がつき,else
節の式false
には型$\mathbf{bool}$がつくので,if
式は型付け不能である.「型付け不能」についての注
「型付け不能」について: ある式$e$が型付け不能であることを言うには,いかなる$\Gamma$と$\tau$をもってきても,$\Gamma \vdash e : \tau$を導けないことを言わなければならないので,この説明は厳密には不十分である.
let
式に関する規則まず,この規則に現れる $\Gamma, x:\tau$ は $\Gamma$ に $x$ は $\tau$ であるという情報を加えた拡張された型環境で,より厳密には,
\[\begin{array}{c} (\Gamma, x:\tau)(y) = \left\{ \begin{array}{l} \tau & (if \ x=y) \\ \Gamma(y) & \textit{(otherwise)} \end{array}\right. \end{array}\]で定義される.ただし,この型環境の定義域 $\mathbf{Dom}(\Gamma, x:\tau)$ は
\[\mathbf{Dom}(\Gamma, x:\tau) = \mathbf{Dom}(\Gamma) \cup \{x\}\]で定義される.
この規則は,型環境$\Gamma$の下で式$e_1$が型$\tau_1$を持ち,式$e_2$が$\Gamma$を$x : \tau_1$というエントリで拡張して得られる型環境$\Gamma,x:\tau_1$の下で型$\tau_2$を持つならば,式$\mathbf{let}\ x = e_1\ \mathbf{in}\ e_2$は全体として$\tau_2$を持つという判断を導いてよい,ということを表している..この規則は$\mathbf{let}$式がどのように評価されるかと合わせて考えると分かりやすい.式$\mathbf{let}\ x = e_1\ \mathbf{in}\ e_2$を評価する際には,まず$e_1$を現在の環境で評価し,得られた結果に$x$を束縛した上で$e_2$を評価して,その結果を全体の評価結果とする.そのため,型付け規則においても,$e_1$の型付けには(「現在の環境」に対応する)型環境$\Gamma$を使い,$e_2$の型付けには$e_1$の型$\tau_1$を$x$の型とした型環境$\Gamma,x:\tau_1$を用いるのである.
型付け規則を導入したところで,具体的にどのような型判断が「正しい」とされるのかを定義しよう.型判断 $\Gamma \vdash e : \tau$ は,これが上記の型付け規則で 導出できる (derivable) ときに正しい型判断であると決める.この型判断が導出できる,とは,根が型判断$\Gamma \vdash e : \tau$で,上記のすべての辺が型付け規則に沿っている木が存在することである.(すべての葉は前提が無い型付け規則が適用された形になっている.)この木を型判断$\Gamma \vdash e : \tau$を導出する 導出木 (derivation tree) という.
例えば,以下は型判断$x : \mathbf{int} \vdash \mathbf{let}\ y = 3\ \mathbf{in}\ x + y : \mathbf{int}$の導出木である.
TODO: 導出木の画像を貼る
このように導出木が存在するので,型判断$x : \mathbf{int} \vdash \mathbf{let}\ y = 3\ \mathbf{in}\ x + y : \mathbf{int}$ は正しい.