这是 SML 中 value restriction 的结果。
当语言为您的程序推断类型时,它可以找到适用于 any 类型的表达式,它用类型变量表示。 map 函数就是一个很好的例子。 (我的语法可能有点不对劲,因为我从未使用过 SML。)
fun map f nil = nil
| map f (x::xs) = f x :: map f xs
由于此函数适用于 任何 类型的列表,因此它会获得一个类型变量:
('a -> 'b) -> 'a list -> 'b list
我们可以对int list 和string list 使用相同的map 函数——这是一个通用类型变量。值得注意的是nil 的情况:nil 可以很容易地成为int 的空列表作为string 的空列表。它的类型为'a list。
在一个完美的世界里,这就是我们所拥有的一切。但是有一个问题:可变引用。按照与上述相同的逻辑,以下ref 的类型中也会有一个类型变量:
val x = ref nil
我们希望x 是('a list) ref。就像nil 本身一样,它可以很容易地成为(int list) ref 或(string list) ref——或者可以吗?问题是我们可以设置引用。如果我们可以使用x 好像 它有更具体的类型(int list) ref,我们可以将它设置为[1,2,3]。然后,如果我们可以将它用作其他地方的(string list) ref,我们可以将[1,2,3] 读出到期望字符串列表的东西!这是个问题。
为了克服这个问题,SML 有值限制。粗略地说,这意味着那些“看起来”不像函数的东西不能是完全多态的——它们不能有可泛化的类型变量。相反,x 的类型将基于我们使用它的第一个具体类型(即(int list) ref)。如果我们继续尝试将x 与不同的具体类型一起使用,我们将收到关于不可泛化类型变量的错误。
在某种意义上,一个不可泛化的类型变量只是一个占位符,直到我们做使用x 并给它一个具体的类型。这有点令人困惑,因为它看起来仍然像一个普通类型变量 ('a),但是如果我们以多种方式使用它,就会给我们一个错误。我认为 OCaml 在区分两者方面做得更好。 OCaml 会将x 的类型推断为'_a,这在语法上与普通类型变量不同,并清楚地表明它只是一个占位符,而不是普通的多态值。
这在语言中有点小问题,但如果你想拥有这样的可变引用,这基本上是不可避免的。