Hindley-Milner型システムを10分で理解する

WARNING

この記事ChatGPTによって中国語から翻訳されたものでいくつかのりがまれているかもしれません。不正確部分があればご了承ください

型推論

型推論とは

プログラマー怠惰生物です。多くの場合、私たちは冗長指定したくありません

1let t = 1 + 1
2let f x = x + 1

ここでの tfx 3つの変数それぞれの非常明確ですもちろんよりくの場合、型明示的指定するいです

また、型指定するのがしい場合もあります。例えば

1let s f g x = f x (g x)
2let k x y = x
3let i = s k k
4let ϕ = s (k (s i)) k

ϕでしょうか

ϕ計算できますが、非常面倒です。自然、人間代替するアルゴリズムつけることです

実際、このコードhaskellf#replるとϕp -> (p -> q) -> qであることをえてくれますそして、彼らが使用しているアルゴリズムHindley-Milner型推論アルゴリズムです

型推論アルゴリズム

一言えば、型推論アルゴリズム方程式くことです。方程式構築過程のようになります

  1. 各関数パラメータおよび各関数呼しの型変数xᵢします
  2. 関数呼方程式構築しますf xという関数呼しは、以下ルール方程式構築できます
f:abx:t1(f x):t2t1t2=ab \frac{f:a \rightarrow b\quad x:t_1 \quad (f\ x):t_2}{t_1 \rightarrow t_2 = a \rightarrow b}

えばこのような関数

1let f a b = a + b

このような方程式構築できます

a:t1b:t2f:t3a+b:t4(+):intintintt1t2t4= intintintt3=t1t2t4 \begin{aligned} a:\\ &t_1 \\\\ b:\\ &t_2 \\\\ f:\\ &t_3 \\\\ a + b:\\ &t_4 \\\\ (+):\\ & \mathsf{int} \rightarrow \mathsf{int} \rightarrow \mathsf{int} \\\\ t_1 \rightarrow t_2 \rightarrow t₄ =&\ \mathsf{int} \rightarrow \mathsf{int} \rightarrow \mathsf{int} \\\\ t_3 =&\\ t_1 \rightarrow t_2 \rightarrow t_4 \end{aligned}

以下つの方程式くことで、各変数ることができます。現在問題このような方程式をどのようにくかです

単一化

単一化自動定理証明からのアルゴリズム、上記方程式くために使用できます。背後には一連置換する理論がありますスペース制約上、ここではこれらの理論については議論せず、最自然アルゴリズム紹介します1

まず、上記方程式では、最大2種類参加します。一つは型変数xᵢもうつは矢印xᵢ → xⱼです。実際には、矢印xᵢ→ xⱼf(xᵢ, xⱼ)なすことができこれにより2つの形式られます

  1. 型変数。
  2. f(v₀, v₁, v₂, ...)形式。上記intint()なすことができます

アルゴリズムりです

各方程式走査します

  1. 自己ループチェック、両側
    xi=xi x_i = \cdots x_i \cdots
    えば xi=f(xi)x_i = f(x_i) 形式出現した場合、アルゴリズム失敗します
  2. 方程式形式:
    xi==xi \begin{aligned} x_i &= \star \\\\ \star &= x_i \end{aligned}

    である場合、\starであれまだ走査していない方程式xix_i\starえます
  3. それがケース1.でない場合、つまり
    f(x0,x1,x2,)=g(y0,y1,y2,) f(x_0, x_1, x_2, \cdots) = g(y_0, y_1, y_2, \cdots)
    であれば
    • fgf ≠ g場合、アルゴリズム失敗します
    • f=gf = g場合、nn nn f パラメータ しい待遍方程式 xi=yix_i = y_i追加します

Let多相性

これで型推論アルゴリズムができました。次のような関数えてみましょう

1let f x = x

上記推論アルゴリズムではf:x₀ → x₀推論されここには自由変数 x₀がありますSystem F型表記法いてこのような関数「一般化」します

f:ψ(x0)f:x0.ψ(x0) \frac{f:\psi(x_0)}{f:\forall x_0.\psi(x₀)}

これによりf「多相型」になり、「具体化」することができます

f:x.ψ(x)f:ψ(a) \frac{f:\forall x.\psi(x)}{f:\psi(a)}

面倒問題いつ一般化具体化うべきかということです

自然fun x -> ...のようなラムダ外部ジェネリックジェネリックされた変数参照するときにインスタンスうことです。例えば

1// 以下はすべて型が正しい項目です
2(λ x. x) 10
3(λ x. x) true

このようにすると、問題ラムダだけが多型型になりラムダ内部変数はまだ単一型しかてないということです

1(λf.(λ t. f true)(f 0))(λx.x)

上記るとλx.x多型型であってもf多型型として推論されません。実際にはff 0int → x₀推論されf truebool → x₀推論されますこれによりエラー発生します

F#ocamlではfジェネリックえる方法してみてください

1let f x = x in
2    let t = f 0 in f true

無型または単純型ラムダでは、上記2つのコード完全等価letはただのシンタックスシュガーですしかしML言語ではletシンタックスシュガーではありません。実際、ML言語ではジェネリックタイミングはまさにletバインディング時点です

