Hindley-Milner 中的“让”推理

`Let` inference in Hindley-Milner

我正在尝试通过用我常用的语言 Clojure 实现算法 W 来自学 Hindley-Milner 类型推断。我 运行 遇到了 let 推理的问题,我不确定我是否做错了什么,或者我期望的结果是否需要算法之外的东西。

基本上,使用 Haskell 表示法,如果我尝试推断其类型:

\a -> let b = a in b + 1

我明白了:

Num a => t -> a

但我应该得到这个:

Num a => a -> a

同样,我实际上是在 Clojure 中这样做的,但我不认为问题是 Clojure 特有的,所以我使用 Haskell 符号来使其更清楚。当我在 Haskell 中尝试时,我得到了预期的结果。

无论如何,我可以通过将每个 let 转换为函数应用程序来解决该特定问题,例如:

 \a -> (\b -> b + 1) a

但后来我失去了 let 多态性。由于我对 HM 没有任何先验知识,我的问题是我是否遗漏了一些东西,或者这是否只是算法的工作方式。

编辑

如果有人遇到类似问题并且想知道我是如何解决它的,我正在关注 Algorith W Step By Step。在第 2 页的底部,它说 "It will occasionally be useful to extend the Types methods to lists." 因为它对我来说不是强制性的,所以我决定跳过那部分,稍后再看。

然后我将 TypeEnvftv 函数直接翻译成 Clojure,如下所示:(ftv (vals env))。由于我将 ftv 实现为 cond 形式并且没有 seq 的子句,它只是为 (vals env) 返回 nil。这当然正是静态类型系统旨在捕获的那种错误!无论如何,我只是将 ftv 中与 env 映射相关的子句重新定义为 (reduce set/union #{} (map ftv (vals env))) 并且它有效。

很难说出哪里出了问题,但我猜你的 let-generalization 是错误的。

让我们手动输入字词。

\a -> let b = a in b + 1

首先,我们将 a 与一个新类型变量相关联,比如 a :: t0

然后我们输入b = a。我们也得到 b :: t0

然而,这是关键点,我们应该b的类型概括为b :: forall t0. t0。这是因为我们只能对环境中不存在的 tyvar 进行概括:在这里,相反,我们在环境中确实有 t0,因为 a :: t0 在那里。

如果您对它进行概括,您会得到一种对于 b 而言过于笼统的类型。然后 b+1 变为 b+1 :: forall t0. Num t0 => t0,整个术语变为 forall t0 t1. Num t1 => t0 -> t1,因为 ab 的类型不相关(t0,一旦泛化,可以 alpha 转换为 t1).