我實現Algorithm W(該Hindley-Milner type system)在JavaScript:如何使用算法W鍵入檢查遞歸定義?
它實現了上述規則的功能是typecheck
並且其具有以下特徵:
typecheck :: (Context, Expr) -> Monotype
其被定義爲如下:
function typecheck(context, expression) {
switch (expression.type) {
case "Var":
var name = expression.name;
var type = context[name];
return inst(type);
case "App":
var fun = typecheck(context, expression.fun);
var dom = typecheck(context, expression.arg);
var cod = new Variable;
unify(fun, abs(dom, cod));
return cod;
case "Abs":
var param = expression.param;
var env = Object.create(context);
var dom = env[param] = new Variable;
var cod = typecheck(env, expression.result);
return abs(dom, cod);
case "Let":
var assignments = expression.assignments;
var env = Object.create(context);
for (var name in assignments) {
var value = assignments[name];
var type = typecheck(context, value);
env[name] = gen(context, type);
}
return typecheck(env, expression.result);
}
}
A li關於數據類型的一點點:
上下文是一個將標識符映射到多類型的對象。
type Context = Map String Polytype
的表達式由下面的代數數據類型定義:
data Expr = Var { name :: String } | App { fun :: Expr, arg :: Expr } | Abs { param :: String, result :: Expr } | Let { assignments :: Map String Expr, result :: Expr } | Rec { assignments :: Map String Expr, result :: Expr }
此外,我們具有由所述算法需要的,但是不能在一個問題必需以下功能:
inst :: Polytype -> Monotype
abs :: (Monotype, Monotype) -> Monotype
gen :: (Context, Monotype) -> Polytype
該inst
功能專門化polytype和d gen
函數概括了一個monotype。
不管怎樣,我想延長我的typecheck
功能允許recursive definitions還有:
其中:
但是,我陷入了雞和雞蛋的問題。上下文第一個假設爲v_1 : τ_1, ..., v_n : τ_n
。此外,這意味着e_1 : τ_1, ..., e_n : τ_n
。因此,您首先需要創建上下文以查找e_1, ..., e_n
的類型,但爲了創建上下文,您需要查找e_1, ..., e_n
的類型。
你如何解決這個問題?我正在考慮給標識符v_1, ..., v_n
分配新的單型變量,然後將每個單型變量與其各自的類型統一起來。這是OCaml爲其let rec
綁定使用的方法。然而,這種方法不會產生如由以下OCaml的代碼演示了最普遍的類型:
$ ocaml
OCaml version 4.02.1
# let rec foo x = foo (bar true)
and bar x = x;;
val foo : bool -> 'a = <fun>
val bar : bool -> bool = <fun>
但是,GHC並計算出最普遍的類型:
$ ghci
GHCi, version 7.10.1: http://www.haskell.org/ghc/ :? for help
Prelude> let foo x = foo (bar True); bar x = x
Prelude> :t foo
foo :: Bool -> t
Prelude> :t bar
bar :: t -> t
正如你所看到的,OCaml的推斷類型val bar : bool -> bool
而GHC推斷類型bar :: t -> t
。Haskell如何推斷最常用的函數bar
?
我從@augustss'回答中瞭解到遞歸多態函數的類型推斷是不可判定的。例如,哈斯克爾不能沒有額外的類型註釋推測出以下size
函數的類型:
data Nested a = Epsilon | Cons a (Nested [a])
size Epsilon = 0
size (Cons _ xs) = 1 + size xs
如果我們指定類型簽名size :: Nested a -> Int
然後哈斯克爾接受的方案。但是,如果我們只允許代數數據類型的一個子集inductivetypes,那麼數據定義Nested
就會變得無效,因爲它不是歸納的;因此,數據定義Nested
是無效的。如果我沒有弄錯,那麼歸納多態函數的類型推斷確實是可以確定的。如果是這樣,那麼用於推斷多態感應函數類型的算法是什麼?
我剛剛在OCaml中測試了以下聲明:let rec foo x = foo(bar true)and bar x = x ;;'。推斷的類型是'val foo:bool - >'a'和'val bar:bool - > bool'。但是,在GHC中,當我測試'let foo x = foo(bar True)時; bar x = x'推斷的類型更普遍,'foo :: Bool - > t'和'bar :: t - > t'。我想知道GHC如何推斷出功能「bar」的最普通類型。你會碰巧知道嗎? –
在你的示例中,bar不是遞歸的,它的類型可以分別推斷出來。 –
Haskell類型檢查從定義的相互遞歸組開始,並將它們分成強連通組件。然後每個組件都自行處理。這給出了比一次處理整個初始羣體更普遍的類型。 (這不僅僅是ghc,它是Haskell規範的一部分。) – augustss