つまり

  1. let var = e₁ in e₂ではe₁ジェネリックされますジェネリック方法のとおりです
    • 現在環境ΓΓ自由変数F(Γ)F(Γ)
    • e1e_1自由変数F(e1)F(e_1)
    • F(e1)F(Γ)F(e_1) - F(Γ)各自由変数xix_i走査e1:te_1:te1:xi.te_1:∀x_i.t変更します
  2. 現在環境ΓΓからジェネリック変数e:t1e:t_1参照するたびに、新しい型変数e:t1e:t_1インスタンスt1t_1まなくなるまでけます

これがHindley-Milnerアルゴリズム基本的です。具体的実装についてはalgorithm Jalgorithm Wがあります。詳細WikipediaMilner原著論文参照してください実装algorithm J採用しています

値制約

もし本当コードしたなら

1let s f g x = f x (g x)
2let k x y = x
3let i = s k k
4let ϕ = s (k (s i)) k

あなたはHaskellでは問題ないことに気付くでしょう

しかしF#ではこれはエラーになります

なぜエラーになるのでしょうかこれはもっとトリッキープログラムから説明しますF#には可変型があります

1let mutable x = 10
2x <- 20
3printfn "%A" x

このようなプログラムえられた場合

1let mutable x = []
2x <- [3]
3List.map (fun x -> x = true) x

最初エラーになりますしかし、最初エラーにならないと仮定すると2行目エラーになるでしょうかこれをしく調べてみましょう

まず<-F#ではされていますがF#には!:=refという廃止された演算子がありそののようになります

1ref  : 'a -> 'a ref
2(!)  : 'a ref -> 'a
3(:=) : 'a ref -> 'a -> unit

上記プログラムのようにくことができます

1let x = ref []
2x := [3]
3List.map (fun x -> x = true) x

2行目参照されるときx't list refつまり∀t.ref[list[t]]でありこれはref[list[b]]例化されるべきですこれにより、次2つの等式られます

ref[list[b]]=ref[a]a=list[int] \begin{aligned} \mathsf{ref}[\mathsf{list}[b]] =& \mathsf{ref}[a] \\\\ a =& \mathsf{list}[\mathsf{int}] \end{aligned}

上記方程式からb=int,a=list[int]b = \mathsf{int}, a = \mathsf{list}[\mathsf{int}] られこれはエラーになりませんさらにきな問題2行目チェック後、bかにintになりますがこれは 例化された xでありΓΓ x't list refであるため3行目エラーにならない

これは非常いことで、型システム不健全性導入します3行目間違いなくランタイムエラーこします

この問題解決するためにANDREW K. WRIGHT2値制限value restriction導入しました。値制限意味非常シンプルですlet var = e₁ in e₂e₁構文値syntactic valueである場合にのみe₁一般化しますもしe₁構文値でなくかつそれがΓ\Gammaにない自由変数っているならエラー報告します

構文値とはML言語では、定数、変数、λ式、型コンストラクタしますもちろんref[a]構文値とはなされませんこれにより、問題らかに解決されますなぜなら、上記エラー本質的副作用発生によるものでありMLでは構文値評価副作用発生しないからです

この解決策過近似であり、多くのしいプログラムえば前述プログラム拒否されますHaskell副作用がないためこの問題存在しませんこれがHaskellf . gのような関数わせを大量使用できる一方、MLではできない理由です

量詞位置

System Fでは、量詞任意位置出現できます。例えばx.(xy.y)∀x.(x → ∀y.y)。実際、System FYコンビネータ構築することはできません強正規性する、無型ラムダ計算(xx)(x x)構造エンコードすることはできます

λ(x:α.αα).(x(α.αα))x λ(x:∀α.α→α).(x (∀α.α→α))x

このλ (α.αα)(α.αα)(∀α.α→α)→(∀α.α→α)その最初引数それがΛα.λ(x:α).xというid関数にのみ作用できることを決定しますそれをid適用する形式無型λくと

(λx.xx)(λx.x) (λx.xx)(λx.x)

間違いなくそれはβ簡約によってλx.xλx.xになります

これによりSystem Fλではその引数「多型型」むことができることがわかりますではなぜたちの型推論アルゴリズムがその多型型推論できなかったのでしょうか

これは、私たちの推論アルゴリズム「単純型λ式」型推論アルゴリズムなすことができHMシステム単純型λ拡張であるからです

ここまでると、自然疑問じますSystem F同様推論うことができるのでしょうかつまり、私たちはこのように

λ(x:t1).(x t2)x λ(x:t_1).(x\ t_2)x

このじて t1t_1 t2t_2 ることができるアルゴリズム存在するのでしょうか

1985年、Hans-J. Boehm論文 PARTIAL POLYMORPHIC TYPE INFERENCE IS UNDECIDABLE fixコンビネータ追加すると、上記問題判定不能になるとえてくれます1995年、J.B.Wells論文 Typability and type checking in System F are equivalent and undecidable 、明示的 t1t₁ t2t₂ えずに、最終的チェックだけをめるとこの問題はまだ判定不能であるとさらにえてくれます

したがってHMシステム2種類λ変種なすことができます

  • 単純型λ変種なす場合、HMシステムlet var = e₁ in e₂という形式追加e₁一般化許可パラメータ多態性実現します
  • System F変種なす場合、HMシステム量子位置制限します最前面だけですこの制限によりHMシステム推論実現できSystem F型推論実現できません

しかしこれはより強力システム型推論うことができないという意味ではありません。実際、Haskellはより強力型推論実装ScalaTypescriptRustなどの言語 局部型推論local type inferenceばれる技術使用しておりこれはプログラマ一部マーク提供することで、不必要マーク大部分消去することができます