如果我有以下OCaml功能:
let myFun = CCVector.map ((+) 1);;
它在Utop中运行良好,并且Merlin不会将其标记为编译错误。但是,当我尝试编译它时,我收到以下错误:
错误:此表达式的类型, (int,'_ a)CCVector.t - > (int,'_ b)CCVector.t , 包含无法推广的类型变量
如果我对它进行扩展,那么它编译得很好:
let myFun foo = CCVector.map ((+) 1) foo;;
所以我想知道为什么它不能以eta-reduced形式进行编译,以及为什么eta-reduced表单似乎在顶层(Utop)中工作,但在编译时却没有?
哦,CCVector的文档是here。 '_a部分可以是`RO或`RW,具体取决于它是只读还是可变。
答案 0 :(得分:23)
你在这里得到的是ML语言家族的价值多态性限制。
限制的目的是将let-polymorphism和副作用放在一起。例如,在以下定义中:
let r = ref None
r
不能有多态类型'a option ref
。否则:
let () =
r := Some 1; (* use r as int option ref *)
match !r with
| Some s -> print_string s (* this time, use r as a different type, string option ref *)
| None -> ()
错误地类型检查为有效,但它崩溃了,因为引用单元格r
用于这两种不兼容的类型。
为了解决这个问题,在80年代进行了许多研究,价值多态性就是其中之一。它将多态性限制为只允许其定义形式为#34;非扩展"的绑定。 Eta扩展形式是非扩展的,因此你的eta扩展版本的myFun
具有多态类型,但不是eta减少的。 (更准确地说,OCaml使用这个值多态的宽松版本,但故事基本相同。)
当let绑定的定义是扩展的时,没有引入多态,因此类型变量是非泛化的。这些类型在顶层打印为'_a
,它们的直观含义是:它们必须在以后实例化为某种具体类型:
# let r = ref None (* expansive *)
val r : '_a option ref = {contents = None} (* no polymorphism is allowed *)
(* type checker does not reject this,
hoping '_a is instantiated later. *)
我们可以在定义后修复类型'_a
:
# r := Some 1;; (* fixing '_a to int *)
- : unit = ()
# r;;
- : int option ref = {contents = Some 1} (* Now '_a is unified with int *)
修复后,您无法更改类型,从而防止上述崩溃。
允许此输入延迟,直到编译单元的输入结束。 toplevel是一个永不结束的单元,因此您可以在会话的任何位置使用'_a
类型变量的值。但是在单独的编译中,'_a
变量必须实例化为某种类型而没有类型变量,直到ml
文件结束:
(* test.ml *)
let r = ref None (* r : '_a option ref *)
(* end of test.ml. Typing fails due to the non generalizable type variable remains. *)
这是您的myFun
函数与编译器一起发生的事情。
AFAIK,对多态性和副作用问题没有完美的解决方案。与其他解决方案一样,值多态限制也有其自身的缺点:如果您想拥有多态值,则必须以非扩展的方式进行定义:您必须eta-expand myFun
。这有点糟糕但被认为是可以接受的。
您可以阅读其他一些答案: