OCaml 'underscore types'(例如'_a)是否引入了运行时类型错误/健全性违规的可能性?

Do OCaml 'underscore types' (e.g. '_a) introduce the possibility of runtime type errors / soundness violations?

我阅读了一些关于标准 ML 中的值限制的内容,并尝试将示例转换为 OCaml 以查看它会做什么。似乎 OCaml 在 SML 由于值限制而拒绝程序的上下文中生成这些类型。我还在其他上下文中看到过它们,例如尚未 "specialized" 到特定类型的空哈希表。

http://mlton.org/ValueRestriction

以下是 SML 中被拒绝的程序示例:

val r: 'a option ref = ref NONE
val r1: string option ref = r
val r2: int option ref = r
val () = r1 := SOME "foo"
val v: int = valOf (!r2)

如果你将第一行逐字输入到新泽西州的 SML 回复中,你会得到 以下错误:

- val r: 'a option ref = ref NONE;
stdIn:1.6-1.33 Error: explicit type variable cannot be generalized at its binding declaration: 'a

如果你放弃显式类型注释,你会得到

- val r = ref NONE

stdIn:1.6-1.18 Warning: type vars not generalized because of
   value restriction are instantiated to dummy types (X1,X2,...)
val r = ref NONE : ?.X1 option ref

这个虚拟类型到底是什么?它似乎完全无法访问并且无法与任何东西统一

- r := SOME 5;

stdIn:1.2-1.13 Error: operator and operand don't agree [overload conflict]
  operator domain: ?.X1 option ref * ?.X1 option
  operand:         ?.X1 option ref * [int ty] option
  in expression:
    r := SOME 5

相比之下,在 OCaml 中,虚拟类型变量是可访问的,并且与它可以访问的第一件事统一。

# let r : 'a option ref = ref None;;
val r : '_a option ref = {contents = None}

# r := Some 5;;
- : unit = ()
# r ;;
- : int option ref = {contents = Some 5}

这有点令人困惑,并提出了一些问题。

1) 符合标准的 SML 实现是否可以选择使上面的 "dummy" 类型可访问?

2) OCaml 如何在没有值限制的情况下保持稳健性?它提供的保证比 SML 弱吗?

3) 类型 '_a option ref 似乎不如 'a option ref 多态。为什么 let r : 'a option ref = ref None;;(带有显式注释)在 OCaml 中不被拒绝?

1) Could a conforming SML implementation choose to make the "dummy" type above accessible?

修订后的 定义 (SML97) 没有指定 "dummy" 类型;它正式指定的只是 val 不能引入多态类型变量,因为右侧表达式不是非扩展表达式。 (还有一些关于类型变量没有泄漏到顶层的评论,但正如 Andreas Rossberg 在他的 Defects in the Revised Definition of Standard ML 中指出的那样,这些评论实际上是关于未确定的类型而不是出现在定义的形式主义中的类型变量,因此它们不能真正被视为需求的一部分。)

在实践中,我认为实现有四种方法:

  • 一些实现在类型检查期间拒绝受影响的声明,并强制程序员指定单态类型。
  • 一些实现,如 MLton,阻止泛化,但推迟统一,以便适当的单态类型可以在程序的后期变得清晰。
  • SML/NJ,如您所见,发出警告并实例化一个随后无法与任何其他类型统一的虚拟类型。
  • 我想我听说有些实现默认为 int?我不确定。

所有这些选项都可能是允许的并且显然是合理的,尽管 "defer unification" 方法确实需要注意确保类型不与尚未生成的类型名称(尤其是类型名称)统一从仿函数内部,从那时起,单态类型可能对应于仿函数的不同应用中的不同类型,这当然会与常规多态类型具有相同类型的问题。

2) How does OCaml preserve soundness without the value restriction? Does it make weaker guarantees than SML does?

我对 OCaml 不是很熟悉,但是从你写的内容来看,它使用的方法与 MLton 相同;所以,它不应该牺牲稳健性。

(顺便说一下,尽管你暗示了什么,OCaml 确实 有值限制。OCaml 中的值限制与 SML 中的值限制之间存在一些差异,但是 none 你的代码片段与这些差异有关。你的代码片段只是展示了 OCaml 与 SML 的一种实现方式中限制的执行方式的一些差异。)

3) The type '_a option ref seems less polymorphic than 'a option ref. Why isn't let r : 'a option ref = ref None;; (with an explicit annotation) rejected in OCaml?

同样,我对 OCaml 不是很熟悉,但是 — 是的,这对我来说似乎是个错误!

弱多态类型('_ 风格的类型)是为了方便编程而不是类型系统的真正扩展。

2) How does OCaml preserve soundness without the value restriction? Does it make weaker guarantees than SML does?

OCaml 并没有牺牲值限制,而是实现了一种启发式方法,使您无需像 ref None 这样系统地注释值的类型,其类型只是“每周”多态。这种通过查看当前“编译单元”的启发式方法:如果它可以确定每周多态类型的实际类型,那么一切都会像初始声明具有适当的类型注释一样工作,否则编译单元将被拒绝并显示消息:

Error: The type of this expression, '_a option ref,
       contains type variables that cannot be generalized

3) The type '_a option ref seems less polymorphic than 'a option ref. Why isn't let r : 'a option ref = ref None;; (with an explicit annotation) rejected in OCaml?

这是因为 '_a 不是“真正的”类型,例如禁止编写明确定义此“类型”的值的签名:

# module A : sig val table : '_a option ref end = struct let option = ref None end;;
Characters 27-30:
  module A : sig val table : '_a option ref end = struct let option = ref None end;;
                             ^^^
Error: The type variable name '_a is not allowed in programs

通过使用递归声明将弱多态变量声明和后面完成类型定义的函数使用打包在一起,可以避免使用这些弱多态类型,例如

# let rec r = ref None and set x = r := Some(x + 1);;
val r : int option ref = {contents = None}
val set : int -> unit = <fun>

回答你上一个问题的第二部分,

3) [...] Why isn't let r : 'a option ref = ref None;; (with an explicit annotation) rejected in OCaml?

这是因为 OCaml 对类型注释中出现的类型变量有不同的解释:它将它们解释为存在量化,而不是普遍量化。也就是说,类型注释只需要对 some 变量的可能实例化是正确的,而不是 all。例如,偶数

let n : 'a = 5

在 OCaml 中完全有效。可以说,这是相当误导的,而不是最佳的设计选择。

要在 OCaml 中强制执行多态性,您必须编写类似

的内容
let n : 'a. 'a = 5

这确实会导致错误。但是,这引入了 local 量词,因此与 SML 仍然有些不同,并且不适用于 'a 需要绑定到其他地方的示例,例如以下:

fun pair (x : 'a) (y : 'a) = (x, y)

在 OCaml 中,您必须将其重写为

let pair : 'a. 'a -> 'a -> 'a * 'a = fun x y -> (x, y)