GHC/GHCi 意外接受了代码

Code unexpectedly accepted by GHC/GHCi

我不明白为什么这段代码应该通过类型检查:

foo :: (Maybe a, Maybe b)
foo = let x = Nothing in (x,x)

因为每个组件都绑定到同一个变量 x,我希望这个表达式的最通用类型是 (Maybe a, Maybe a)。如果我使用 where 而不是 let,我会得到相同的结果。我错过了什么吗?

简而言之,x 的类型被 let 泛化了。这是 Hindley-Milner 类型推理算法中的关键步骤。

具体来说,let x = Nothing 最初分配 x 类型 Maybe t,其中 t 是一个新的类型变量。然后,类型被泛化,普遍量化它的所有类型变量(技术上:除了在其他地方使用的变量,但这里我们只有 t)。这导致 x :: forall t. Maybe t。请注意,这与 Nothing :: forall t. Maybe t.

完全相同

因此,每次我们在代码中使用 x 时,它指的是一个可能不同的类型 Maybe t,很像 Nothing。由于这个原因,使用 (x, x) 得到与 (Nothing, Nothing) 相同的类型。

相反,lambda 具有相同的泛化步骤。相比之下,(\x -> (x, x)) Nothing "only" 的类型为 forall t. (Maybe t, Maybe t),其中两个组件都被强制为同一类型。这里 x 再次被分配类型 Maybe tt 新鲜,但它不是一般化的。然后 (x, x) 被分配类型 (Maybe t, Maybe t)。仅在顶层我们概括添加 forall t,但此时获得异构对为时已晚。