• ch.22 型再構築
    • 22-0 この章でやりたいこと
    • 22-1 型変数と型代入
    • 22-2 型変数についての2つの視点
    • 22-3 制約に基づいた型付け
    • 22-4 単一化
    • 22-5 主要型
    • 22-6 暗黙の型注釈
    • 22-7 let 多相
    • 22-8 注釈
  •  #  chapter 22. 型再構築
    •  ◆  section 22-0. この章でやりたいこと

      これまでの計算体系の型検査アルゴリズムは、ラムダ抽象の引数の型を注釈する必要があり、明示的な型注釈に依存していた。
      本章では、型注釈に依存しない、より強力な型再構築アルゴリズムを説明する。

      型再構築は、レコードや部分型などの機能と組み合わせると複雑になりうる。このため、この章では単純型だけを考えることにする。
    •  ◆  section 22-1. 型変数と型代入

      Bool や Nat のような解釈のある基本型とは異なり、非解釈な基本型は何か特定の項に置き換えるためのプレースホルダーにすぎない。
      本章では、他の型を代入(または具体化)できる型変数として非解釈な基本型を扱う。

      定義 22.1.1

        型に対する代入の適用は、以下のような自明な方法で定義される。

        σ(X) = T ... X ∈ dom (σ) であり、(X -> T) ∈ σ の場合
        X ... X ∉ dom (σ)
        σ(X) = Nat
        σ(X) = Bool
        σ(X) = σT1 -> σT2

        定理 22.1.2 [型代入の下での型付けの保存]
        σ が任意の型代入で、かつ Γ├ t : T ならば、σ Γ├ σt : σT

        証明)型付け導出に関する単純な帰納法

      •  ◆  section 22-2. 型変数についての2つの視点

        型変数は型検査の間は 抽象的なままであるべきである。これにより正しく型付けされた項は、後に型変数にいかなる具体的な型を代入したとしても適切に振る舞うことが保証される。例えば、項

        λf : X -> X. λa : X. f (f a);

        は型 (X -> X) -> X -> X を持つ。X を具体的な型 T に置き換えたときはいつでも、その具体化

        λf : T -> T. λa : T. f (f a);

        は正しく型付けされる。

        元々の項 t がそもそも正しく型付けされていないとき、幾つかの型変数に適切な値を選ぶことで、その項を正しく型付けされた項に具体化できるだろうか。例えば、

        λf : Y. λa : X. f (f a);

        はこのままでは型付け可能ではない。しかし
        • Y を Nat -> Nat で置き換え、X を Nat で置き換える
        • Y を X -> X に置き換える
        などの具体化を行えば、型付け可能な項が得られる。

        後者の置き換えを採用した場合、それは最も一般的な具体化となる。つまり、正しく型付けされた項を得るために型変数の値について最小限の約束しかない、という意味である。

        型変数の妥当な具体化を探すことは、プログラマが型注釈を省略することを可能にする、型再構築のアイデアにつながる。

        1. 構文解析が終了した時点で、省略された型注釈部分にフレッシュな型変数を入れておく
        2. 型再構築を行い、全ての半数に対して型検査が成功するような最も一般的な値を見つける

        2 の過程は、let 多相(22.6, 22.7節参照)を導入すると少し複雑になる。

        定義 22.2.1 Γを文脈とし、t を項とする。(Γ, t) の解とは、σΓ├ σt : T なる組 (σ, T) のことである。

        例 22.2.2 Γ = f:X, a:Y かつ t = f a とする。このとき以下は全て (Γ, t) の解。

        • ([X |-> Y -> Nat], Nat)
        • ([X |-> Y -> Z], Z)
        • ([X |-> Y -> Z, Z |-> Nat], Z)
        • ([X |-> Y -> Nat -> Nat], Nat -> Nat)
        • ([X |-> Nat -> Nat, Y |-> Nat], Nat)

        演習 22.2.3 空の文脈における以下の項の異なる解を3つ提示せよ。
        λx:X. λy:Y. λz:Z. (x z) (y z)

        解答例)
        • ([X |-> Z -> A -> B, Y |-> Z -> A], B)
        • ([X |-> Z -> Nat -> Nat, Y |-> Z -> Nat], Nat)
        • ([X |-> Nat -> Nat -> Nat, Y |-> Nat -> Nat, Z |-> Nat], Nat)
      •  ◆  section 22-3. 制約に基づいた型付け

        項 t と文脈 Γ が与えられたとき、(Γ, t) の解が最低限充足せねばならない制約の集合を求めるアルゴリズムを示す。
        このアルゴリズムは本質的には通常の型検査アルゴリズムと同じであるが、唯一異なるのは、制約の検査というより後の検討のために記録するだけということである。

        定義 22.3.1
        • 制約集合 C は等式の集合 { Si = Ti i∈1..n } である。
        • S と T の代入による具体化 σS と σT が同一になるとき、代入σ は等式 S = T を単一化するという。
        • σ が C のすべての等式を単一化するとき、σ は C を単一化する(または充足する)という。

        定義 22.3.2 制約型付け関係 Γ├ t : T |x C は図22-1にて定義される。
        Γ├ t : T |x C は、「制約Cが充足されるならば、項 t は Γ の前提の下で型 T を持つ」と読む。
        また、|x は各部分導出において導入される型変数の集合を表している。

        図22-1 制約型付け規則
        Γ├ x : T |ø {}CT-Var
        x:T ∈ Γ

        Γ, x:T1 ├ t2 : T2 | x CCT-Abs
        Γ├ λx:T1. t2 : T1 -> T2 |x C

        Γ├ t1 : T1 |x1 C1Γ├ t2 : T2 |x2 C2x1∩x2 = x1∩FV(T2) = X2∩FV(T1) = øX はフレッシュC' = C1∪C2∪{T1=T2 -> X}CT-App
        Γ├ t1 t2 : X |x1∪x2∪{X} C'

         CT-Zero
        Γ├ 0 : Nat |ø {}

        Γ├ t1 : T |x CC' = C ∪ {T=Nat}CT-Succ
        Γ├ succ t1 : Nat |x C'

        Γ├ t1 : T |x CC' = C ∪ {T=Nat}CT-Pred
        Γ├ pred t1 : Nat |x C'

        Γ├ t1 : T |x CC' = C ∪ {T=Nat}CT-IsZero
        Γ├ iszero t1 : Bool |x C'

         CT-True
        Γ├ true : Bool |ø {}
         CT-False
        Γ├ false : Bool |ø {}

        Γ├ t1 : T1 |x1 C1Γ├ t2 : T2 |x2 C2Γ├ t3 : T3 |x3 C3x1,x2,x3は重複しないC' = C1∪C2∪C3∪{T1=Bool, T2=T3}CT-If
        Γ├ if t1 then t2 else t3 : T2 |x1∪x2∪x3 C'


        演習 22.3.3 あるS, x, Cに対して、結論が以下のようになるような制約型付け導出を構築せよ。
        ├ λx:X. λy:Y. λz:Z. (x z) (y z) : S |x C

        解答例)
        省略


        制約型付け関係のアイデアは、t が Γ の下で型付け可能かどうかを、制約 C を集めるのと同時に(制約 C を共有しながら)結果の型 S を求める。その後に、C を充足する任意の代入 σ により、σS は t が持ちうる型になる。充足する代入が存在しなければ、型付け可能なように t を具現化することはできない。

        例えば、項 t = λx:X->Y. x 0 に対する制約集合は [X -> Y = Nat -> Z] であり、結果の型 S は (X -> Y) -> Z となる。
        この制約集合を満たす代入は以下の通り複数ある。
        • σ = [X |-> Nat, Y |-> Z], σS = (Nat -> Z) -> Z
        • σ = [X |-> Nat, Z |-> Bool, Y |-> Bool], σS = (Nat -> Bool) -> Bool

        定義 22.3.4 Γ├ t : S | C とする。(Γ, t, S, C) の解とは、σ が C を充足し、かつσS = T となるような組 (σ, T) のことである。

        文脈Γ と項 t が与えられたとき、Γ と t の型変数を具体化して正しい型付けを生成する方法には、二種類の表現がある。

        (1) [宣言的な方法] 定義22.2.1の意味で、(Γ, t) のすべての解の集合として表現する。
        (2) [アルゴリズム的な方法] 制約型付け関係を通じて、Γ├ t : S | C を満たす S と C を見つけることで、(Γ, t, S, C) の解の集合を得る。

        この二つの表現は等価であることは以下の二段階で示される:
        • (Γ, t, S, C) の解はすべて (Γ, t) の解でもある (定理 22.3.5)
        • (Γ, t) の解はすべて、(Γ, t, S, C) の解に拡張できる (定理 22.3.7)

        定理 22.3.5 [制約型付けの健全性] Γ├ t : S | C とする。もし (σ, T) が (Γ, t, S, C) の解なら、それは (Γ, t) の解でもある。

        証明)
        Γ├ t : S | C の制約型付け導出に関する帰納法による。最後に使った規則で場合分けをする。

        1) CT-Var の場合 t = x x:S∈Γ C = {}
        C は空であるから、単に σS = T である。T-Var により、σΓ├ x : T を得る。

        2) CT-Abs の場合
        ...

        以下省略


        定義 22.3.6 x に含まれる変数のすべてに対して未定義で、他の変数には σ と同じように振る舞う代入を、σ\x と書く。

        定理 22.3.7 [制約型付けの完全性] Γ├ t : S |x C とする。(σ, T) が (Γ, t) の解であり、かつ dom(σ) ∩ x = ø ならば、σ'\x = σ となるような (Γ, t, S, C) の解 (σ', T) が存在する。

        証明)
        制約型付け導出に関する帰納法による。

        1) CT-Var の場合 t = x x:S∈Γ
        仮定より、(σ, T) は (Γ, x) の解であるから、T = σS とわかる。つまり、(σ, T) は (Γ, x, S, {}) の解。

        2) CT-Abs の場合
        ....

        以下省略


        系 22.3.8 Γ├ t : S | C とする。(Γ, t) の解が存在するとき、かつそのときに限り、(Γ, t, S, C) の解が存在する。

        証明)
        定理22.3.5 と 定理22.3.7 による


        演習 22.3.9 製品レベルのコンパイラでは、フレッシュな型変数名の生成は "gensym" という関数の呼び出しに置き換えられる。この操作は、隠れたグローバル変数に対して副作用を持つため、形式的な論証が難しい。しかし、使われていない型変数名を適用規則を通じて「直列的に受け渡す」ことで、この過程を数学的に模倣することはできる。
        F は異なる型変数名の列であるとし、制約生成関係を Γ├F t : T |F' C と書くことにする。フレッシュな型変数が必要なときは、F の最初の変数を取り出し、残りを F' として返す。
        このアルゴリズムの規則を書き下せ。その規則と元の制約生成規則が適切な意味で等価になることを証明せよ。

        規則)

        Γ├F t1 : T1 |F' C1Γ├F' t2 : T2 |F'' C2F'' = X, F'''CT-App'
        Γ├F t1 t2 : X |F''' C1 ∪ C2 ∪ {T1=T2->X}

        など

        証明)
        以下を示せば良い。
        • [健全性] Γ├F t : T |F' C かつ Γや t に現れる変数がFに出現しないならば、Γ├ t : T |F\F' C
        • [完全性] Γ├ t : T |x C ならば、xの名前の並び替えFが存在し、Γ├F t : T |ø C


        演習 22.3.10 演習22.3.9 のアルゴリズムを ML で実装せよ。

        解答例)
        省略(付録に記載)


        演習 22.3.11 一般的な再帰関数定義を扱うように制約生成アルゴリズムを拡張せよ。

        解答例)
        Γ├ t1 : T1 |x1 C1X は x1,Γ,t1に現れないCT-Rec
        Γ├ fix t1 : X |x1∪{X} C1 {T1=X->X}


      •  ◆  section 22-4. 単一化

        定義 22.4.1 代入 σ と σ' について、σ' = γ ∘ σ となる代入 γ が存在するとき、σ は σ' より一般的であるといい、σ ⊆ σ' と書く。

        定義 22.4.2 制約集合 C に対する主要単一子 (あるいは最汎単一子)とは、C を充足し、また C を充足する任意の代入 σ' に対して σ ⊆ σ' となるような代入 σ を指す。

        演習 22.4.3 以下の制約集合に対する主要単一化子を書き出せ。

        解答)
        制約集合主要単一化子
        {X=Nat, Y=X -> X}{X |-> Nat, Y |-> Nat -> Nat}
        {Nat -> Nat=X -> Y}{X |-> Nat, Y |-> Nat}
        {X -> Y = Y -> Z, Z=U -> W}{X |-> U -> W, Y |-> U -> W, Z |-> U -> W}
        {Nat=Nat -> Y}単一化できない
        {Y=Nat -> Y}単一化できない
        {}{}


        定義 22.4.4 型の単一化アルゴリズムを 図22-2のように定義する。

        「let {S = T} ∪ C' = C」は「集合 C から制約 S = T を選び、C の他の制約を C' として表す」と読む。

        図 22.2 単一化アルゴリズム
        unify(C) = if C = ø
        then {}
        else let {S = T} ∪ C' = C in
        if S = T
         then unify(C')
        else if S = X and X ∉ FV(T)
         then unify([X |-> T]C') ∘ [X |-> Y]
        else if T = X and X ∉ FV(S)
         then unify([X |-> S]C') ∘ [X |-> S]
        else if S = S1 -> S2 and T = T1 -> T2
         then unify(C' ∪ {S1 = T1, S2 = T2})
        else
         fail

        X ∉ FV(T) の部分は、出現検査(occur check)である。例えば、'a と 'a -> 'a は出現検査に引っかかり、単一化できない。


        定理 22.4.5
        (1) unify(C) はすべての C に対して、失敗するか代入を返すことで停止する。
        (2) unify(C) = σ ならば、σ は C の単一化子である。
        (3) δ が C の単一化子ならば、unify(C) = σ かつ σ ⊆ δ である。(σ は主要単一子)

        証明)
        略


        演習 22.4.6 単一化アルゴリズムを実装せよ。

        証明)
        略


      •  ◆  section 22-5. 主要型

        定義 22.5.1 (Γ, t, S, C) の主要解とは、解 (σ, T) であり、σ が主要単一子であるようなものをさす。さらに、T を Γ の下での t の主要型と呼ぶ。

        演習 22.5.2 λx:X. λy:Y. λz:Z. (x z) (y z) の主要型は何か。
        解答例)
        (Z -> A -> B) -> (Z -> A) -> Z -> B


        定理 22.5.3 (Γ, t, S, C) が解を持つならば、主要解が存在する。

        証明)
        単一化のアルゴリズムの性質による。


        系 22.5.4 (Γ, t) が解を持つかどうかは決定可能。

        証明)
        系22.3.8 と 定理22.5.3 による。


        演習 22.5.5 主要型を計算する型検査器を構成せよ。

        証明)
        のちの演習 22.5.6, 22.5.7 もこれの拡張問題。
        省略


      •  ◆  section 22-6. 暗黙の型注釈
        省略
      •  ◆  section 22-7. let 多相
        例えば、以下のようなプログラムを書き、恒等関数 id を定義したとする。

        let id = λx:X. x
        let a = id 1 in
        let b = id true in ...

        このとき、以下のような型付け・制約が行われる。
        • id は、任意の型X に対して、X -> X という型を持つ。
        • 続く a の定義で id を用いているため、制約 X -> X = Nat -> Nat が生成される。
        • さらに、b の定義で 制約 X -> X = Bool -> Bool が生成される。
        しかし、これらの制約は X に関して充足不可能な要求を行なっているので、プログラム全体が型付け不能になってしまう。

        なぜこのような問題が起きたのかというと、同じ型変数 X が両方の場合で用いらているため、二箇所の id の引数が同じ型でなければならない、という誤った制約ができてしまったからである。
        やりたいのは、id が使われる度にそれぞれに異なる変数Xを関連づけるということである。

        これを達成するための単純な方法として、のちのすべての id の出現を以下のように項で置き換えるという方法がある。

        let id = λx:X. x
        let a = (λx:X. x) 1 in
        let b = (λx:X. x) true in ...

        つまり、let の規則を以下のように変更するという手段。

        Γ├ [x |-> t1] t2 : T2Γ├ t1 : T1LetPoly
        Γ├ let x = t1 in t2 : T2

        しかし、id が何回も出現する場合、型検査器は元のプログラムのサイズに対して指数的な量の計算をしなければならないことがありえてしまう。
        型検査のやり直しを避けるため、形式的に等価ではあるが、型付け規則を巧妙に定式化し直すことにする。概略は以下である。

        1. 制約型付け規則を使って、右辺 t1 に対して 型 S1 と関連する制約の集合 C1 を計算する
        2. 単一化を用いて制約集合 C1 に対する最も一般的な解 σ を見つけ、t1 の主要型 T1 を σS1 を通じて得る。
        3. T1 に残っている型変数 X1, ..., Xn を一般化し、型スキーム ∀X1 ... Xn. T1 を定義する。
          (意図せぬものを ∀X1, ..., Xn により束縛しないように注意)
        4. 型スキームを記録できるように文脈を拡張してから、本体 t2 の型検査を始める。
        5. t2 の中で束縛変数 x が現れる度に、その型スキームを文脈から引き、フレッシュな型変数 Y1, ..., Yn を生成することで、型スキームを具体化する。


        最後に「値制限」について、話しておく。

        書き換え可能な記憶域セルのように副作用のある機能と、この多相性との相互作用に注意する必要がある。

        let r = ref (λx. x) in
        r := λx:Nat. succ x;
        !r true

        概説のアルゴリズムを使うと、以下のようなことが起きる。
        • let の右辺の主要型を一般化した型スキームは ∀X. Ref (X -> X) となる。
        • この型スキームは、2行目の破壊的代入を型検査するとき、Ref (Nat -> Nat) に具体化される。
        • 続いて3行目の型検査で、Ref (Bool -> Bool) に具体化される。
        これは健全ではない。3行目の呼び出しで succ を true に適用することになってしまう。

        この節で導入された型付け規則は、let 式に出くわしたらその右辺を本体に直ちに代入すべきだといっている。
        しかし評価規則は、右辺が値に簡約された後のみ代入できると言っている。

        この不具合を修正するには、評価か型付けのどちらかを修正すればよい。

        評価を変えるならば、プログラムは以下のようになる。

         E-Let
        let x=t1 in t2 -> [x |-> t1] t2

        let r = ref (λx. x) in
        (ref (λx. x)) := λx:Nat. succ x;
        (!(ref (λx. x))) true

        これは文句なく安全であるが、値呼び評価順序の直観にはそぐわない、やや奇妙な意味論を持つ言語になってしまう。

        評価規則に合わせて型付け規則を変更するほうがよい。これは簡単。
        右辺が構文的に値であるときのみ、let 束縛を多相的に扱えるという制約を課すだけでよい。この制約は「値制限」と呼ばれる。
        この制限により、先の r に割り当てられる型は、∀X. Ref (X -> X) ではなく Ref (X -> X) となる。さらに二行目により X は Nat に強制され、三行目では Nat と Bool が単一化できないために型検査で失敗することになる。


        演習 22.7.1 この節で概説したアルゴリズムを実装せよ。

        証明)
        省略


      •  ◆  section 22-8. 注釈
        省略